Version 9 of lset forward compatibility

Updated 2005-09-03 07:12:23

Originated by Tom Wilkason on comp.lang.tcl.

Modifications by DGP.

  if {[package vcompare [package provide Tcl] 8.4] < 0} {
      proc tcl::K {a b} {return $a}
      proc lset {listName index val} {
          upvar $listName list
          set list [lreplace [tcl::K $list [set list {}]] $index $index $val]
      }
  } ;# end if

KPV Does anybody have a version that works with multiple indices?

MG Nov 12 2004 - Does this work?

  proc lset2 {listName args} {
     if { [llength $args] == "0" } {
          error "wrong # args: should be \"lset2 listVar index ?index...? value\""
        }
     upvar $listName list
     if { [llength $args] == "1" || ([llength $args] == "2" && [llength [lindex $args 0]] == "0") } {
          set list [lindex $args [expr {[llength $args]=="1"?0:1}]]
          return $list;
        }
     set val [lindex $args end]
     foreach x [lrange $args 0 end-1] {
             set list [lreplace $list $x $x $val]
            }
     return $list;
   }

LIO I couldn't seem to get the above to work correctly. I wrote a recursive version, the only difference to the actual lset (that I can think of) is it will not take the indeces as a single list (although it would be easy to add that in).

  if {[package vcompare [package provide Tcl] 8.4] < 0} {
      proc tcl::K {a b} {return $a}
      proc lset_r {list args val} {
          if { [llength $args] == "0" } {
              return $val
          } else {
              return [lreplace $list [lindex $args 0] [lindex $args 0] [lset_r [lindex $list [lindex $args 0]] [lrange $args 1 end] $val]]
          }
      }
      proc lset {listName args} {
          if { [llength $args] == "0" } {
              error "wrong # args: should be \"lset varName ?index...? newValue\""
          }
          upvar $listName list
          set list [lset_r [tcl::K $list [set list {}]] [lrange $args 0 end-1] [lindex $args end]]
      }
  }

TP 2005-01-12 I wrote this version a while back to use in Jacl. It probably could be optimized with the K tricks. Nor am I sure that it's fully compatible with the current 8.4 native lset.

 proc lset { varName args } {
    upvar 1 $varName list
    switch [llength $args] {
        0 {
            error \
            "wrong # args: should be \"lset listVar index ?index...? value\""
        }
        1 {
            set list [lindex $args 0]
            return $list
        }
        2 {
            set index [lindex $args 0]
            if {[llength $index] > 1} {
                set value [lindex $args end]
                set list [eval lset list $index [list $value]]
                return $list
            } else {
                if {[regexp end $index]} {
                    set index \
                        [expr ([llength $list]-1) [string range $index 3 end]]
                }
                if {$index < 0 || $index >= [llength $list]} {
                    error "list index out of range"
                }
                set value [lindex $args end]
                set list [lreplace $list $index $index $value]
                return $list
            }
        }
        default {
            set index [lindex $args 0]
            if {[regexp end $index]} {
                set index [expr ([llength $list]-1) [string range $index 3 end]]
            }
            set rest  [lrange $args 1 end-1]
            set value [lindex $args end]
            set sublist [lindex $list $index]
            set first [lrange $list 0 [expr {$index - 1}]]
            set last [lrange $list [expr {$index + 1}] end]

            set list $first
            lappend list [eval lset sublist $rest [list $value]]
            foreach l $last {
                lappend list $l
            }
            return $list
        }
    }
 }

RS 2005-02-20 hacked up this recursive multiple-index version for the iPaq (Keuchel's port was 8.4a2, no lset yet):

 proc lset {_list args} {
    upvar 1 $_list list
    set indices [lrange $args 0 end-1]
    if {[llength $indices]==1} {set indices [lindex $indices 0]} ;# list case
    set list [lset0 $list $indices [lindex $args end]]
 }
 proc lset0 {list indices val} {
    if {[llength $indices]==0} {return $val}
    set p     [lindex $indices 0]
    set list2 [lindex $list $p]
    set ind2  [lrange $indices 1 end]
    lreplace $list $p $p [lset0 $list2 $ind2 $val]
 }

#-- Test with indices in a list, or in pieces:

 % set x {{a b} {c d}}
 {a b} {c d}
 % lset x 0 1 e
 {a e} {c d}
 % lset x {0 1} f
 {a f} {c d}

KG 2005-Sep-03 wrote this recursive multiple-index version to help backport sugar to 8.3. (was too eager to solve the problem to remember to check here for an existing solution first!):

proc lset { varName args } {

    upvar 1 $varName theList

    set theValue  [lindex $args end]
    switch -exact [llength $args] {
        0 {
            # lset v (do nothing)
        }

        1 {
            # lset v x (copy x to v)
            set theList $theValue
        }

        2 {
            # lset v i x        (set the i'th element of v to x)
            # lset v {} x       (set v to x)
            # lset v {i j k} x  (set the k'th element of the j'th element of the i'th element of v to x)
            set indexList [lindex  $args 0]
            set index     [lindex  $indexList 0]
            set theLength [llength $theList]
            switch -exact [llength $indexList] {
                0 {
                    # lset v {} x   (set v to x)
                    set theList $theValue
                }

                1 {
                    # lset v i x    (set the i'th element of v to x)
                    if { [string is integer -strict $index] && ($index >= $theLength) } {
                        error "list index out of range: $index >= $theLength"
                    }
                    set theList [lreplace $theList $index $index $theValue]
                }

                default {
                    # lset v {i j k} x  (set the k'th element of the j'th element of the i'th element of v to x)
                    set subList [lindex $theList $index]
                    set subList [lset subList [lrange $indexList 1 end] $theValue]
                    set theList [lreplace $theList $index $index $subList]
                }
            }
        }

        default {
            # lset v i j k x    (set the k'th element of the j'th element of the i'th element of v to x)
            set indexList [lrange $args 0 end-1]
            set theList   [lset theList $indexList $theValue]
        }
    }

    return $theList

} #-- Passes all the test cases in the 8.4 help page for lset