Version 4 of lshift

Updated 2009-03-03 08:35:30 by LEG

lshift - shift list and return first element

lshift listVar

This is not a command included in Tcl, but rather a common 'idiom' for in place list manipulation used e.g. for implementing queues are for commandline processing.

A proper implementation should throw an error when listVar does not exist, and also when listVar contains the empty list (or string).


Implementations

The following is taken from Tcl Gems though slightly modified:

proc lshift listVar {
    upvar 1 $listVar l
    set r [lindex $l 0]
    set l [lreplace $l [set l 0] 0]
    return $r
}

You can use this programming idiom also without having the lshift command, e.g. with the K combinator:

    K [lindex $l 0] [set l [lreplace $l [set l 0] 0]

Or more generalized:

lshift listVar ?count?

Shift list count elements and return the respective elements.

proc lshift {listVar {count 1}} {
    upvar 1 $listVar l
    set r [lrange $l 0 [incr count -1]]
    set l [lreplace $l [set l 0] $count]
    return $r
}

And finally a full fledged one with error handling:

proc lshift {listVar {count 1}} {
    upvar 1 $listVar l
    if {![info exists l]} {
        # make the error message show the real variable name
        error "can't read \"$listVar\": no such variable"
    }
    if {![llength $l]} {error Empty}
    set r [lrange $l 0 [incr count -1]]
    set l [lreplace $l [set l 0] $count]
    return $r
}

Different Interpretations

In lshift -Adding Unique Items to Lists of Fixed Length the command lshift puts an element on a list if it is not already there. It is used to implement a 'recently used' list

Nifty Tricks

In Tcl Gems lshift is used for command line arguments processing. In Stacks and Queues the genial RS names lshift 'qpop' - if push appends to the end, then pop pops from the end (stack) and lshift (qpop) pops from the front (queue).

LEG would like to contribute the following method, to iterate over lines of text, wether stored in a file or in a variable:

proc vGets {listVar lineVar} {
    upvar 1 $listVar l $lineVar line
    if {[catch {lshift l} line]} {return -1} else {string length $line}
}
if {..} {# text in variable "text"
    set lines [split $text \n]
    set getLine vGets
} else {# text in channel fd 
    set lines $fd
    set getLine gets
}
while {[$getLine lines line]!=-1} {
   # do something with the line ..
}