'''[http://www.tcl.tk/man/tcl/TclCmd/lreplace.htm%|%lreplace]''' replaces or deletes elements in a [list], and can also prepend elements to a list. ** Documentation ** [http://www.tcl.tk/man/tcl/TclCmd/lreplace.htm%|%man page]: ** Synopsis ** : '''lreplace''' ''list first last'' ?''element element ...''? ** Description ** '''lreplace''' returns a new list formed by replacing one or more elements of ''list'' with the ''element'' arguments. ''first'' gives the zero-based index in ''list'' of the first element to be replaced. If ''first'' is less than zero then it is interpreted as zero. ''first'' must correspond to the index of an existing element in ''list''. ''Last'' gives the index in ''list'' of the last element to be replaced. If ''last'' is less than ''first'' then no elements are deleted; the new elements are simply inserted before ''first''. ''first'' or ''last'' may be '''`end`''' (or any abbreviation of it), which denotes the last element of the list. Each ''element'' is added to the list in place of those that are deleted. If ''element'' arguments are not specified, then the elements between first and last are simply deleted. ** Examples ** ======none % lreplace {a b c} 0 0 @ @ b c % lreplace {a b c} 1 1 a c % lreplace {a b c} 1 1 hello there a hello there c ====== ** Performance: Modifying a List In-Place ** If `lreplace` is handed an unshared list, it will grab the opportunity to modify the list in place, avoiding the processing overhead of making the copy, and the additional memory usage of the copy. The problem with the following example is that `$l` is shared: ====== set l [lreplace $l $j $j $newvalue] ====== The value of `$l` is bound to `$l` in the caller of `lreplace`, making it shared, so `lreplace` must copy the value before it can modify it. To unshare the value, set `$l` to the empty string after obtaining its value, and before `lreplace` is actually invoked, i.e., sometime later on the command line: ====== set l [lreplace $l[set l {}] $j $j $newvalue] ====== Tcl substitutes the value `$l`, and then appends the empty string to that value, except that it doesn't, because appending the empty string is a no-op. In the meantime, thanks to `[set]`, `$l` is no longer bound to that value, so that value is unshared. When `lreplace` is finally called, it the value is no longer shared. `[unset] l` would also work, but would be slower because of the additional overhead of unsetting a variable. In older versions of Tcl, before appending the empty string was a no-op, [K] was used: ====== set l [lreplace [K $l [set l {}]] $j $j $newvalue] ====== Set also [Shuffle a list] and [lset] The following procedure accomplishes the same thing: ====== proc lipreplace {_list first last args} { upvar $_list list set list [lreplace $list[set list {}] $first $last {*}$args] } ====== ======none % set A [list 1 2 3] 1 2 3 % lipreplace A 1 end c d 1 c d ====== ** Performance; Shifting Subsequent Items ** It's more expensive to replace items toward the front of the list. If `lreplace` causes the size of the list to change, all remaining items in the list must be shifted up or down to accomodate the new size. Therefore, when iterating through a list of lists and deleting items, designing the process so that items toward the end of the list are replaced first can have a big speed benefit. For example, the list could be iterated from back to front: ====== for {set i [expr {[llength $list]-1}]} {$i >= 0} {incr i -1} { set list [lreplace $list[set list {}] $i $i] } ====== ** Prepend, Yes, Append, No ** [AMG]: While `lreplace` can be used to prepend elements to the beginning of a list, it cannot be used to append elements to the end of a list. ====== lreplace {0 1 2} 0 -1 x y z → x y z 0 1 2 lreplace {0 1 2} end -1 x y z → 0 1 x y z 2 lreplace {0 1 2} end+1 -1 x y z → error: list doesn't contain element end+1 ====== The documentation says "if ''last'' is less than ''first'', then any specified elements will be inserted into the list at the point specified by ''first'' with no elements being deleted." Like `[lindex]`, it refers to `[string index]` for an explanation of the index notation and interpretation, so '''end''' is the last element, which is '''2''' in this case. Clearly, the insertion takes place ''just before'' the ''first'' element, even when ''first'' is '''end'''-relative. This is inconsistent with `[linsert]`, which (despite the documentation) inserts ''just after'' the ''index'''th element when ''index'' is '''end'''-relative. It is also inconsistent that `[lreplace]` accepts indexes before the beginning of the list but rejects indexes after the end of the list. `[linsert]` accepts out-of-bounds indexes in either direction. By the way, to prepend and append individual elements or entire lists to a list, several combinations of `[concat]`, `[list]`, and [{*}] can be used. Or if in-place modification is desired, use `[lappend]` with or without [{*}], or use `[lset]` one element at a time. For example, all of the following combinations return `x y 0 1`: ====== set elem1 0; set elem2 1 set list1 {x y}; set list2 {0 1} ====== %| | |% &| Combine lists and elements using '''concat''' and '''list''' | `concat $list1 [[list $elem1]] [[list $elem2]]` |& &| Combine lists using '''concat''' | `concat $list1 $list2` |& %| | |% &| Combine lists and elements using '''list''' and '''{*}''' | `list {*}$list1 $elem1 $elem2` |& &| Combine lists using '''list''' and '''{*}''' | `list {*}$list1 {*}$list2` |& %| | |% &| Append elements to a list using '''lappend''' | `lappend list1 $elem1 $elem2` |& &| Append a list to a list using '''lappend''' and '''{*}''' | `lappend list1 {*}$list2` |& %| | |% &| Append elements to a list using '''lset''' | `lset list1 end+1 $elem1; lset list1 end+1 $elem2` |& %| | |% : [PYK] 2016-04-14: (Note: The documentation has been changed to say "... before the point specified by ''first'' with no elements being deleted."). Another way to describe the behaviour of `linsert` is to say that when it's given an end-relative index, it approaches the list from the end, and from that perspective, inserting a value ''just before the index'th element of list'' means inserting it prior to the first item it encounters, which is last item in the list. If anything, `linsert` is the "inconsistent" one among the list commands, but it's a useful inconsistency. `lreplace` is more oriented towards ensuring that no elements beyond the last index it's given are replaced. That "beyond" idea makes switching orientation like `linsert` does less appealing. In the case of a range like `end -1`, appending to the list wouldn't be very consistent with its behaviour for more common ranges that specify a non-negative width, replacing the last element wouldn't be very consistent either, since the range has no width, and replacing the entire list is unlikely to be what was desired, so it does what seems most sensible. ** Bounds Checking ** [escargo] 2005-09-15: Perhaps there is a better place for the following discussion, but this is where I saw a specific feature mentioned that I want to raise an issue about. '''List commands bounds checking and associated behavior''' The description above states (in part): "If ''first'' is less than zero then it refers to the first element of ''list''." I see two problems with this: * What should the behavior be when indexing outside the bounds of a list? * How does this behavior vary from other list operations? Personally, I see allowing negative indexes (''first'' or ''last'') to be errors, and ones that are ''silent failures'' at that. (This is unlike [Icon Programming Language%|%Icon] where at least some negative indexes have semantically valid meaning.) Perhaps list operations that do indexing should have a '''-strict''' option so that if indexes are outside the proper ranges, errors would be thrown. On the second issue, [lindex] allows negative indexes (and indexes greater than the length of the list), but returns no values. The negative index is not coerced to zero. Thus the same index value (a negative one) refers to no element with [lindex] but the zeroth element with lreplace. Perhaps an item for the [Tcl 9.0 WishList] would be consistent behavior for [list] and [string] indexing semantics. ** Misc ** If the ''last'' argument of lreplace was made optional, one could essentially just alias ldelete to lreplace [CmCc] [AMG]: I assume your suggestion is that omitting the ''last'' argument (plus all the ''element'' arguments to follow, of course) be taken to mean that the element at index ''first'' be removed from the output list. This seems reasonable to me. My kneejerk complaint is that the name `lreplace` doesn't seem suitable for this usage, but this complaint already applies to the current case of omitting all ''element'' arguments, so no new naming problem is introduced. ** See Also** [linsert]: [lappend]: [list]: <> Command