How to "zip" lists in tcl - list

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

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 remove a list from list in tcl

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

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]

Get items in specific index list of lists

I have a key-value list such as:
set x {{a 1} {b 2} {c 3}}
I need to extract all the items in index=1 in all sub-lists to get:
{1 2 3}
You can use this:
$ set y {}
$ foreach sublist $x { lappend y [lindex $sublist 1]}
$ puts $y
1 2 3
A solution for TCL 8.6 or newer:
Use lmap to iterate through x without saving value anywhere ,in one-line:
$ lmap sublist $x {lindex $sublist 1}
References:
lmap,tcl.tk
I've used the following function:
proc MapList {Var List Script} {
if {![llength $List]} {return $List}
upvar 1 $Var Item
foreach Item $List {lappend Res [uplevel 1 $Script]}
return $Res
}
And used it like this:
MapList Arg $x {lindex $Arg 1}
One solution is to conscript the dict values command:
dict values [concat {*}{{a 1} {b 2} {c 3}}]
How this works: the dict values collects a list consisting of every other item (starting from the second) in another list. This is intended to be used on dictionaries, but since dictionaries are basically just even-sized lists, it works on any even-sized list, with one caveat: if any key appears more than once, the result of dict values will only contain the last value associated with that key.
A list consisting of two-item sublists can easily be transformed into an even-sized list by passing the sublists individually as arguments to concat.
Another way is to traverse the list using one of the methods mentioned in the other answers, or maybe like this:
set res {}
for {set i 0} {$i < [llength $x]} {incr i} {
lappend res [lindex $x $i 1]
}
set res
This is similar to
set res {}
foreach item $x {
lappend res [lindex $item 1]
}
set res
(or the corresponding lmap item $x {lindex $item 1})
but does provide the option to 1) start at an index ≠ 0, 2) end before the end of the list, and 3) traverse the list by two (or more) item steps.
Documentation: concat, dict, for, foreach, incr, lappend, lindex, llength, lmap, set

TCL remove an element from a list

How te remove an element from TCL list say:
which has index = 4
which has value = "aa"
I have Googled and have not found any built-in function yet.
set mylist {a b c}
puts $mylist
a b c
Remove by index
set mylist [lreplace $mylist 2 2]
puts $mylist
a b
Remove by value
set idx [lsearch $mylist "b"]
set mylist [lreplace $mylist $idx $idx]
puts $mylist
a
The other way to remove an element is to filter it out. This Tcl 8.5 technique differs from the lsearch&lreplace method mentioned elsewhere in that it removes all of a given element from the list.
set stripped [lsearch -inline -all -not -exact $inputList $elemToRemove]
What it doesn't do is search through nested lists. That's a consequence of Tcl not putting effort into understanding your data structures too deeply. (You can tell it to search by comparing specific elements of the sublists though, via the -index option.)
Lets say you want to replace element "b":
% set L {a b c d}
a b c d
You replace the first element 1 and last element 1 by nothing:
% lreplace $L 1 1
a c d
regsub may also be suitable to remove a value from a list.
set mylist {a b c}
puts $mylist
a b c
regsub b $mylist "" mylist
puts $mylist
a c
llength $mylist
2
Just wrapped up what others have done
proc _lremove {listName val {byval false}} {
upvar $listName list
if {$byval} {
set list [lsearch -all -inline -not $list $val]
} else {
set list [lreplace $list $val $val]
}
return $list
}
Then call with
Inline edit, list lappend
set output [list 1 2 3 20]
_lremove output 0
echo $output
>> 2 3 20
Set output like lreplace/lsearch
set output [list 1 2 3 20]
echo [_lremove output 0]
>> 2 3 20
Remove by value
set output [list 1 2 3 20]
echo [_lremove output 3 true]
>> 1 2 20
Remove by value with wildcar
set output [list 1 2 3 20]
echo [_lremove output "2*" true]
>> 1 3
You can also try like this :
set i 0
set myl [list a b c d e f]
foreach el $myl {
if {$el in {a b e f}} {
set myl [lreplace $myl $i $i]
} else {
incr i
}
}
set myl
There are 2 easy ways.
# index
set mylist "a c b"
set mylist [lreplace $mylist 2 2]
puts $mylist
a b
# value
set idx [lsearch $mylist "b"]
set mylist [lreplace $mylist $idx $idx]
puts $mylist
a