lremove

Difference between version 22 and 24 - Previous - Next
---
[JMN] 2024
With the implementation of [http://tip.tcl.tk/367.html%|%TIP #367] which is mentioned at the bottom of this page; for Tcl 8.7+ compatibility it is probably better to use a different name for the 'lremove by value' concept discussed on this page.

lremove for Tip 367 removes elements by supplied indices - which may be in any order.

As a first draft of a pure Tcl equivalent for Tcl 8.6 I offer the following:

         proc ::lremove {list args} {
            set data [lmap v $list {list data $v}]
            foreach doomed_index $args {
                if {[llength $doomed_index] != 1} {error "bad index \"$doomed_index\": must be integer?\[+-]integer? or end?\[+-]integer?"}
                lset data $doomed_index x ;#x won't collide as all our data has been mapped to 2 elements per value
            }
            set keep [lsearch -all -inline -not -exact $data x]
            return [lmap v $keep {lindex $v 1}]
        }

There may be better ways to do it, and this hasn't been reviewed or thoroughly tested.




---

How to remove items from a list not by their position ([lreplace]) but by the actual content. 
My solution would be:

======
 proc K { x y } { set x }
 proc lremove { listvar string } {
         upvar $listvar in
         foreach item [K $in [set in [list]]] {
                 if {[string equal $item $string]} { continue }
                 lappend in $item
         }
 }
======

which gives us

 % set a [list a b c a b c a b c]
 a b c a b c a b c
 % lremove a b
 % set a
 a c a c a c

Is there any reason not to do it that way?

Gotisch
----
[RS] proposes this built-in way, waving many flags :)
======
 % set a [lsearch -all -inline -not -exact $a b]
 a c a c a c
======

[wdb] Simply ingenious!

----

[@JaviGarate] Awesome!

----
Todd A. Jacobs suggests this slightly more flexible version:

======
 # lremove ?-all? list pattern
 #
 # Removes matching elements from a list, and returns a new list.
 proc lremove {args} {
     if {[llength $args] < 2} {
        puts stderr {Wrong # args: should be "lremove ?-all? list pattern"}
     }
     set list [lindex $args end-1]
     set elements [lindex $args end]
     if [string match -all [lindex $args 0]] {
        foreach element $elements {
            set list [lsearch -all -inline -not -exact $list $element]
        }
     } else {
        # Using lreplace to truncate the list saves having to calculate
        # ranges or offsets from the indexed element. The trimming is
        # necessary in cases where the first or last element is the
        # indexed element.
        foreach element $elements {
            set idx [lsearch $list $element]
            set list [string trim \
                "[lreplace $list $idx end] [lreplace $list 0 $idx]"]
        }
     }
     return $list
 }

 # Test cases.
 set foo {1 2 3 4 5 6 7 8 9 10}
 puts "Foo: [list $foo]"
 puts "Foo: replace one: [lremove $foo $argv]"
 puts "Foo: replace all: [lremove -all $foo $argv]"

 puts {}

 set bar {1 2 3 4 1 6 7 1 9 10}
 puts "Bar: [list $bar]"
 puts "Bar: replace one: [lremove $bar $argv]"
 puts "Bar: replace all: [lremove -all $bar $argv]"
======

[dzach] ...but then, when I do :
======
 set bar {1 2 3 {} 4 1 6 7 1 9 10 {}}
 lremove -all $bar {}
======
I still get:
 1 2 3 {} 4 1 6 7 1 9 10 {}

[jmn]  this is because the proc expects a *list* of patterns as the 2nd argument.
Try instead:
======
 lremove -all $bar [list {}] 
======


I don't see why the complicated 'lreplace' line with the 'string trim' is required.
As far as I can see - it could be replaced with:
======
 set list [lreplace $list $idx $idx]
======


When removing a single value (1st encountered match) from a list - I use:
======
 set posn [lsearch -exact $list $val]
 set list [lreplace $list $posn $posn]
======

This is slightly different in functionality from RS's beautiful one liner above, which removes all instances.
If you know you only have one instance of the value to be removed - this 2 liner may be slightly faster on large lists (tested on Tcl8.6b1).. but on a 2007 vintage machine we're talking maybe a couple of hundred microseconds for lists even of size approx 10K, so it's not likely to be an issue except for some pretty intensive inner loops with large lists.

[milarepa] How about this:

======
proc lremove {list match} {
    set idx_list [lsearch -all $list $match]
    foreach idx [lreverse $idx_list] {
        set list [lreplace $list $idx $idx]
    }
    return $list
}
======
[gold] Multiple entries, using Suchenworth's idea:

======
             # written on Windows XP on eTCL
             # working under TCL version 8.5.6 and eTCL 1.0.1
             # gold on TCL WIKI , 3jul2013
        package require Tk
        proc cleaner {target args} {
            set res $target
            foreach unwant $args {
              # suchenworth idea
              set res [lsearch -all -inline -not -exact $res $unwant ]
            }
            return $res
        }
        console show
        set a_target_list [list a b c a b c a b c 1234 1234 5678]
        puts " [cleaner {a b c a b c a b c} b c]"
        puts " [cleaner $a_target_list 1234 5678]"

 Output:
 a a a
 a b c a b c a b c
======

Another `lremove` method, by http://wiki.tcl.tk/55526%|%Cyril %|% :

======
proc lremove {l p} {
    set p [lsearch -exact $l $p]
    return "[lrange $l 0 $p-1] [lrange $l $p+1 end]"
}
======

Example : 

======
set l {a b c d e}
puts [lremove $l c]
# => a b d e
======

2018-09-13: note that `[concat [lrange $l 0 $p-1] [lrange $l $p+1 end]]` is a better way to concatenate the lists.

----
See also
   * [list]
   * [http://tip.tcl.tk/367.html%|%TIP #367]

<<categories>> Command