Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

arrays

tcl

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?

like image 477
Shawn Avatar asked Sep 06 '25 03:09

Shawn


1 Answers

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).

like image 134
RHSeeger Avatar answered Sep 08 '25 01:09

RHSeeger