Loop through all combinations of arrays (variable size) - tcl

In tcl I need to execute a script for each possible combination of values of an unknown number of variables.
Describing it in words:
A goes from a0 -> a1 with steps of "da"
B goes from b0 -> b1 with steps of "db"
C goes from c0 -> c1 with steps of "dc"
....
The number of variables can vary. Note: The names of the variables are not known beforehand, 'A' could also be called 'Ape' or anything else. Same goes for the other variables.
What I have so far is:
array set min_vals {A $a0 B $b0 C $c0 ...} ;# --> These are user-defined
array set max_vals {A $a1 B $b1 C $c1 ...} ;# --> These are user-defined
array set step_vals {A $da B $db C $dc ...} ;# --> These are user-defined
# First I determine the number of variables and the number of values they can have
set nr_vars [array size min_vals] ;# Determine nr of variables
set nr_vals [list] ;# --> Set empty list for nr of values for each variable
foreach var_name [array names min_vals] {
set nr [expr {round( ( $max_vals(${var_name})-$min_vals(${var_name}) ) / $step_vals(${var_names}) )}]
set nr_vals [concat $nr_vals $nr]
}
Now I need to somehow loop through each possible combination:
[A=a0, B=b0, C=c0]
[A=a0+da, B=b0, C=c0]
[A=a0+2*da, B=b0, C=c0]
...
...
[A=a1, B=b0, C=c0]
[A=a0, B=b0+db, C=c0]
[A=a0+da, B=b0+db, C=c0]
...
...
[A=a1, B=b1, C=c1]
I hope there is an easy way to do this. The only way I could think of doing this was by having a single loop with number of iterations containing all combinations and let each iteration-number correspond to a specific combination. But I'm sure there must be a less cumbersome way.
_
Edit:
Maybe I wasn't really clear about what I exactly wanted. I don't care about the actual output. My aim is to set each variable to the correct value and run another script with these variables:
set A $a0
set B $b0
set C $c0
source run/some/script.tcl
And repeat this for each possible combination of values of A, B and C.

Use nested for loops
for {set a $min_vals(A)} {$a <= $max_vals(A)} {incr a $step_vals(A)} {
for {set b $min_vals(B)} {$b <= $max_vals(B)} {incr b $step_vals(B)} {
for {set c $min_vals(C)} {$c <= $max_vals(C)} {incr c $step_vals(C)} {
do something with [list $a $b $c]
}
}
}
Ah, needs to be more dynamic. Hmmm,
set variables {A B C}
array set min_vals {A 1 B 10 C 100}
array set max_vals {A 3 B 30 C 300}
array set step_vals {A 1 B 10 C 100}
proc build_loops {} {
global variables
# create the "seed" code: what to do with the generated tuple
set code "do_something_with \[list "
foreach var $variables {
append code "\$[loop_var $var] "
}
append code "]"
# and wrap layers of for loops around the seed
foreach var [lreverse $variables] {
set loop_var [loop_var $var]
set code [format {for {set %s $min_vals(%s)} {$%s <= $max_vals(%s)} {incr %s $step_vals(%s)} {%s}} \
$loop_var $var \
$loop_var $var \
$loop_var $var \
$code \
]
}
return $code
}
proc loop_var {varname} {
return "loop_[string tolower $varname]"
}
proc do_something_with {args} {
puts $args
}
set code [build_loops]
puts $code
eval $code
for {set loop_a $min_vals(A)} {$loop_a <= $max_vals(A)} {incr loop_a $step_vals(A)} {for {set loop_b $min_vals(B)} {$loop_b <= $max_vals(B)} {incr loop_b $step_vals(B)} {for {set loop_c $min_vals(C)} {$loop_c <= $max_vals(C)} {incr loop_c $step_vals(C)} {do_something_with [list $loop_a $loop_b $loop_c ]}}}
{1 10 100}
{1 10 200}
{1 10 300}
{1 20 100}
{1 20 200}
{1 20 300}
{1 30 100}
{1 30 200}
{1 30 300}
{2 10 100}
{2 10 200}
{2 10 300}
{2 20 100}
{2 20 200}
{2 20 300}
{2 30 100}
{2 30 200}
{2 30 300}
{3 10 100}
{3 10 200}
{3 10 300}
{3 20 100}
{3 20 200}
{3 20 300}
{3 30 100}
{3 30 200}
{3 30 300}
I keep a separate list of the variable names: [array names a] returns an unordered list of names, and (I assume) it is important to know the order of the tuple given to the do_something_with proc

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

Finding "lsort" indices in .tcl

How can we obtain indices of lsort?
For example:
lsort -real {1 -4 6 0}
how can I obtain indices for the code above as idx = (1, 3, 0, 2)?
The -indices option to lsort does exactly what you want:
set values {1 -4 6 0}
set indices [lsort -indices -real $values]
foreach idx $indices {
puts "[lindex $values $idx] is at $idx"
}
Output:
-4 is at 1
0 is at 3
1 is at 0
6 is at 2

Robust way to pick value from X-Y table in TCL

I would like to know the most robust method for extracting Y Value for a given X Value from column of X-Y data.
I am currently performing this operation with the following code, but is very unreliable/flakey as it keeps falling over with error of can't read or no variable var_01
Please advice.
Iterate based on Column Z
for {set i 0} {$i < [llength $Col_z]} {incr i} {
set Xdata [lindex $Col_x $i]
set Ydata [lindex $Col_y $i]
lappend var $Ydata
if { $Xdata >= 0.9 && $Xdata <= 1.1 } {
set a [lindex $var $i]
lappend var_01 $a
} else {lappend var_01 0
#set var_01 0}
}
It's very hard to work out what you want to do, but maybe it helps to simplify the code a bit:
foreach z $Col_z x $Col_x y $Col_y {
if {$z eq {}} {
break
}
if {$x >= 0.9 && $x <= 1.1} {
lappend var_01 $y
} else {
lappend var_01 0
}
}
Edit according to comment: is this better?
set var_01 {}
foreach z $Col_z x $Col_x y $Col_y {
if {$z eq {}} {
break
}
if {$x >= 0.9 && $x <= 1.1} {
lappend var_01 $y
}
}
Note that var_01 might be empty if no value of x is within the range.
Documentation:
&& (operator),
<= (operator),
>= (operator),
break,
eq (operator),
foreach,
if,
lappend,
set
A very convenient way to represent tables in tcl is by simple array. Here is an example:
array set xy {}
foreach i {1 2 3} {
foreach j {10 20 30} {
set xy($i,$j) [expr $i + $j]
}
}
Now xy is an array whose keys look like table indexes. Here:
% array names xy
3,10 2,20 1,30 3,20 2,30 3,30 1,10 2,10 1,20
Or more clear:
% foreach k [array names xy] {puts $k}
3,10
2,20
1,30
3,20
2,30
3,30
1,10
2,10
1,20
Here is how to access them:
% puts $xy(3,10)
13
The 3,10 inside the parenthesis is a string! The array returns the value associated with that string, which was associated in the above loop. (Therefore there must not be space after the comma).
It's easy to access the values if the indexes are given in variables:
% set x 3
3
% set y 10
10
% puts $x,$y
3,10
The last command is equivalent to explicit quotation marks:
% puts "$x,$y"
3,10
And here is how we access the array element at that key:
% puts $xy($x,$y)
13
And if the key doesn't exist:
% puts $xy(4,10)
can't read "xy(4,10)": no such element in array
Let's conclude with printing the keys and values of the array:
% foreach k [array names xy] {puts "$k: $xy($k)"}
3,10: 13
2,20: 22
1,30: 31
3,20: 23
2,30: 32
3,30: 33
1,10: 11
2,10: 12
1,20: 21
ADDED
Now suppose you have the y and z values, how do you find the x?
set y 20
set z 23
Using the special, powerful tcl property of everything is a string:
Here we find all keys and values matching the key pattern *,20:
set results [array get xy *,$y]
Let's see:
puts $results
% 2,20 22 3,20 23 1,20 21
We got a list of 3 pairs, each contains the key and value.
Now let's extract the key/value that corresponds to outr $z. We will use the powerful regexp tcl command, seeing $results now as a string instead of a list:
regexp "(\\d+),($y) ($z)" $results whole x1 y1 z1
And now x1, y1, z1 hold all the information we want:
puts "$x1 $y1 $z1"
% 3 20 23

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

Print Cartesian product of two list, without nested foreach

I need to calculate a Cartesian product of two lists.
My list contains large number of elements, so nested foreach is not a good idea in my case.
Anything else, that can be used there?
You might be able to work on the values while it's being put together. It's hard to say without at least a snapshot of the structure you're working with. Here's a simple example.
The proc doesn't return a matrix it does work on points in the matrix.
proc my_cartesian {a b} {
set len_a [llength $a]
set len_b [llength $b]
set len [expr $len_a * $len_b]
set y 0
for {set i 0} {$i < $len} {incr i} {
set x [expr $i % $len_a]
if {$x == 0 && $i != 0} {
incr y
}
set px [lindex $a $x]
set py [lindex $b $y]
# Your code
puts "$px, $py"
}
}
my_cartesian {a b c} {1 2 3}
output:
a, 1
b, 1
c, 1
a, 2
b, 2
c, 2
a, 3
b, 3
c, 3