upvar command to actually use them inside the
procedure.set array(1,2) 10 set array(2,2) 11This is quite possible, but it can become very clumsy (there can be no intervening spaces for instance).
In Tcl 8.5 the dict command
has been introduced. This provides efficient access to key-value
pairs, just like arrays, but dictionaries are pure values. This
means that you can pass them to a procedure just as a list or a
string, without the need for dict.
Unlike arrays, you can nest dictionaries, so that the value for a particular key consists of another dictionary. That way you can elegantly build complicated data structures, such as hierarchical databases.
Here is an example (adapted from the man page):
#
# Create a dictionary:
# Two clients, known by their client number,
# with forenames, surname
#
dict set clients 1 forenames Joe
dict set clients 1 surname Schmoe
dict set clients 2 forenames Anne
dict set clients 2 surname Other
#
# Print a table
#
puts "Number of clients: [dict size $clients]"
dict for {id info} $clients {
puts "Client $id:"
dict with info {
puts " Name: $forenames $surname"
}
}
What happens in this example is:
dict set command accepts a list of keywords
(descending into the nesting of the dictionaries) and uses the last
argument as the actual value.foreach to
loop over the contents of the dictionary (only the first
level!).dict
with command. This command takes the dictionary and sets
variables by the name of the keys to the values in that dictionary.
That way the contents is readily available via these
variables.Done up to this point
# # 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.
#
# 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