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
Related
I have several arrays with same key range. I am trying to print them as columns sorted by key value.
For eg: binA($i), binB($i), binC($i) I:1-100
Expected output:
i A B C
1 binA(1) binB(1) binC(1)
2
3
....
100 .....
Can anyone please show me how to do this in TCL?
untested
set bins {A B C}
foreach i [lsort -integer [array names binA]] {
set fields [list $i]
foreach bin $bins {
lappend fields [set bin${bin}($i)]
}
puts [join $fields]
}
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
#!/usr/bin/expect -f
set myarr1(chicken) animal
set myarr1(cows) animal
set myarr1(tiger) animal
set myarr1(horse) animal
set myarr2(carrot) vegetable
set myarr2(tomato) vegetable
set myarr2(potato) vegetable
set myarr2(pea) vegetable
set arr_list { myarr1 myarr2 }
foreach key [array names [lindex $arr_list 0]] {
puts "${key}=$[lindex $arr_list 0]($key)"
}
foreach key [array names [lindex $arr_list 1]] {
puts "${key}=$[lindex $arr_list 1]($key)"
}
Output obtained:
cows=$myarr1(cows)
horse=$myarr1(horse)
chicken=$myarr1(chicken)
tiger=$myarr1(tiger)
tomato=$myarr2(tomato)
pea=$myarr2(pea)
potato=$myarr2(potato)
carrot=$myarr2(carrot)
Required output:
cows=animal
horse=animal
chicken=animal
tiger=animal
tomato=vegetable
pea=vegetable
potato=vegetable
carrot=vegetable
I am able to get the required output if I use the following in foreach loop:
foreach key [array names myarr1] {
puts "${key}=$myarr1($key)"
}
foreach key [array names myarr2] {
puts "${key}=$myarr2($key)"
}
I am trying to create a list of array names and then loop through that list of array names and print it. If there is a better way to approach this problem, I am all ears. Thanks for the assist !
You really ought to use nested foreachs for that.
foreach arr $arr_list {
foreach key [array names $arr] {
puts "${key}=$$arr($key)"
}
}
Except that doesn't work! Why? It's easy on one level: the syntax for $ doesn't support such complexity; it really only supports a (very useful) subset of legal variable names and can't do complicated substitutions (array element names support more diverse options). We need to rewrite to use the single-argument set form as a first step:
foreach arr $arr_list {
foreach key [array names $arr] {
puts "${key}=[set [set arr]($key)]"
}
}
That works, but isn't very elegant (or fast, for that matter). It's actually better to use upvar 0 to make a local alias to the array that you're processing; variable alias from a to whatever was talked about with $arr will let us shorten things elsewhere, and it's pretty elegant in practice:
foreach arr $arr_list {
upvar 0 $arr a
foreach key [array names a] {
puts "$key=$a($key)"
}
}
You can also do things like sorting the list of element names (array names does not guarantee to return things in any particular order), putting spacing in between each of the arrays that you print out, etc. But that's the core of how to improve things. You can also use array get instead of array names and a multi-variable foreach, but then sorting the keys is more awkward (well, before Tcl 8.6's lsort gained the -stride option).
If you just want to print them, use the bundled parray proc:
foreach arr $arr_list {
parray $arr
}
which outputs
myarr1(chicken) = animal
myarr1(cows) = animal
myarr1(horse) = animal
myarr1(tiger) = animal
myarr2(carrot) = vegetable
myarr2(pea) = vegetable
myarr2(potato) = vegetable
myarr2(tomato) = vegetable
You can use the foreach command to traverse both the keys and the values in the array.
array set arr {a 1 b 2 c 3}
foreach {k v} [array get arr] {
puts $k=$v
}
References: foreach
You're on the right track with the foreach command, but to loop over the list of arrays you need to nest it:
foreach arr $arr_list {
foreach {key val} [array get arr] {
puts "$key = $val"
}
}
Also, look into the parray command, and see if that style of printing works for what you need. (I'd link it, but the site appears to be down at the moment. I'll try to remember to edit this later. Check out the command details at http://www.tcl.tk/man/tcl8.6/)
What you probably want is:
foreach arr $arr_list {
foreach key [array names $arr] {
puts "$key=[set ${arr}($key)]"
}
}
That said, you’re using arrays in a non-standard way that possibly is suboptimal. Have you looked into dictionaries? (Unfortunately, discussing how your data should be structured isn’t a Stackoverflow topic.)
While executing a script, I need to delete multiple elements (these elements are not sequential) of an array. I will get my array and indexes while executing the script.
For example:
I may get an array and list of indexes like below:
my #array = qw(one two three four five six seven eight nine);
my #indexes = ( 2, 5, 7 );
I have below subroutine to do this:
sub splicen {
my $count = 0;
my $array_ref = shift #_;
croak "Not an ARRAY ref $array_ref in $0 \n"
if ref $array_ref ne 'ARRAY';
for (#_) {
my $index = $_ - $count;
splice #{$array_ref}, $index, 1;
$count++;
}
return $array_ref;
}
If I call my subroutine like below:
splicen(\#array , #indexes);
That works for me but:
Is there any better way to do this?
If instead you splice from the end of the array, you won't have to maintain the offset $count:
sub delete_elements {
my ( $array_ref, #indices ) = #_;
# Remove indexes from end of the array first
for ( sort { $b <=> $a } #indices ) {
splice #$array_ref, $_, 1;
}
}
Another way think about it is to build a new array rather than modifying the original:
my #array = qw(one two three four five size seven eight nine);
my #indexes = (2, 5, 7);
my %indexes = map { $_ => 1 } #indexes;
my #kept = map { $array[$_] } grep { ! exists $indexes{$_} } 0 .. $#array;
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.