C-Like Array in TCL - c

I'm porting a program from C to TCL, and I'm trying to implement a data structure similar to an Array in C. The two main things I need it to do are
Be Ordered
Allow insertion into any index
Return the array from a procedure
I will know the size of the array before run time, and the size should not change throughout the program (so it is static). Are there any data structures that fit this bill?
I'm using TCL 8.6 if that matters
EDIT: I also need to be able to return the data structure from a function.

The corresponding data structure would be list. It meets all your requirements. If you want it to have a fixed size, you "pre-allocate" it like this:
set data [lrepeat 8 {}]
which creates eight empty compartments.
It is ordered, you can access every element by index (0-based), and you can pass its value into procedures/functions and also return it. You can traverse it with e.g. foreach and for, and there are a lot of list manipulating commands.
While list is the Tcl data container that corresponds most closely to a C array, one can use array or dict to simulate a fixed-size, direct-access, ordered container.
# allocation
set listdata [lrepeat 8 {}]
array set arraydata {0 {} 1 {} 2 {} 3 {} 4 {} 5 {} 6 {} 7 {}}
set dictdata [dict create 0 {} 1 {} 2 {} 3 {} 4 {} 5 {} 6 {} 7 {}]
# or
set dictdata {0 {} 1 {} 2 {} 3 {} 4 {} 5 {} 6 {} 7 {}}
# access (write, then read)
lset listdata 5 96
lindex $listdata 5 ;# -> 96
set arraydata(5) 96
set arraydata(5) ;# -> 96
dict set dictdata 5 96
dict get $dictdata 5 ;# -> 96
# return from procedure
# (not possible with arraydata, arrays are shared using either
# scope manipulation or namespaces)
return $listdata
return $dictdata
# traversal
for {set i 0} {$i < 8} {incr i} {puts [lindex $listdata $i]}
# or
foreach elem $listdata {puts $elem}
for {set i 0} {$i < 8} {incr i} {puts [lindex $arraydata($i)]}
dict for {idx val} $dictdata {puts $val}
Documentation: array, dict, for, foreach, incr, lindex, lrepeat, lset, puts, return, set

Either the tcl array (some examples) or the tcl dict command will suit your needs.

Related

How can I create and push to a shared or distributed array of arrays?

I have written Julia code in which I initialize an empty array as follows:
a = []
Later in the code, I simply push to this array as follows:
push![a, b]
where b = [c, d, e, ...] is another array, and each b can be of different length.
This works just fine in un-parallelized code. However, I want to do the same thing in parallelized code where a = [] is a shared or distributed array that the different processors can push to.
Neither SharedArray or DArray worked for me. Any advice?
Firstly you should always need to declare what are you holding in your array [] means Any[] and it is almost never a good idea.
Let us consider this vector with placeholders:
julia> a=[Int[] for _ in 1:8]
8-element Vector{Vector{Int64}}:
[]
[]
[]
[]
[]
[]
[]
[]
This Vector contains 8 references to other Vectors.
Let us now distribute it:
julia> using Distributed; addprocs(4);
julia> #everywhere using DistributedArrays
julia> b = distribute(a)
8-element DArray{Vector{Int64}, 1, Vector{Vector{Int64}}}:
[]
[]
[]
[]
[]
[]
[]
[]
This new b is now available through all worker processes where each worker holds its localpart of it. Let us mutate it!
julia> fetch(#spawnat 2 append!(localpart(b)[1], [1,2,3,4]));
julia> fetch(#spawnat 3 append!(localpart(b)[2], [10,20]));
julia> fetch(#spawnat 3 push!(localpart(b)[2], 30))
3-element Vector{Int64}:
10
20
30
We can see that everything is working as expected (we have used fetch to make sure our code actually got executed on remote workers).
Let us know check on the master process the state of b:
julia> b
8-element DArray{Vector{Int64}, 1, Vector{Vector{Int64}}}:
[1, 2, 3, 4]
[]
[]
[10, 20, 30]
[]
[]
[]
[]
You can see that we have successfully used remote workers to mutate b.
I asked a similar question here. I originally followed Prezmyslaw's answer but could not get distribute to distribute an already existing array the way I thought it would for Julia 1.7. What worked for me was defining the array as it was initialized :
using Distributed
addprocs(4)
#everywhere using DistributedArrays
a = distribute([[] for _ in procs()])
#sync #distributed for i = 1:10
b = fill(i, 5)
append!(localpart(a)[1], b) # I swapped push! for append!
end
a
What this does is: first it creates an array with subarrays that are distributed to each worker, then it distributes computation and fills the corresponding subarrays with the values calculated on each worker, finally it merges the subarrays to obtain a full array with all the values.
It is interesting to compare this with the exact same code but substituting a = distribute([[] for _ in procs()]) for a = [[] for _ in procs()]; distribute(a). Evidently the latter does not work as expected (at least for Julia 1.7).

How to create a ragged array in Chapel

I'm attempting to create and "array of arrays" all of different lengths. For instance
A = [[1 2 5 ]
[3 5]
[2 6 9]]
With Chapel strongly typed, I don't know how to declare and populate A. My first use of this will be to iterate over A and find a set of unique elements, as in
[1 2 3 5 6 9]
So it would be useful to see an example of construction and use. Thanks!
We call this feature "skyline" arrays or "jagged" arrays. While Chapel currently does not implement it directly, it is easy to do it with records. For example, each element of the outer array can be a record carrying the corresponding inner array and its domain:
record InnerArray {
var dom: domain(1); // starts out empty
var arr: [dom] int;
// Optional function: when printing an InnerArray, do not show the domain.
proc writeThis(c: channel) { c.write("["); c.write(arr); c.write("]"); }
}
proc initElem(ref dest: InnerArray, src: []) {
dest.dom = src.domain;
dest.arr = src;
}
var A: [1..3] InnerArray;
initElem(A[1], [1,2,5]);
initElem(A[2], [3,5]);
initElem(A[3], [2,6,9]);
// The default printout invokes writeThis on each element of A.
writeln(A);
// To iterate over all inner elements, need nested loops.
// If appropriate, either/both can be "forall".
for outer in A do
for inner in outer.arr do
writeln(inner);

How to Retrieve array key values in Tcl

Given Data
array set color {a red b green c blue}
set keylist [array names $color]
pointlist {{1 2 3 4} {5 6 7 8} {9 10 11 12} {13 14 15 16}}
I get a new list after running a procedure.
newlist {b b b b} # Note: length of newlist is same as length of pointlist
Question is : when i run a loop these values of b should be substituted, for example in this green.
for {set i 0} { $i < [llength $pointlist]} {incr i } {
lassign [lindex $pointlist $i] x1 y1 x2 y2
APICommand -points "$x1,$y1,$x2,$y2" -color $color($keylist)
}
Now this $color($keylist) is not getting me correct result and errors out saying :
can't read "color(red blue green)": no such element in array
Instead I want the first set of 4 points to get the green color which is the value of b. Similarly next set of 4 points from pointlist should also get green as its value is also b.
#So, after substitution it should look like APIcommand -points 1,2,3,4 -color green ..... so on and so forth
Note this will not always be b , it just happens to be in this case.
Need solution asap. Thanks in advance.
Sounds like you need $color([lindex $newlist $i])
When iterating across a list, the foreach command is useful. It can also be used to iterate over multiple corresponding lists. Assuming newlist holds the colors that correspond to the points, I might try something like (usual warnings about untested code):
foreach point $pointlist new $newlist {
APICommand -points [join $point ,] -color $color($new)
}

How can I make sure TCL doesn't mess up the order of array elements?

The problem is that the order in which I "insert" elements in an array changes throughout the execution of the script.
Here is a quick reproduction of the problem:
#!/bin/bash
# : \
exec /home/binops/afse/eer/eer_SPI-7.3.1/tclsh "$0" "$#"
proc myProc { theArray } {
upvar $theArray theArrayInside
parray theArrayInside
puts "------"
foreach { key value } [array get theArrayInside] {
puts "$key => $value"
}
}
# MAIN
set myArray(AQHI) AQHI
set myArray(O3) 1
set myArray(NO2) 2
set myArray(PM2.5) 3
parray myArray
puts "------"
myProc myArray
Output is:
myArray(AQHI) = AQHI
myArray(NO2) = 2
myArray(O3) = 1
myArray(PM2.5) = 3
------
theArrayInside(AQHI) = AQHI
theArrayInside(NO2) = 2
theArrayInside(O3) = 1
theArrayInside(PM2.5) = 3
------
PM2.5 => 3
O3 => 1
NO2 => 2
AQHI => AQHI
Notice I didn't use generic keys like A, B, C and generic values like 1, 2, 3, as you may have expected. This is because the order isn't messed up with these generic keys/values. Maybe this can help identify the problem.
Also notice that the initial order (AQHI, O3, NO2, PM2.5) is lost even at the first call to parray (order is now AQHI, NO2, O3, PM2.5; alphabetically sorted?). It is then changed again upon calling array get ... (inversed?)
So anyways, the question is: How can I make sure the initial order is kept?
You're making the mistake of equating Tcl arrays to those in a language like C, where it's a list of elements. Instead, Tcl arrays are maps (from a key to a value) like a HashMap in Java, and the order of elements is not preserved.
You may be better off using a list (if you just have a number of items you need to store in order).
If you're using 8.5 or higher, a dict if you actually have a mapping of keys to values, since dictionaries are order preserving maps. There are dict backports to Tcl versions before 8.5, but I'm not sure whether they preserve order (and they're slower).
If you can't use 8.5 dicts and need key/value pairs, one option is to use a list of key value pairs and then use lsearch to pull out the values you need
> set mylist {{key1 value1} {key2 value2} {key3 value3}}
> lsearch -index 0 $mylist key2
0
> lindex $mylist [list [lsearch -index 0 $mylist key2] 1]
> value2
> proc kv_lookup {dictList key} {
set index [lsearch -index 0 $dictList $key]
if {$index < 0} {
error "Key '$key' not found in list $dictList"
}
return [lindex $dictList [list $index 1]]
}
> kv_lookup $mylist key2
value2
Man pages for 8.4 are here
You may also want to look at this page on keyed lists for Tcl. It implements what I mentioned above, plus some other useful commands.
For an example of the different between an ordered "map" and an unordered one, you can take a look at the two java classes HashMap (unordered) and LinkedHashMap (ordered).
Tcl is flexible enough that one can devise many schemes to handle what you want. Here's an idea that stores the order of your keys within the array itself, assuming the empty string is not a valid key in your data:
proc array_add {ary_name key value} {
upvar 1 $ary_name ary
set ary($key) $value
lappend ary() $key
}
proc array_foreach {var_name ary_name script} {
upvar 1 $var_name var
upvar 1 $ary_name ary
foreach var $ary() {
uplevel 1 $script
}
}
array_add a foo bar
array_add a baz qux
array_add a abc def
array_add a ghi jkl
array_foreach key a {puts "$key -> $a($key)"}
# foo -> bar
# baz -> qux
# abc -> def
# ghi -> jkl
array names a
# ghi {} foo baz abc
array get a
# ghi jkl {} {foo baz abc ghi} foo bar baz qux abc def
parray a
# a() = foo baz abc ghi
# a(abc) = def
# a(baz) = qux
# a(foo) = bar
# a(ghi) = jkl

Dual array correspondance

I just found myself in a position where I have two arrays in Tcl.
I'm given $W_Array and $P_Array.
I need to traverse through one array not knowing what the size of each one is before hand, and execute a command only when there is a value for both arrays. Yes the array lengths could be different.
What is the best way of doing this?
The other answers jumped to using lists, I presume you mean Tcl's array, which are also called hash maps or associative arrays.
I think you're asking for something like:
array set a1 {a 1 b 2 c 3 d 4 e 5}
array set a2 {z 0 x 1 b 2 e 99}
foreach n [array names a1] {
if {[info exists a2($n)]} {
puts "Do something with $a1($n) and $a2($n)"
}
}
# FOREACH LOOP RESULTS IN THESE TWO PRINTOUTS
Do something with 5 and 99
Do something with 2 and 2
Not sure exactly what you mean by "a value for both arrays", but tcl's foreach supports iteration over multiple arrays at once... so you can say e.g.
foreach w $W_Array p $P_Array {
if {$w == $val && $p == $val} {
...
}
}
When the arrays are not of the same length, foreach will return all values from the longest array and the empty value {} for the missing elements in any shorter arrays.
Use llength command to find out if the arrays contain a value.
if {[llength $W_Array] > 0 && [llength $P_Array] > 0} {
# Do something
}

Resources