lset

lset , a built-in Tcl command, places a value into a position in a list.

Synopsis

lset varName ?index ...? newValue

Documentation

official reference
source code for official documentation
TIP #22
Multiple index arguments to lindex
TIP #33
Add lset command to assign to list elements
TIP #45
Empty index lists for lindex and lset
re: tcl for beginner? , comp.lang.tcl, 2002-03-11
A reply from kbk explaining some of the rationale, poltics, design, and usage of lset and lindex.
TIP #331
Allow lset to Extend Lists

Description

varName is the name of a variable whose value is a list. The first index argument, if it exists, specifies the cardinal index of an item in the list. Each additional index argument specifies the cardinal index of an item in the list specified by the previous index. It is an error if the item specified by the previous index argument is not a valid list. newValue is an argument whose value will be assigned to the position specified by the indexes.

If there is only one index argument, it will be interpreted as a list of indexes, exactly as if each index was a separate argument to the command.

lset can be used as a variant of set that returns an error if the variable doesn't already exist:

set var1 1
catch {unset var2}
lset var1 2
lset var2 2 ;#-> can't read "myvar": no such variable

Example: 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 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 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
}

Appending to Nested Lists

linsert can be used to append to a list, but for nested lists, lset is the command to use to append to nested lists:

set mylist {{one {1 2 3}} {two {2 3 4}} {three {3 4 5}}}
lset mylist 1 1 end+1 5
puts $mylist

result:

{one {1 2 3}} {two {2 3 4 5}} {three {3 4 5}}

History

Kevin Kenny announced lset on comp.lang.tcl.announce, 2001-11-18.

Discussion


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 [...] i.e., 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 lreplace'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 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 2002-10-22:

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 2002-10-23:

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. See TIP #331

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

AMG: lset produces canonical lists.

LV: Construct the value of $list as a list, and then see how things work. For instance,

% set list [list     a     b     c   ]
a b c
% set list [list {    a} {     b} {   c   }]
{    a} {     b} {   c   }
% lset list 0 a
a {     b} {   c   }

So, when provided a string which has not yet been listified, tcl turns the string into a list, then proceeds with the list operation. You will likely see this type of side effect when passing a string to a list operation.

If this is not the behavior you desire, then making use of list or split may provide less unexpected behavior.

Hai Vu: We can make use of this side effect to squeeze multiple white space characters into one. Also, this serves as a reminder that in Tcl, a string is not 100% the same as a list.

AMG: Be careful! Not all strings are valid lists.


See "lset forward compatibility" if you need lset for older Tcl versions