Version 12 of lpop

Updated 2009-05-23 09:29:01 by nem

lpop - remove last element of list variable

lpop listVar

This is not a command included in Tcl, but rather a common idiom for in place list manipulation used e.g. for implementing stacks.

lpop removes the last element from the list in listVar and returns it. listVar is altered in place.

See also: lpush alias lappend as the "reverse" operation of lpop, lprepend, lshift as the counterpart operating on the other end of the list, as well as Chart of existing list functionality.


Different Interpretations

In ExtraL, the command accepts an optional position parameter:

lpop listVar ?pos?

If pos is given, the element at position pos of the list is 'popped' off the list instead of the last element.

LEG would prefer to interpret the optional argument not as a position, but rather as the count of elements to pop of the tail of the list.

lpop listVar ?count??

This would harmonize better with the semantics of lappend, alias lpush, which append a number of elements to a list. Maybe lpluck list ?pos?, would be a better name for the ExtraL functionality, where position would be specified just like in lindex.

In Bag of algorithms, The anatomy of a bytecoded command and Searching A Star In Space, amongst others, lpop pops off the first element of the list.

This is kind of pushmi-pullyu : On one side a convenient implementation of the stack push command s lappend listVar element. Here the last element of the list (the 'right-most') has to be seen as the top of the stack, which might appear counterintuitive to some people. Those would, on the other side rather push elements on the stack 'to the left', and pop them from there (index 0 of the list).

A more convenient name for this second interpretation of lpop might be lshift. Since the Tcl-core does not provide an opposite of the lappend command, implementing a stack where the top element is at list position 0 (or 'left-most') has poorer performance.

Implementations

AMG: A stack whose top is at list position zero can be implemented as follows:

proc push {listvar elem} {
   upvar 1 $listvar list
   set list [concat [list $elem] $list]
}

proc pop {listvar elemvar} {
   upvar 1 $listvar list $elemvar elem
   set list [lassign $list elem]
}

NEM See reference below for why this will be inefficient. Also, use linsert rather than concat for push.

This one, copied from Performance of Various Stack Implementations, contributed by Lars H, is simple and best in performance.

proc lpop listVar {
        upvar 1 $listVar l
        set r [lindex $l end]
        set l [lreplace $l [set l end] end] ; # Make sure [lreplace] operates on unshared object
        return $r
}

Note: the 'set l end' idiom is a trick to "unset" l and at the same time provide the verb 'end' to lreplace resulting in 'lreplace $l end end'.

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

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

This one is a similiar implementation with optional element count:

proc lpop {listVar {count 1}} {
    upvar 1 $listVar l
    set r [lrange $l end-[incr count -1] end]
    set l [lreplace $l end-$count [set l end]]
    set r
}

And the following is a full fledged version with error handling:

proc lpop {listVar {count 1}} {
    upvar 1 $listVar l

    if {![info exists l]} {
        error "can't read \"$listVar\": no such variable"
    }
    if {![llength $l]} {error Empty}
    set r [lrange $l end-[incr count -1] end]
    set l [lreplace $l end-$count [set l end]]
    return $r
}

Sarnold I hacked this one today:

proc lpop {list args} {
        set len [llength $args]
        if {$len==0} {return}
        set tail [lrange $list end-[expr {$len-1}] end]
        uplevel 1 [list foreach $args $tail break]
        lrange $list 0 end-$len
}        
set list {1 3 5 7 9 11}
set list [lpop $list a b c]
puts "$a $b $c";# 7 9 11