Tcl Part I · Module 7 · Dictionaries and Arrays
Inspecting an Array: array names, exists, size, parray
Four commands for finding out what an array contains, one of which exists purely to help you while debugging. They are straightforward, and this chapter spends most of its length on a single fact that follows from them and that trips up more Tcl scripts than any other array behaviour: an array has no order whatsoever. Not insertion order, not alphabetical, not stable in any sense you can plan around. Dictionaries guarantee an order and arrays guarantee nothing, and once you have seen what that looks like in practice, sorting stops being a stylistic preference.
Foundation11 min readTclPart IArraysIntrospectionparray
Part I · Module 7 · Chapter 7.10 · Inspecting an Array: array names, exists, size, parray
1. The three questions
array set limits {retries 3 timeout 60 mode fast}
puts "exists : [array exists limits]"
puts "size : [array size limits]"
puts "names : [lsort [array names limits]]"exists : 1
size : 3
names : mode retries timeoutarray exists answers whether the name is an array — not whether it has anything in it, and not whether the name exists at all. array size counts the elements. array names returns them as a list.
Note the lsort. Section 4 explains why it is there.
2. array names takes a pattern
Same glob language as dict keys from Chapter 7.4:
array set p {max_fan 16 max_tran 0.4 min_width 2 mode fast}
puts [lsort [array names p max_*]]
puts [lsort [array names p *_*]]
puts "[array names p zzz*]<- nothing"max_fan max_tran
max_fan max_tran min_width
<- nothingCounting matches is then an llength:
puts [llength [array names p max_*]]23. Testing a single element
array names tells you what is there; info exists tells you about one element:
array set limits {retries 3}
puts "retries : [info exists limits(retries)]"
puts "timeout : [info exists limits(timeout)]"retries : 1
timeout : 0This is the test to use before reading an element you are not sure about — reading a missing one is an error, as Chapter 7.8 §4 showed.
| Question | Command |
|---|---|
Is a an array? | array exists a |
Is element a(x) set? | info exists a(x) |
| How many elements? | array size a |
| Which elements? | array names a ?pattern? |
4. An array has no order
Build an array in a deliberate order and ask for the names back:
foreach x {alpha beta gamma delta epsilon zeta} {
set A($x) 1
}
puts [array names A]zeta beta epsilon delta alpha gammaThat is not alphabetical, not the insertion order, and not reversed. It is the order the internal hash table happens to hold, and the language does not define it. The same thing happens with array get:
array set lim {retries 3 timeout 60 mode fast width 8 depth 2}
puts [array get lim]width 8 depth 2 mode fast retries 3 timeout 60Compare with the dictionary from Chapter 7.5 §4, which returns insertion order every single time:
set d [dict create]
foreach x {alpha beta gamma delta epsilon zeta} {
dict set d $x 1
}
puts [dict keys $d]alpha beta gamma delta epsilon zeta5. Sorting by something other than the name
Because array names gives you a list, every tool from Chapter 6.10 applies — including sorting the names by their values:
array set cycles {smoke 120 regress 4500 nightly 18000 lint 60}
proc by_value {arrName a b} {
upvar 1 $arrName arr
expr {$arr($a) - $arr($b)}
}
foreach k [lsort -command [list by_value cycles] [array names cycles]] {
puts [format "%-10s %6d" $k $cycles($k)]
}lint 60
smoke 120
regress 4500
nightly 18000lsort -command passes two elements to your procedure and expects a negative, zero or positive result — the custom-ordering option from Chapter 6.10. Here the elements are names and the comparison looks up their values.
6. parray, for debugging
parray prints an array, sorted and aligned:
array set limits {retries 3 timeout 60 mode fast width 8}
parray limitslimits(mode) = fast
limits(retries) = 3
limits(timeout) = 60
limits(width) = 8Sorted by name, with the = signs lined up, and each line labelled with the array name — so pasting it into a bug report is self-explanatory. It takes a pattern too:
array set P {max_a 1 max_b 2 min_c 3}
parray P max_*P(max_a) = 1
P(max_b) = 2And it tells you plainly when the name is not an array:
set scalar 1
parray scalar"scalar" isn't an array7. A debugging walk-through
A small procedure that reports everything knowable about a name:
proc describe {name} {
upvar 1 $name v
if {![info exists v]} {
puts "$name: does not exist"
return
}
if {[array exists v]} {
puts "$name: array with [array size v] element(s)"
foreach k [lsort [array names v]] {
puts [format " %-10s %s" $k $v($k)]
}
} else {
puts "$name: scalar = $v"
}
}
array set limits {retries 3 timeout 60}
set plain hello
describe limits
describe plain
describe missinglimits: array with 2 element(s)
retries 3
timeout 60
plain: scalar = hello
missing: does not existThree states, three answers. Note the order of the tests: info exists first, because it is true for both kinds, then array exists to tell them apart — the distinction from Chapter 7.8 §6.
8. Exercises
Exercise 1. Given array set a {x 1 y 2 z 3}, which of these are guaranteed to print the same thing on every run and every machine: array size a, array names a, lsort [array names a], array get a?
Exercise 2. Write one line printing the number of elements whose name ends in _max.
Exercise 3. Explain why lsort is optional with dict keys and effectively mandatory with array names.
Exercise 4. What does set out [parray a] leave in out, and what does the script show on screen?
Exercise 5. Write dump — a procedure returning (not printing) a parray-style string for an array, sorted, with values aligned in a column.
Exercise 6. A colleague reports that their script's output rows "move around between runs". The rows come from foreach k [array names results]. Give the diagnosis and the one-word fix.
Answers
1. array size a and lsort [array names a] are guaranteed. array names a and array get a are not — their order is undefined.
2.
array set a {w_max 1 h_max 2 depth 3}
puts [llength [array names a *_max]]23. A dictionary guarantees insertion order, so dict keys already has a defined, reproducible order and lsort merely changes it to a different one. An array guarantees nothing, so without lsort the order is arbitrary and may change — there is no correct order to fall back on.
4. out is empty, because parray returns nothing. The array still prints on screen, because parray writes directly to standard output as a side effect.
5.
proc dump {name} {
upvar 1 $name a
set lines {}
set w 0
foreach k [array names a] {
if {[string length $k] > $w} { set w [string length $k] }
}
foreach k [lsort [array names a]] {
lappend lines [format "%-*s = %s" $w $k $a($k)]
}
return [join $lines "\n"]
}
array set limits {retries 3 timeout 60 mode fast}
puts [dump limits]mode = fast
retries = 3
timeout = 60%-*s takes its width from an argument — the trick from Chapter 5.11.
6. The diagnosis is that array names has no defined order, so the rows can come out differently on different runs or interpreters. The fix is lsort.
9. Remember
array exists aasks whether the name is an array;array size acounts elements;array names a ?pattern?lists them.info exists a(x)is the test for a single element, and the one to use before reading something that may be absent.- An array has no order at all.
array namesandarray getreturn whatever the hash table holds, and it is not defined by the language. - Dictionaries guarantee insertion order; arrays guarantee nothing.
lsortis a choice for one and part of the idiom for the other. - Never let unsorted
array namesoutput reach a report or a file. lsort -commandsorts names by their values, sincearray namesreturns an ordinary list.parrayprints sorted, aligned output for debugging — it takes a pattern, says"x" isn't an arraywhen it should, and returns nothing, so you cannot capture it.
Where this fits
Part of the Tcl curriculum.
