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开发者_Python百科 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
精彩评论