Let's say I create an array like this:
for {set i 1} {$i <=4} {incr i} {
lappend run "$i [expr $i+1]"
}
puts "$run"
{1 2} {2 3} {3 4} {4 5}
Now I'd like to find the elements containing 2 and get rid of them so I will be left with
run ={3 4} {4 5}
How could I do it?
First of all: an array is an associative structure in Tcl. What you are creating here is a list.
The usual idiom for filtering out elements from a list is to do something like this (pseudocode):
set result [lmap item $list {if <test-to-keep> {set item} else continue}]
or
set result {}
foreach item $list {if <test-to-keep> {lappend result $item}}
(The lmap command is available in Tcl 8.6 or later, though there is a pure-Tcl version usable in Tcl 8.5 that you can copy and paste into your program mentioned in the links below.)
For this example, you can use this solution:
set result [lmap item $run {if {2 ni $item} {set item} else continue}]
That is, look at each item and if 2 does not occur in it, keep it.
The ni operator is in Tcl 8.5 or later: if you have an older Tcl you should upgrade.
Documentation:
continue,
foreach,
if,
lmap (for Tcl 8.5),
lmap,
ni (operator),
set
Related
I have tcl arrays having the names as 0,1,2,.. and the values contain meaningful information.
I want to use a single command like [array names $my_array] to get the values of the array.
Right now I only see this option,(lengthy but gets job done)
for {set index 0} {$index<[array size my_array]} {incr index} {
lappend my_list_values $my_array($index)
}
You can use array get to fetch all the elements of the array, and a foreach loop that consumes multiple elements of its list in each iteration (Plus sorting by key to get a reproducible order):
% set foo(0) a
a
% set foo(1) b
b
% set foo(2) c
c
% foreach {key val} [lsort -integer -stride 2 [array get foo]] { lappend values $val }
% puts $values
a b c
%
Or with array names:
foreach key [lsort -integer [array names foo]] { lappend values $foo($key) }
Here's a proc implementing a foreach functionality for arrays. In Tcl 8.7 this will be builtin: array for
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
puts stderr [list $e]
dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
return -options $e
} finally {
array donesearch $arrayName $sid
}
return
}
You can add this to the array ensemble:
set map [namespace ensemble configure array -map]
dict set map foreach ::array_foreach
namespace ensemble configure array -map $map
then
% array set a {foo bar baz qux}
% array foreach {key value} a {puts "key=$key, value=$value"}
key=foo, value=bar
key=baz, value=qux
I have a bash script that calls an expect script like so
$SCRIPTS_DIRECTORY/my_expect_script.sh $my_bash_array
I can pass a variable over, it seems, and use it.
For this example the variable seems to be in [lindex $argv 0].
From bash, it will be a bunch of values, e.g. 1 2 3 4 5.
I am trying to figure out how to use expect to grab this variable, save it to an array then loop through the array to spit out multiple commands one at a time.
So my output should be something like
send command 1 \r
send command 2 \r
etc., until it reaches the end of the array.
I thought in expect I would assign it like
array set myArray [lindex $argv 0]
but it looks like I am wrong.
Would anyone have any good places I can look that might explain going from bash to expect better, or know how to do this? I assume its relatively simple, but expect is very iffy with me in some aspects.
Sample.sh
my_array=(1 2 3 4 5)
expect sample.exp "${my_array[#]}"
Sample.exp
foreach arg $argv {
puts "arg : $arg"
}
Output :
dinesh#mypc:~$ ./sample.sh
arg : 1
arg : 2
arg : 3
arg : 4
arg : 5
dinesh#mypc:~$
Expanding on #glenn jackman's comment...
Expect treats an Array as a set of key-pair values, while Expect treats a List simply as a list.
Therefore to be able to pass an array/list as a single arg...
Array Example (myExpectArray.tcl)
array set myArray [lindex $argv 0];
foreach item [array name myArray] {
puts $myArray($item)
}
Call the expect script from command line...
myExpectArray.tcl "0 dog 1 cat 2 rabbit"
To call this from bash, you'd need to loop through the array and build the string to pass to the expect script. Using a List is easier...
List Example (myExpectList.tcl)
set myList [lindex $argv 0];
for {set i 0} {$i < [llength $myList]} {incr i} {
puts [lindex $myList $i]
}
Call the expect script from bash as follows...
myAnimals+=( "dog" "cat" "rabbit" )
myArgs=${myAnimals[#]}
myExpectList.tcl "$myArgs"
Note: You must convert the list ($myAnimals) to a string ($myArgs) otherwise Expect treats each element of the array as a unique arg.
Maybe this is pretty stupid, but I really can't find a soulution. I created two variables and want to transform them into lists.
This commands are tool specific, but they work the way I want:
redirect max_transition {report_constraint -view $pargs(-scenario) -drv_violation_type {max_transition} -all_violators} -variable
redirect max_capacitance {report_constraint -view $pargs(-scenario) -drv_violation_type {max_capacitance} -all_violators} -variable
Now I want to create tcl lists out of them. I could use a loop, because the data has the same structure.
set reports {$max_transition $max_capacitance}
set report_length [llength $reports]
for {set i 0} {$i < $report_length} {incr i} {
set tns_value 0
set max_wns 0
set vios 0
set report [lindex $reports $i]
puts $report
# remove all uneccessary white spaces
set no_space [regexp -all -inline {\S+} $report]
# insert a new line for every path
set insert_lines [string map {" U_" \nU_} $no_space]
# create list out of result reports
set report_list [split $insert_lines "\n"]
if {[llength $report_list] > 1} {
for {set i 1} {$i < [llength $report_list]} {incr i} {
# get value of violation
set slack [lindex [split [lindex $report_list $i] " "] 3]
set tns_value [expr $tns_value + $slack]
if {$vios == 0} {set max_wns $slack}
incr vios
}
}
# write out values
puts "$pargs(-scenario), $report, $max_wns, $tns_value, $vios"
}
But this does not work out. The loop just puts out the variable's names (because of "puts $report") but not its content.
If I do it without a loop (so for each variable the same code consecutively), I get the lists I want.
So how can I process these variables as a whole in a loop?
The problem lies in the below loop variable i, it is overriding variable value of outer-loop. Try changing inner-loop variable to j.
for {set i 1} {$i < [llength $report_list]} {incr i} {
# get value of violation
set slack [lindex [split [lindex $report_list $i] " "] 3]
set tns_value [expr $tns_value + $slack]
if {$vios == 0} {set max_wns $slack}
incr vios
}
It's hard to write an answer for this since so much is unknown. To begin with, you should probably change to assignment by list and a foreach loop like this:
set reports [list $max_transition $max_capacitance]
foreach report $reports {
Since you don't really need to use a for loop here, it makes sense to simplify it. Please comment and I will iteratively improve the answer if I can.
I have 5 arrays and i want to iterate over them one by one, so I'm thinking to add these arrays to another array and access them one by one with indexes
array set ListOfArrays {1 $array1 2 $array2 3 $array3 4 $array4 5 $array5}
for { set i 1} { $i <= 5 } {incr i} {
set list $ListOfArrays($i)
foreach {key} [array names list] {
puts $key
}
}
The output is empty...
what is wrong?!
Tcl's arrays can't be put in a list; they're (collections of) variables, not values. But you can put the names of the arrays in.
array set ListOfArrays {1 array1 2 array2 3 array3 4 array4 5 array5}
for { set i 1} { $i <= 5 } {incr i} {
upvar 0 $ListOfArrays($i) list
foreach {key} [array names list] {
puts $key
}
}
The upvar 0? It makes a local alias to a variable, and it's great for this sort of thing.
As an alternative to the array-based solutions, a solution using dictionaries. I borrow the data from Jerry's answer.
set dict1 {1 a 2 b}
set dict2 {3 c 4 d}
set dict3 {5 e 6 f}
set dict4 {7 g 8 h}
set dict5 {9 i 10 j}
set dicts [list $dict1 $dict2 $dict3 $dict4 $dict5]
foreach dict $dicts {
foreach key [dict keys $dict] {
puts $key
}
}
Unlike arrays, dictionaries can be passed as values and evaluated using $. This means that you can put your five dictionaries in a list and traverse that list with foreach. In the inner loop, each key is printed.
foreach dict $dicts {
dict for {key -} $dict {
puts $key
}
}
is basically the same thing.
Documentation: dict, foreach, list, puts, set
I have a proc which finishes off with an array. To return it I use "array get" to retrive a list. This list however does not only contain my array entrys, but also their index:
So my array [ a b c d ] turns into a list { 0 a 1 b 2 c 3 d }
How can i get rid of these index numbers without disturbing the list order?
A few options, apart from using foreach:
# [array get] actually returns a dictionary
puts [dict values $list]
# Could do this too
set entrylist {}
dict for {- entry} $list {
lappend entrylist $entry
}
puts $entrylist
There are more possibilities in Tcl 8.6:
puts [lmap {- entry} $list {set entry}]
(There's also a dict map, but it isn't useful here.)
I like dict values…
The simplest and most basic way I think would be to use a foreach loop:
% set list {0 a 1 b 2 c 3 d}
% set entrylist {}
% foreach {id entry} $list {
% lappend entrylist $entry
% }
% puts $entrylist
a b c d
If you already have an array and work with Tcl 8.5 or later, use dict values:
set arr(0) a
set arr(1) b
set arr(2) c
set arr(3) d
puts [dict values [array get arr]]
But it is better to use a simple list:
set mylist {a b c d}
lset list 1 boo
puts [lindex $mylist 1]
lappend mylist eff
Arrays are associative. Always.