| ActiveTcl User Guide |

# More Array Commands - Iterating and use in procedures

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