How to remove a list from list in tcl - list

I have two lists
set a1 {a b c d e f}
set b1 {b f e}
I am trying to do remove_from_list $a1 $b1 >> {a c d}
Is there a function that can operate on lists on tcl?

To begin with, you can't use brackets for list literals. Brackets are used for command substitution in Tcl.
Instead, use the list-making command, list
% set a1 [list a b c d e f]
a b c d e f
Or, more or less equivalently:
% set b1 {b f e}
b f e
There is no standard command to subtract one list from another. It's very easy to construct a filter, though.
You can filter out items in a1 that are in b1 by an lmap (list map) filter:
lmap item $a1 {
if {$item ni $b1} {
set item
} else {
continue
}
}
# results in
a c d
The if command inside the lmap body determines if an item is not in (ni)$b1. If this is true, the value of the item becomes a part of the result. If it is false, the item is skipped.
In Tcl 8.5 and earlier, there is no lmap. In that case, one can copy-and-paste an ersatz lmap (works the same) into the code: link below.
Or, one can use foreach instead. It's a little messier but works.
set res {}
foreach item $a1 {
if {$item ni $b1} {
lappend res $item
}
}
% set res
a c d
Documentation:
continue,
foreach,
if,
lappend,
list,
lmap (for Tcl 8.5),
lmap,
ni (operator),
set

You can also use an array. It's easy to add and remove elements
% foreach elem $a1 {set x($elem) 1}
% foreach elem $b1 {unset x($elem)}
% set result [array names x]
d a c
It's a pretty efficient approach too, only a single pass through each list.
Or use a dictionary to maintain the original insertion order:
% foreach elem $a1 {dict set y $elem 1}
% foreach elem $b1 {dict unset y $elem}
% set result [dict keys $y]
a c d

# with struct::set from tcllib
package require struct::set
set a1 {a b c d e f}
set b1 {b f e}
struct::set difference $a1 $b1
# result in
d a c
Dokumentation:
struct::set

Related

Split a list of numbers into smaller list based on a range in TCL

I have a sorted list of numbers and I am trying to split the list into smaller lists based on range of 50 and find the average in TCL.
For eg: set xlist {1 2 3 4 5 ...50 51 52 ... 100 ... 101 102}
split lists: {1 ... 50} { 51 .. 100} {101 102}
result: sum(1:50)/50; sum(51:100)/50; sum(101:102)/2
The lrange command is the core of what you need here. Combined with a for loop, that'll give you the splitting that you're after.
proc splitByCount {list count} {
set result {}
for {set i 0} {$i < [llength $list]} {incr i $count} {
lappend result [lrange $list $i [expr {$i + $count - 1}]]
}
return $result
}
Testing that interactively (with a smaller input dataset) looks good to me:
% splitByCount {a b c d e f g h i j k l} 5
{a b c d e} {f g h i j} {k l}
The rest of what you want is a trivial application of lmap and tcl::mathop::+ (the command form of the + expression operator).
set sums [lmap sublist [splitByCount $inputList 50] {
expr {[tcl::mathop::+ {*}$sublist] / double([llength $sublist])}
}]
We can make that slightly neater by defining a custom function:
proc tcl::mathfunc::average {list} {expr {
[tcl::mathop::+ 0.0 {*}$list] / [llength $list]
}}
set sums [lmap sublist [splitByCount $inputList 50] {expr {
average($sublist)
}}]
(I've moved the expr command to the previous line in the two cases so that I can pretend that the body of the procedure/lmap is an expression instead of a script.)

How to find the union of two lists in tcl?

I'm familiar with finding the intersection of two lists, however, I wanted to find the union of two lists in tcl (while eliminating duplicates). I do have a working copy of this code, but I'm not sure it is robust enough for any kind/number of lists and am hence looking for a better solution.
Any help or ideas are appreciated.
If you treat lists as sets, so you don't worry about order if the items, you could just sort the joined list:
set union [lsort -unique [list {*}$list1 {*}$list2]]
Tclx provides a union command:
% info patchlevel
8.5.9
% set a [list a b c]
a b c
% set b [list a d e]
a d e
% package require Tclx
8.4
% union $a $b
a b c d e
%
% union
wrong # args: should be "union lista listb"
%
One way that doesn't need sorting is to use dictionary keys as sets:
% set a [list a b c]
a b c
% set b [list a d e]
a d e
% set d {}
% foreach k $a { dict set d $k . }
% foreach k $b { dict set d $k . }
% set c [dict keys $d]
a b c d e
This has the advantage of not needing to sort at all, which can help quite a lot with large input sets.

remove an element from a tcl list of lists by value

I looked at TCL remove an element from a list, and it doesn't seem to work for me. Some code for example:
set mylist [list {a b c} {d e f} {g h i}]
This is what I want to happen:
set idx [lsearch $mylist "a"]; # or if "d", it should take out {d e f} instead. Likewise, if "g" it should take out {g h i}
set mylist [lreplace $mylist $idx $idx]
puts "$mylist"
Output:
{d e f} {g h i}
This is what actually happens:
Output:
{a b c} {d e f} {g h i}
When I puts $idx, it comes out with "-1" no matter what I search. I know it's easy to remove the elements with a firm index, but I need the program to be able to search the elements of a list to remove it. Basically, how do I find the index of the element that I want to remove by only searching for one part of it?
EDIT: Nevermind. I figured out that you need to use * in your search. Since I haven't seen it anywhere else here, I'll leave my original question, and the solution I found:
set label "a"
set idx [lsearch $mylist $label*]
set mylist [lreplace $mylist $idx $idx]
Output:
{d e f} {g h i}
Are you always looking for the search term in the first element of each sublist? If so, you can use lsearch's -index option, which specifies which part of each element is to be examined:
set mylist [list {a b c} {d e f} {g h i}]
set label "a"
set idx [lsearch -index 0 -exact $mylist $label]
set mylist [lreplace $mylist $idx $idx]

How to "zip" lists in tcl

I have three lists :
set l1 {1 2 3}
set l2 {'one' 'two' 'three'}
set l3 {'uno' 'dos' 'tres'}
and I would like to build this list :
{{1 'one' 'uno'} {2 'two' 'dos'} {3 'three' 'tres'}}
In python, I would use something like the built-in function zip. What should I do in tcl ? I have looked in the documentation of 'concat', but
haven't find a priori relevant commands.
If you're not yet on Tcl 8.6 (where you can use lmap) you need this:
set zipped {}
foreach a $l1 b $l2 c $l3 {
lappend zipped [list $a $b $c]
}
That's effectively what lmap does for you, but it was a new feature in 8.6.
lmap a $l1 b $l2 c $l3 {list $a $b $c}
List map, lmap, is a mapping command that takes elements from one or more lists and executes a script. It creates a new list where each element is the result of one execution of the script.
Documentation: list, lmap
This command was added in Tcl 8.6, but can easily be added to earlier versions.
Getting lmap for Tcl 8.5 and earlier
Here's a version that takes an arbitrary number of list names:
set l1 {a b c}
set l2 {d e f}
set l3 {g h i j}
proc zip args {
foreach l $args {
upvar 1 $l $l
lappend vars [incr n]
lappend foreach_args $n [set $l]
}
foreach {*}$foreach_args {
set elem [list]
foreach v $vars {
lappend elem [set $v]
}
lappend result $elem
}
return $result
}
zip l1 l2 l3
{a d g} {b e h} {c f i} {{} {} j}
Requires Tcl 8.5 for the {*} argument expansion.
An 8.6 version
proc zip args {
foreach l $args {
upvar 1 $l $l
lappend vars [incr n]
lappend lmap_args $n [set $l]
}
lmap {*}$lmap_args {lmap v $vars {set $v}}
}

concatinating tcl lists into one list

I have a procedure that returns a single element or a list of elements. I am trying to concatenate all the returned elements into a single list.
set childList [list];
foreach item $myList {
lappend childList $item;
set tempChildList [::myproc $item]; #for different items it will return either single element or a list of elements.
if {[llength $tempChildList] > 0} {
lappend childList $tempChildList;
}
}
So now in my last statement when i lappend $tempChildList into childList it forms a list of lists like below
{a {b c} {d e f} {g {h i}} j}
but i want to concatenate the childList and tempChildList so that my final result will be
{a b c d e f g h i j}
i was thinking of using concat command but the issue is it wont concat the nested lists like {g {j i}} in my above use case.
If you can import the struct::list module, you can do this:
% package require struct::list
1.8.1
% set oldlist {a {b c} {d e f} {g {h i}} j}
% set newlist [::struct::list flatten -full $oldlist]
a b c d e f g h i j
In your situation, I recommend not flattening the list, but rather being more careful during its construction. In particular, flattening has problems where the list contains compound words (which can result in things going horribly wrong when you do a fancy demo, and things like that). By being more careful, knowing what sort of result you're getting from ::myproc (and assuming that's a simple list), you can then produce a simple concatenated list quite easily:
set childList [list]
foreach item $myList {
lappend childList $item {*}[::myproc $item]
}
Note that if you're keen on returning a single item from ::myproc, return it with this:
return [list $theItem]
Though if $theItem is a simple word (e.g., some kind of ID) you can get away without being careful.
Here is something that might work:
proc flatten {lst} {
while 1 {
set newlst [concat {*}$lst]
if {$newlst == $lst} { break }
set lst $newlst
}
return $newlst
}
set lst {a {b c} {{{1 2} 3} 4} {d e f} {g {h i}} j}
puts [flatten $lst]
Output:
a b c 1 2 3 4 d e f g h i j
Discussion
Take a look at the following interactive session:
(1) % set lst {a {b c} {d e f} {g {h i}} j}
a {b c} {d e f} {g {h i}} j
(2) % set newlst [concat {*}$lst]
a b c d e f g {h i} j
Notice that when we set the newlst in step 2, the result is almost what we want. Now, just repeat step 2 until the lst and newlst are equal--that's when we know that we have flatten the list completely.
Try this:
% set list {a {b c} {d e f} {g {h i}} j}
{a {b c} {d e f} {g {h i}} j}
% set newlist [regsub -all "\{|\}" $list ""]
a b c d e f g h i j
Hope this helps.
Instead of giving lappend childList $tempChildList , you can use
set childlist "$childlist $tempChildList"
if {[llength $tempChildList] > 0} {
set childlist "$childlist $tempChildList"
}
Try:
% set str {a {b c} {d e f} {g {h i}} j}
% while {1} {if [regexp -- {\\{} $str] {set str [join $str]} else {break}}
% set str
% a b c d e f g h i j