Version 21 of lset

Updated 2010-02-23 15:03:56 by hv

http://www.purl.org/tcl/home/man/tcl8.5/TclCmd/lset.htm


Kevin Kenny writes on news:comp.lang.tcl.announce :

 Subject: TIP's 22, 33, 45 - extended [list] functionality - now final.
 Date: 18 Nov 2001 11:30:33 -0800
 Message-ID: <[email protected]>

Jeff Hobbs today committed to the CVS HEAD at Sourceforge the changes described in TIP's 22 (Multiple Index Arguments to lindex), 33 (Add 'lset' Command to Assign to List Elements), and 45 (Empty index lists for lindex and lset)

These changes augment the lindex command so that it can extract elements from sublists, for example:

    [lindex {{a b c} {d e f} {g h i}} 1 1] => e

They also implement an lset command that may be used to change individual elements within lists and sublists. Taken together, these commands can be used to treat lists as if they were linear arrays. For instance, the following procedure might be used to reverse the order of elements in a list.

    proc reverse { list } {
        set i 0
        set j [expr { [llength $list] - 1 }]
        while { $j > $i } {
            set temp [lindex $list $i]
            lset list $i [lindex $list $j]
            lset list $j $temp
            incr i
            incr j -1
        }
        return $list
    }

RS on 2005-02-19 offers this variant that uses index arithmetics, so only one index has to be taken care of:

    proc reverse list {
        set i 0
        while { 2*$i < [llength $list] } {
            set  temp        [lindex $list $i]
            lset list $i     [lindex $list end-$i]
            lset list end-$i $temp
            incr i
        }
        return $list
    }

LES on 2005-09-26 offers this variant, with a strange use of string repeat, that still uses lset and is some 15~20 percent faster:

 proc reverse list          {
          set  _temp  [ string repeat {x } [ llength $list ] ]
          set  _nn  [ expr [ llength $list ] - 1 ]
          set  _c  0
          for          {set i $_nn } {$i >= 0} {incr i -1}          {
                  lset _temp $_c [ lindex $list $i ]
                  incr _c
          }
          return  $_temp
 }

But it can be a lot faster if we lose lset altogether:

 proc reverse list          { 
         for          {set i [ expr [ llength $list ] - 1 ] } {$i >= 0} {incr i -1}          {
                 lappend _temp [ lindex $list $i ] 
         }
         return  $_temp
 }

Updated documentation for the commands is available in the 'doc/' subdirectory in the source tree. The original proposals may be found at

    http://www.purl.org/tcl/tip/22.htm 
    http://www.purl.org/tcl/tip/33.htm 
    http://www.purl.org/tcl/tip/45.htm





[Explain why lset is A Good Thing.]

MAKR, 2007-12-14, stumbled over some timing weirdness when comparing lrepeat and lset ... anyway - MS explained in Tcl'ers chat: [...] makr: main (internal) diff is that lset works in-place whenever possible. So does lreplace, but the fact that it operates on a value (and not a variable) makes it so that it is never possible when replacing in a variable's value [...] ie, lset lst 0 a will replace-in-place if $lst is not a shared obj; lreplace $lst 0 0 a ALWAYS gets a shared list as first argument: one ref as the value of the lst variable, another as lrepace's argument [...] so lreplace is forced to make a copy of the list and replace in the copy; when you are doing set lst [lreplace $lst 0 0 a] that is a pure waste ... except if lst happens to have read traces [...] so lset lst 0 a would be (more or less) the functional equivalent of set lst [lreplace [K $lst [set lst {}]]] 0 0 a] or in the best current alternative set lst [lreplace $lst[set lst {}] 0 0 a].


I have been trying the new command 'lset' and it is a nice addition to the core. However, it does not permmit to create new elements in the list. Why not?

Today, there is not a simple method in TCL to convert the list:

  {a a} {b b} {c c}

into:

  {a a} {b b b} {c c}

it would be nice to do:

   lset list 1 2 b

There are many mathematical algorithms that need to fill a list, but not necessarily from the beginning. So, a command that filled position n of the list and filled with "" the elements from 0 to n-1 would be a good adition to the lset command.

For me, the best option is just change the lset especification to permmit to do so. Maybe other people would prefer an additional option:

   lset -create list 1 1 element

I would like to collect here other opinions


KBK (22 October 2002) - The semantics you propose have been controversial. I think the next thing to do is the intermediate step of allowing [lset] to address the element past the end of a list, functioning as [lappend] if the designated element is not there. That at least allows for building up arrays in loops without needing to address sparse arrays. Sparse arrays are something of a can of worms.

In the meantime,

    set sublist [lindex $list 1]
    lset list 1 {}
    lappend sublist b
    lset list 1 $sublist

is about the best I can do for your example.

RS wonders whether

 set sublist [lindex $list 1]
 lset list 1 [lappend sublist b]

is equivalent - especially what the lset list 1 {} above is good for...


ramsan (23 october 2002) - I think that we should take advantage of the fact that [lset] is a new command for changing now its functionality to the best functionality. I see no benefit in going through an intermediate step. Which is the best functionality? For me, the best one is to permmit to create one element and all its predecessors when necessary.

I would appreciate if someone can point out any advantage of the current implementation. Maybe we can give additional feedback to the TCT if we can join here some opinions on the subject.

SS - the advantage of the current semantics is that it is more likely to find bugs causing the script to poke at random indexes in the array. it is not simple to decide if functionality is better than safety, btw I think a good idea is to create a command able to populate a range of the list with empty elements, leaving lset how it is today. Example:

 set foo {}
 lblank foo 0 100
 for {set i 0} {$i < 100} {incr i} {
   lset foo $i $i; # => OK
 }

lblank (very bad name, sorry, just an example) should probably accept even a single argument instead of a range, so that lblank foo 100 is an alias for lblank foo 100 100.

An alternative is a simpler lresize <size>, that just is able to resize the list to <size> elements, adding null elements where needed (it may also cut the list if <size> is < than <current size>).

AMG: In Tcl 8.6, lset is able to extend lists. [L1 ]

set list {a a} {b b} {c c}
lset list 1 2 b ;# returns {a a} {b b b} {c c}

Sarnold I tried to rewrite a binary tree benchmark (found on http://shootout.alioth.debian.org/ ) some times ago and found that nested lists perform better this task than a flat list. Is it related to Tcl internals - I mean, when I use lset, does the interpreter create a new Tcl_Obj structure each time ? In other languages I know, flat arrays are not much slower than structures pointing to others - and that is basically what nested lists are in Tcl, because of data sharing - so why ?


Hai Vu I found that lset has a side effect which squeezes multiple white space characters into one:

% set list "   a       b        c   "
   a       b        c   

% lset list 0 a
a b c