Version 5 of lshift -Adding Unique Items to Lists of Fixed Length

Updated 2006-04-17 16:06:57

WJG(17/April/2006) It's often useful to keep lists of unique items but those lists need to be of fixed length. Adding a new item to the list, means that the oldest item has to be deleted: a 'recent documents' list for example. Here's a simple solution. Indeed, it's so simple that the comments are longer than the code itself!

 #---------------
 # lshift.tcl
 #---------------

 proc lshift {l i} {
  # if the item already exists, just return the list back
  if {![lsearch $l $i]} {return $l}
  # insert new item at the top
  set l [linsert $l 0 $i]
  # delete the one at the end
  return [lrange $l 0 end-1]
 }

 #---------------
 # the ubiquitous demo
 #---------------
 proc demo {} {
  # just for the linux users
  catch {console show}

  # create a list
  foreach a {apple pear bannana} {
    lappend fruit $a
  }

  # the list before changes
  puts $fruit

  # cherry will be added, bannana will go
  set fruit [lshift $fruit cherry] 
  puts $fruit

  # we already have cherry, no change 
  set fruit [lshift $fruit cherry] 
  puts $fruit

  }

  demo

MG Apr 17 2006 - Did you know you can use lrange $list 0 end-1 to trim the last element from the list, and avoid the expr? A couple of suggestions, too: it might be better if you could specify a maximum length instead (recent document/history lists won't always be fully populated, and the user may clear it). Also, if an item is already in the list, it could be better to bring it to the beginning, rather than do nothing.

WJG(17/April/2006) Following your comment, I have changed the lrange statement as you suggested. The overall list length; well, that could be adjusted by simply adding a new items to the list elsewhere. The suggestion about swapping is a good one. After all, if a file is to be re-loaded, it usually is the last one worked on.

WJG(17/April/2006) So, here we go. Here's a more complete example. Using the comments made by MG.

 # recentfiles.tcl

 #---------------
 # keep our values private
 #---------------
 namespace eval recent {

  set cmd puts  ;# default command called in menu
  set max 5     ;# maximum of 5 files in the list

  # build list to hold filenames
  for {set i 1} {$i<=$max} {incr i} {
    lappend files "--"
  }
 }

 #---------------
 # update menu
 #---------------
 proc recent:update {mnu fname} {

  # recreate the menu
  destroy $mnu
  menu $mnu -tearoff 0

  # make changes
  # 1) to the file list 
  set recent::files [lshift $recent::files $fname]

  # 2) to the appropriate menu
  set i 1
  foreach n $recent::files {
    $mnu add command -label "$i)  $n" -command "$recent::cmd $n"
    incr i
  }
 }

 #---------------
 # lshift.tcl
 # http://wiki.tcl.tk/15758
 #---------------
 proc lshift {l i} {
  # if the item already exists, just return the list back
  set swap [lsearch $l $i]
  if {$swap!=-1} {
    set tmp [lindex $l $swap]  
    # delete the old location  
    set l [lreplace $l $swap $swap]
    # bring it to the front
    set l [linsert $l 0 $i]    
    return $l
  } 
  # insert new item at the top
  set l [linsert $l 0 $i]
  # delete the one at the end
  return [lrange $l 0 end-1]
 }

 #---------------
 # the ubiquitous demo
 #---------------
 proc demo {} {
 catch {console show}

 menu .menu
 . configure -menu .menu

 menu .menu.file -tearoff 0
 .menu add cascade -menu .menu.file -label File
 .menu.file add command -label New..  -command fileNew
 .menu.file add command -label Open.. -command fileOpen
 .menu.file add cascade -label "Recent Documents" -menu .menu.file.recent
 .menu.file add separator
 .menu.file add command -label "Save As.."  -command fileSave
 .menu.file add command -label Save -command fileSave
 .menu.file add separator
 .menu.file add command -label Exit -command fileExit

  recent:update  .menu.file.recent apple
  recent:update  .menu.file.recent bannana
  recent:update  .menu.file.recent cherry
  recent:update  .menu.file.recent damson
  recent:update  .menu.file.recent elder
  recent:update  .menu.file.recent fig
  recent:update  .menu.file.recent damson      ;# this one should have no effect
  recent:update  .menu.file.recent grapefruit
  recent:update  .menu.file.recent fig         ;# this one, too, should have no effect

 }

 #---------------
 # the ubiquitous demo!
 #---------------
 demo

Category Example