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 $arrayThe 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 arraySo, 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