Often you will want to loop through the contents of an associative
array - without having to specify the elements explicitly. For this
the array names and array get commands are very useful. With both you
can give a (glob-style) pattern to select what elements you need:
foreach name [array names mydata] {
puts "Data on \"$name\": $mydata($name)"
}
#
# Get names and values directly
#
foreach {name value} [array get mydata] {
puts "Data on \"$name\": $value"
}
Note, however, that the elements will not be returned in any
predictable order: this has to do with the underlying "hash table".
If you want a particular ordering (alphabetical for instance), use
code like:
foreach name [lsort [array names mydata]] {
puts "Data on \"$name\": $mydata($name)"
}
While arrays are great as a storage facility for some purposes,
they are a bit tricky when you pass them to a procedure: they are
actually collections of variables. This will not work:
proc print12 {a} {
puts "$a(1), $a(2)"
}
set array(1) "A"
set array(2) "B"
print12 $array
The reason is very simple: an array does not have a value. Instead
the above code should be:
proc print12 {array} {
upvar $array a
puts "$a(1), $a(2)"
}
set array(1) "A"
set array(2) "B"
print12 array
So, instead of passing a "value" for the array, you pass the
name. This gets aliased (via the upvar command) to a local
variable (that behaves the as original array). You can make changes
to the original array in this way too.
Example
#
# The example of the previous lesson revisited - to get a
# more general "database"
#
proc addname {db first last} {
upvar $db name
# Create a new ID (stored in the name array too for easy access)
incr name(ID)
set id $name(ID)
set name($id,first) $first ;# The index is simply a string!
set name($id,last) $last ;# So we can use both fixed and
;# varying parts
}
proc report {db} {
upvar $db name
# Loop over the last names: make a map from last name to ID
foreach n [array names name "*,last"] {
#
# Split the name to get the ID - the first part of the name!
#
regexp {^[^,]+} $n id
#
# Store in a temporary array:
# an "inverse" map of last name to ID)
#
set last $name($n)
set tmp($last) $id
}
#
# Now we can easily print the names in the order we want!
#
foreach last [lsort [array names tmp]] {
set id $tmp($last)
puts " $name($id,first) $name($id,last)"
}
}
#
# Initialise the array and add a few names
#
set fictional_name(ID) 0
set historical_name(ID) 0
addname fictional_name Mary Poppins
addname fictional_name Uriah Heep
addname fictional_name Frodo Baggins
addname historical_name Rene Descartes
addname historical_name Richard Lionheart
addname historical_name Leonardo "da Vinci"
addname historical_name Charles Baudelaire
addname historical_name Julius Caesar
#
# Some simple reporting
#
puts "Fictional characters:"
report fictional_name
puts "Historical characters:"
report historical_name