How to remove the identical elements between 2 lists? - tcl

I have 2 lists with some identical elements, and I want to remove these elements between these 2 lists; Example would be like this:
set list_1 [list a b c d]
set list_2 [list e f b a]
puts $list_1_remove_identical ; #Output that I want
c d
puts $list_2_remove_identical ;
e f

You can use a dict to store the elements of one list for fast lookup to see if they're present in the other list, and only keep the ones that aren't:
#!/usr/bin/env tclsh
proc list_difference {a b} {
set in_b [dict create]
foreach elem $b {
dict set in_b $elem 1
}
lmap elem $a { if {[dict exists $in_b $elem]} { continue } else { set elem } }
}
set list_1 [list a b c d]
set list_2 [list e f b a]
puts [list_difference $list_1 $list_2] ;# c d
puts [list_difference $list_2 $list_1] ;# e f

As Donal points out correctly, it depends on the nature of the two lists to be compared. For example, if you are looking at sets (no duplicates, ordering not relevant), then you might use tcllib's struct::set:
% package req struct
2.1
% set list_1 [list a b c d]
a b c d
% set list_2 [list e f b a]
e f b a
% struct::set difference $list_1 $list_2
d c
% struct::set difference $list_2 $list_1
e f

You can use the in operator to check for each element if it exists in the other list:
puts [lmap n $list_1 {if {$n in $list_2} continue;set n}]
puts [lmap n $list_2 {if {$n in $list_1} continue;set n}]

Related

Pass few but not all optional arguments to a Tcl procedure

In TCL the way to make a parameter optional is to give it a default value. I don't know if there are any other ways too. e.g
proc my_func {a b c {d 10} {e 11} {f 12}} {
...
}
Now in the above example the parameters a, b and c are compulsory. The parameters d, e and f are optional. Is there another way to create optional parameters?
I am in a situation where I need to create a parameter that can be called from a TCL terminal (in Xilinx Vivado) which has some optional parameters. The user decide to pass a few or all of the optional parameters or none at all. The problem is that, when using positional argument passing, it is impossible to tell TCL which optional parameter we are passing to it. What is the solution to this? e.g
my_func 1 2 3 4 5 6
shall call the my_func with values a=1, b=2, c=3, d=4, e=5 and f=6. Also,
my_func 1 2 3 4
shall call my_func with values a=1, b=2, c=3 and d=4 and the e, f left at their default values. However, I might need to do something like this
my_func 1 2 3 100
where I am passing 100 to f and leave c and d at default value. But the above stament will set d to 100 instead and leave e and f at their default values.
What is the solution since I can clearly not use the positional argument technique here.
A readable way to design the function is to do it Tk style: use -d 100 options:
proc my_func {a b c args} {
set opts [dict merge {-d 10 -e 11 -f 12} $args]
puts "a = $a"
puts "b = $b"
puts "c = $c"
puts "d = [dict get $opts -d]"
puts "e = [dict get $opts -e]"
puts "f = [dict get $opts -f]"
}
Then when you use them, you can specify them in any order:
% my_func
wrong # args: should be "my_func a b c ?arg ...?"
% my_func 1 2 3
a = 1
b = 2
c = 3
d = 10
e = 11
f = 12
% my_func 1 2 3 -e 100 -d 200
a = 1
b = 2
c = 3
d = 200
e = 100
f = 12
If the final argument in your proc definition is literally args, then the remaining arguments (if any) are collected in a list.
This proc demonstrates how d,e,f can be optional. The optional arguments are included as a {name value} pair.
proc my_func {a b c args} {
set defaults {d 10 e 11 f 12}
foreach {var_name var_value} $defaults {
set $var_name $var_value
}
foreach arg $args {
set [lindex $arg 0] [lindex $arg 1]
}
puts "a:$a b:$b c:$c d:$d e:$e f:$f"
}
tcl8.6.8> my_func 1 2 3
a:1 b:2 c:3 d:10 e:11 f:12
tcl8.6.8> my_func 1 2 3 {d 5} {e 8} {f 99}
a:1 b:2 c:3 d:5 e:8 f:99
tcl8.6.8> my_func 1 2 3 {f 99}
a:1 b:2 c:3 d:10 e:11 f:99
The below is a minor variation to the solutions already suggested. By using dict with, on can unpack the dictionary content into the proc-local scope as variables:
proc my_func {a b c args} {
set () [dict merge {(d) 10 (e) 11 (f) 12} $args]
dict with () {}
puts "a = $a"
puts "b = $b"
puts "c = $c"
puts "d = $(d)"
puts "e = $(e)"
puts "f = $(f)"
}
Some remarks:
To avoid collisions with other (existing?) proc-local variables, the optional parameters are denoted as elements of an array named using the empty string: ().
dict with will unpack the so-named keys into that array: (e), (f), ...
The processed optionals can be accessed via $ syntax: $(e), $(f), ...
Watch:
my_func 1 2 3
my_func 1 2 3 (e) 100 (d) 200
Yields:
a = 1
b = 2
c = 3
d = 10
e = 11
f = 12
a = 1
b = 2
c = 3
d = 200
e = 100
f = 12

Accessing list-of-lists returned from tcl procedure

I have a procedure that returns a list of lists. I can't figure out how to access the inner lists without splitting them first. I'm sure there must be a cleaner way.
For example, this:
proc return_l_of_l {} {
set x {a b c}
set y {d e f}
return [list [list $x] [list $y]]
}
set l [return_l_of_l]
set x_list [lindex $l 0]
set y_list [lindex $l 1]
foreach x $x_list { puts $x }
foreach y $y_list { puts $y }
outputs:
a b c
d e f
not:
a
b
c
d
e
f
The problem is this line:
return [list [list $x] [list $y]]
Since x and y already hold lists, it makes a list of lists of lists. You should instead do:
return [list $x $y]
or possibly:
return [list [list {*}$x] [list {*}$y]]
proc return_l_of_l {} {
set x {a b c}
set y {d e f}
return [list [list $x] [list $y]]
}
returns a list of two lists each consisting of a single element — a b c and d e f. That's because {a b c} and {d e f} are already constructs which can be interpreted as lists.
Supposedly you'd just need
return [list $x $y]

creating tcl list of first 2 column of matrix

I have matrix of 10 rows and 10 column. I want to crate tcl list where each element of list will be 2 numbers..1 from each row.
e.g if My matrix is
$a $b $c $d $e
$f $g $h $i $j
$k $l $m $n $o
$p $q $r $s $t
I want to have list that contain elements $a $b, $f $g, $k $l, $p $q.
Can someone tell me how to do this ?
Assuming your matrix is a list of lists, you can use the lmap command:
$ tclsh
% set matrix {
{a b c d e}
{f g h i j}
{k l m n o}
{p q r s t}
}
{a b c d e}
{f g h i j}
{k l m n o}
{p q r s t}
% lmap sublist $matrix {lrange $sublist 0 1}
{a b} {f g} {k l} {p q}
If you are using a matrix as defined by the struct::matrix package in Tcllib, you do this:
set pairlist [$matrix get rect 0 0 1 end]
Notes: the name of the matrix is in the matrix variable, rect is short for “rectangle”, the 0 0 give the coordinates in the matrix of the top-left corner of the rectangle to extract, and 1 end gives the coordinates in the matrix of the bottom right corner of the rectangle (matrices support end to mean the last row and/or column, just like Tcl strings and lists).
package require struct::matrix
struct::matrix data
data add columns 5
data add rows 4
data set rect 0 0 {
{a b c d e}
{f g h i j}
{k l m n o}
{p q r s t}
}
data get rect 0 0 1 end
# {a b} {f g} {k l} {p q}
This should produce the results

Count number of unique element in a list

Say I have a list, a b c b b d e e f …, and I don't know how many different kind of elements are in there.
How do I count the number of each unique element and print them out?
Output would looks like:
a: 32
b: 12
c: 6
…
You have to count them up. This isn't too hard with an array or dictionary of counters. I'll use a dictionary since then they'll be printed in order of first occurrence. (With an array, you'd get a “random” order or you'd have to sort them.)
set counters {}
foreach item $list {
dict incr counters $item
}
dict for {item count} $counters {
puts "${item}: $count"
}
Try this if you have 8.4 or older version of TCL,
set lst "a a a a b b b c c c d d a a a f f f f f s s s s"
set unique [lsort -unique $lst]
foreach f $unique {
set cnt 0
foreach item $lst {
if {$item == $f} {
incr cnt
}
}
puts "$f :: $cnt"
}
Gives Output Like,
% tclsh main.tcl
a :: 7
b :: 3
c :: 3
d :: 2
f :: 5
s :: 4
It can be easily done using lsearch and llength.
Lets say your list is {a c a c s a a c a} then,
set tempList {a c a c s a a c a}
puts "c : [llength [lsearch -all $tempList c]]"
puts "a : [llength [lsearch -all $tempList a]]"
puts "d : [llength [lsearch -all $tempList d]]"
Output :
c : 3
a : 5
d : 0
Explanation : lsearch -all, will return all the index of matching element
and this list of index is returned to llength which will count length of the list.
The dict or array solution is the best one and should be preferred. Another way that works on a sorted list of tokens is to match contiguous regions of non-blank tokens.
% regexp -all -inline {(\S+)(?:\s+\1)*} {a a b b b c d d}
{a a} a {b b b} b c c {d d} d
The result is an even-sized list of alternately matched regions of tokens and the token matched in the region. This can be used to print a frequency report for the tokens in the list in list.
foreach {a b} [regexp -all -inline {(\S+)(?:\s+\1)*} [lsort $list]] {
puts "$b: [llength $a]"
}
Note the limitation that the tokens cannot contain blanks. This can be overcome, but it's simpler to use the array / dict solution which only requires the tokens to be valid list elements.
Documentation: foreach, llength, lsort, puts, Syntax of Tcl regular expressions, regexp

How to pass a dictionary with more arguments into a proc in tcl?

proc test {a b c } {
puts $a
puts $b
puts $c
}
set test_dict [dict create a 2 b 3 c 4 d 5]
Now I want to pass dict into test like this:
test $test_dict
How to make test only selects three elements in the dict with the same name of its parameters (the keys). The expected output should be:
2
3
4
Because it selects a b c in the dictionary but not d. How can I do this? I saw some code does like this but I can't make it work.
I think you should use dict get:
proc test {test_dic} {
puts [dict get $test_dic a]
puts [dict get $test_dic b]
puts [dict get $test_dic c]
}
set test_dict [dict create a 2 b 3 c 4 d 5]
test $test_dict
Edit:
Another variant would be to use dict with:
proc test {test_dic} {
dict with test_dic {
puts $a
puts $b
puts $c
}
}
set test_dict [dict create a 2 b 3 c 4 d 5]
test $test_dict
But test gets still a list.