macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Playing with Keyed Lists on Mac OS X Tcl/Tk Aqua 8.4.2

by Michael J. Norton
06/27/2003

Open source developers have been feverishly porting UNIX applications over to the Mac OS X platform. Apple helped by introducing X11 and offering it as a free download. Life was getting good.

Personally what I really needed was Tclx and Expect, extensions of the Tool Command Language (Tcl), to be ported natively to the Mac OS X Aqua environment. The Tcl environment was available for Mac OS and Mac OS X, but without the bells and whistles I was accustomed to in the UNIX environment. My wish was granted in March 2003 with the posting at the Apple Unix and Open Source web site. The Sourceforge Macintosh Tcl developers posted the Tcl/Tk Aqua 8.4.2 for Mac OS X, batteries included release. Life was getting really good.

The "batteries included" package was submitted with an installer. It loads the Wish App into your App/utilities folder. Life was getting really, really good!

Importance of Tcl/Tk Aqua 8.4.2

Now what's so important about this package? From my perspective, it is the inclusion of Tcl extensions, the Tclx package, and the inclusion of Expect. This was available through the fink Darwin installation package, which ran under X11 or the Mac OS X UNIX terminal app. The batteries included package was for the Aqua interface! I can't tell you how long I waited for all these little goodies to be available under the Mac OS and be provided simultaneously in one package. Now Tcl scripts can pretty much be ported from UNIX and Windows platforms, which have supported Tclx for years.

One important aspect of the Tclx package, in a nutshell, is keyed lists. This is a little programming feature that helps simplify complex lists in Tcl. Keyed lists, as we'll see, provide scripting functionality similar to C structs. For starters, let's take a look at lists, one of the basic data structures in Tcl.

Lists in Tcl

The list is the fundamental data structure of Tcl. A list is a collection of elements. The Tcl list data structure is comprised of string elements. Pretty much everything in Tcl is a special representation of a string. As a simple example, let's build a simple periodic table of elements using the Helium atom. Its representation as a Tcl list is the following:

% set helium_atom { 2 He 4.00260 1s2 }
 2 He 4.00260 1s2

The helium_atom is a very simple Tcl list. You'll see the basic syntax of the Tcl list. The set command is used to assign the list values to the variable name, helium_atom. The elements of the Tcl list are enclosed in braces. The elements of the list are space separated. Each element in the list is a string value.

Retrieving Elements From the List

We have a basic Tcl list, named helium_atom, that contains values for the Helium atom's atomic number, its periodic table symbol, the atomic weight, and electron configuration. We can extract individual element values from the list using the Tcl command, lindex. This Tcl command extracts elements from a list based on the element's index in the list. The first element in the list has the index of 0.

syntax: lindex $list_name index

% set helium_atom { 2 He 4.00260 1s2 }
 2 He 4.00260 1s2 
% set helium_atomic_number [lindex $helium_atom 0]
 2
% set helium_atom_symbol [lindex $helium_atom 1]
 He
% set helium_atomic_weight [lindex $helium_atom 2]
 4.00260
% set helium_electron_configuration [lindex $helium_atom 3 ]
 1s2

If you are confused by those percent signs in the above example, don't worry. I am using the Tcl interpreter to demonstrate these examples. The percent signs are the prompts from the interpreter. Feel free to play along in the interpreter yourself.

Now our one-element periodic table isn't very useful to us, is it? In this next example we're going to build a simple periodic table with two atomic elements, Hydrogen and Helium. Consider this -- we needed a list to store the values for the Helium atom. We will need a second list for the Hydrogen atom. We will need to create a list of periodic elements with each element in the list as a list itself of atomic elements.

Tcl is extremely flexible and will allow you to define a list within a list. We could create a list of a list of atoms. I provided an example of this in Listing 1. We have:

% set atom_list {
>   { Hydrogen { H 1 1.0079 1s1 } }
>   { Helium { He 2 4.00260 1s2 } }
> }

  { Hydrogen { H 1 1.0079 1s1 } }
  { Helium { He 2 4.00260 1s2 } }

The > prompt is the interpreter telling you that you have an open brace. You'll notice that the > prompt goes away with the associated closing brace.

Mac OS X Hacks

Related Reading

Mac OS X Hacks
100 Industrial-Strength Tips & Tricks
By Rael Dornfest, Kevin Hemenway

Simple List Iteration

Our periodic table contains two lists, one for each atom element, Hydrogen and Helium. The periodic table won't really become useful until we employ a means to extract the data from the list. Extracting elements from our list of lists isn't so trivial. We will now have to iterate through each list to extract elements. The Tcl command foreach iterates through all the elements in a list. In our example, a list is an element in the list. We will retrieve each individual list by looping through the atom_list. The list, atom, is assigned the individual list as we loop through.

syntax: foreach element $list_name

% foreach atom $atom_list {
> puts $atom
> }
 Hydrogen { H 1 1.0079 1s1 } 
 Helium { He 2 4.00260 1s2 } 

List iteration in Tcl is pretty slick, isn't it? Imagine writing this using C code. List manipulation is one of the strengths of the Tcl language.

Putting the Concepts Together

So far we have a simple Tcl list with two elements. Each element in the list is a Tcl list. Our next step is to extract the individual atom's data elements. We could use our first solution, using the Tcl command lindex, to achieve the same desired effect. The following code snippet is from Listing 1.

% foreach atom $atom_list {
> set atom_name [lindex $atom 0]
> set atomic_data [lindex $atom 1]
> set atomic_symbol [lindex $atomic_data 0]
> set atomic_number [lindex $atomic_data 1]
> set atomic_weight [lindex $atomic_data 2]
> set electron_configuration [lindex $atomic_data 3]
> puts "$atom_name $atomic_symbol $atomic_number $electron_configuration"
> }
Hydrogen H 1 1s1
Helium He 2 1s2
%

Pretty simple and straightforward, right? But what happens when we must process a fairly complex list of information? Let's see what happens when we add more atomic elements to our periodic table. As an example, we have a portion of the periodic table with the following information from Listing 2:

set periodic_table_list {
  {87 {Francium {Fr} {223} {2-8-18-32-18-8-1} } }
  {88 {Radium {Ra} {226} {2-8-18-32-18-8-2} } }
  {104 {Rutherfordium {RF} {261} {2-8-18-32-32-10-2} } }
  {105 {Dubnium {Db} {262} {2-8-18-32-32-11-2} } }
  {106 {Seaborgium {Sg} {263} {2-8-18-32-32-12-2} } }
  {107 {Bohrium {Bh} {262} {2-8-18-32-32-13-2} } }
  {108 {Hassium {Hs} {265} {2-8-18-32-32-14-2} } }
  {109 {Meitnerium {Mt} {266} {2-8-18-32-32-15-2} } }
  {110 {Ununnilium {Uun} {269} {2-8-18-32-32-17-1} } }
  {111 {Unununium {Uuu} {272} {2-8-18-32-32-18-1} } }
  {112 {Ununbium {Uub} {277} {2-8-18-32-32-18-2} } }
  {113 {Ununtrium {Uut} {undiscovered} {undiscovered} } }
  {114 {Ununquadium {Uuq} {285} {2-8-18-32-32-18-4} } }
  {115 {Ununpentium {Uup} {undiscovered} {undiscovered} } }
  {116 {Ununhexium {Uuh} {289} {2-8-18-32-32-18-6} } }
  {117 {Ununseptium {Uus} {undiscovered} {undiscovered} } }
  {118 {Ununoctium {Uuo} {293} {2-8-18-32-32-18-8} } }
}

Take a moment and examine this code more closely. The objective is to iterate through all the entries in the Tcl list periodic_table_info. It may be a good idea to follow along at the keyboard by sourcing the Listing 2 code from the Wish App or you can cut and past the Tcl list, periodic_table_list into the Tcl interpreter.

foreach atom $periodic_table_list {
	puts $atom
}

This code will generate the following output:

87 {Francium {Fr} {223} {2-8-18-32-18-8-1} } 
88 {Radium {Ra} {226} {2-8-18-32-18-8-2} }
		:
118 {Ununoctium {Uuo} {293} {2-8-18-32-32-18-8} }

If you're playing along in the Tcl interpreter, you'll see that the contents of the last variable in atom are still accessible. Try this Tcl command from your keyboard.

puts $atom
118 {Ununoctium {Uuo} {293} {2-8-18-32-32-18-8} }

This is the last entry from our Tcl list periodic_table_list. We can play around with this simple Tcl list variable and extract individual elements that we'll need. To retrieve the atomic number we simply use our Tcl command lindex. The atomic number is the first element in the list atom. Type the following command into your Tcl interpreter.

set atomic_number [lindex $atom 0]
118

Let's take a close look at the last list element. This list element is a list with a nested list.

{118 {Ununoctium {Uuo} {293} {2-8-18-32-32-18-8} }

Perhaps this is a simpler way of looking at the output:

{118 {nested list}}

Where the nested list has the values:

Ununoctium {Uuo} {293} {2-8-18-32-32-18-8}

Isn't Tcl cool? I know it looks nasty at first. But once you get used to the list syntax and the operation this is all simple and straightforward. Let's take a look at how to extract the nested list itself. Recall that what we have is an element of a list, which in itself is a list.

{118 {Ununoctium {Uuo} {293} {2-8-18-32-32-18-8} }}

Or from the previous example, we have a key and a value -- which is a list.

{118 {nested list}}

To access the atomic number, which is element 0 in the list, we use the following code snippet:

% puts [lindex $atom 0]
118

The second element in the list example is in itself a list. You can see this from the lindex command to extract the second element of this list.

% puts [lindex $atom 1]
Ununoctium {Uuo} {293} {2-8-18-32-32-18-8}

Let's play around with this a little bit. Why don't we create a list from the list element nested list. We'll create a new Tcl list called atom_list. The purpose of creating atom_list is to simplify the list element extraction. Using our original Tcl list, atom, we'll extract the nested list. And do this in the following way:

% set atom_list [lindex $atom 1]
Ununoctium {Uuo} {293} {2-8-18-32-32-18-8}

Now we can extract information from our new list using our normal Tcl list element retrieval command, lindex.

set element_name [lindex $atom_list 0]
Ununoctium
set element_symbol [lindex $atom_list 1]
Uuo
set atomic_weight [lindex $atom_list 2]
293
set electrons [lindex $atom_list 3]
2-8-18-32-32-18-8

I packaged this simple example in Listing 3, which demonstrates how to extract data from lists. Now we have a means to extract all the individual elements from our Tcl list periodic_table_list. Using the tools we used in Listing 3 we can re-examine the original problem of extracting individual data fields from a large Tcl list. This is accomplished with the following code. To loop through our list, see Listing 4.

foreach atom $periodic_table_list {
    #
    # Extract the information from the periodic_table_list
    #
    set atomic_number [lindex $atom 0]
    set atom_list [lindex $atom 1]
    set element_name [lindex $atom_list 0]
    set element_symbol [lindex $atom_list 1]
    set atomic_weight [lindex $atom_list 2]
    set electrons [lindex $atom_list 3]
}

This code allows you to iterate through the Tcl list periodic_table_list and extract individual list elements. You may encounter this brute force method of data extraction if you are reading in information from a database. Now that we have the data we need we can format it into a meaningful data structure in order to process it.

The Tclx Keyed List

As you have seen simple Tcl lists are easy to manipulate. However, complex lists containing nested list elements can become a bit tedious to process. Wouldn't it be nice to have the scripting flexibility of C language-like structs to use with Tcl lists? Extended Tcl (Tclx) provides the keyed lists data structure. A keyed list is almost synonymous to the C language struct. The keyed list is a structured data type. As an example, we have the following keyed list:

Francium
	symbol			Fr
	atomic_weight		223
	electrons			2-8-18-32-18-8-1
Radium
        symbol 		    	Ra
	atomic_weight		226
	electrons			2-8-18-32-18-8-2

The Tclx keyed list allows us to build records precisely in this manner. A keyed list consists of value pairs. The name of our keyed list is periodic_table_info. The keys in the periodic_table_info are the names of the elements in the periodic table, such as Francium and Radium in our example keyed list. Within each key we may store other lists or associate common groupings of data as we would with the C struct. In our example, each key in the list also has paired values, such as, atomic symbol, atomic weight and the element's electrons.

From the Tclx perspective our keyed list has the following form.

{Francium {{symbol Fr} {atomic_weight 223} {electrons 2-8-18-32-18-8-1}}}

{Radium {{symbol Ra} {atomic_weight 226} {electrons 2-8-18-32-18-8-2}}}

The keys, Francium and Radium each are paired with their own elements. The elements themselves are lists.

Francium
 
{{symbol Fr} {atomic_weight 223} {electrons 2-8-18-32-18-8-1}}

Radium 

{{symbol Ra} {atomic_weight 226} {electrons 2-8-18-32-18-8-2}}

The fields of the key value pairs are symbol, atomic weight,and electrons.

Extended Tcl

Now using our tools we developed earlier we would like to build an extended Tcl keyed list. We will take the list periodic_table_list and use this data to build a keyed list, periodic_table_info. To do so we will need to use components of Extended Tcl, commonly known as TclX. Extended Tcl provides additional tools originally not present in Tcl. These extensions include the keyed lists and debugging tools. Most Tcl installs now include TclX but you may need to load it.

% package require Tclx
8.4

Using keylset by Example

It's time to return to the keyboard and play around with some examples. We're going to construct a simple keyed list. We'll name the keyed list, periodic_table_info. Building on our previous example with element 293 and its data still in our interpreter, we'll create a keyed list using the Extended Tcl command, keylset.

syntax: keylset list key value

We're going to modify Listing 3 by first adding the field symbol and its value, see Listing 5. Remember to add the package require Tclx statement to you code. To setup for this example I merely leveraged the data from the Tcl list, periodic_table_list,in the previous example. Using the earlier example we extracted values from the list. The Tcl variables element_symbol, atomic_weight and electrons were set with values. Now to build a keyed list we will insert the following portion of code:

keylset periodic_table $element_name.symbol $element_symbol
puts $periodic_table
{Ununoctium {{symbol Uuo}}}

Inserting data elements step by step, next we add the field atomic_weight and its value,

keylset periodic_table $element_name.atomic_weight \
$atomic_weight
puts $periodic_table
{Ununoctium {{symbol Uuo} {atomic_weight 293}}}

And last we add the field electrons and its values.

keylset periodic_table $element_name.electrons \
$electrons
puts $periodic_table
{Ununoctium {{symbol Uuo} {atomic_weight 293} {electrons 2-8-18-32-32-18-8}}}

Building the Keyed List periodic_table_info

We covered all the ground we need to build a keyed list. Now let's put all the concepts together and build the keyed list , periodic_table_info. We need to iterate through the list elements of periodic_table_list. We accomplished this earlier using the Tcl command, foreach. Using the Tcl command, lindex, we previously retrieved individual elements of the list, periodic_table_list. Last we used the Tclx command, keylset, to set the values of each field in the keyed list. Now to iterate through the elements of periodic_table_list and build a keyed list, we use the following code from Listing 6:

foreach atom $periodic_table_list {
    #
    # Extract the information for the keyed list
    #
    set atomic_number [lindex $atom 0]
    set atom_list [lindex $atom 1]
    set element_name [lindex $atom_list 0]
    set element_symbol [lindex $atom_list 1]
    set atomic_weight [lindex $atom_list 2]
    set electrons [lindex $atom_list 3]

    #
    # Build the keyed list 
    #
    keylset periodic_table_info $element_name.symbol \
	$element_symbol
    keylset periodic_table_info \
	$element_name.atomic_weight $atomic_weight
    keylset periodic_table_info $element_name.electrons \
	$electrons
}

Go ahead and cut and paste this code into the interpreter if you have been following along. If you're curious take a look at the keyed list.

puts $periodic_table_info
{Francium {{symbol Fr} {atomic_weight 223} {electrons 2-8-18-32-18-8-1}}} {Radium {{symbol Ra} {atomic_weight 226} {electrons 2-8-18-32-18-8-2}}} {Rutherfordium {{symbol RF} {atomic_weight 261} {electrons 2-8-18-32-32-10-2}}} {Dubnium {{symbol Db} {atomic_weight 262} {electrons 2-8-18-32-32-11-2}}} {Seaborgium {{symbol Sg} {atomic_weight 263} {electrons 2-8-18-32-32-12-2}}} {Bohrium {{symbol Bh} {atomic_weight 262} {electrons 2-8-18-32-32-13-2}}} {Hassium {{symbol Hs} {atomic_weight 265} {electrons 2-8-18-32-32-14-2}}} {Meitnerium {{symbol Mt} {atomic_weight 266} {electrons 2-8-18-32-32-15-2}}} {Ununnilium {{symbol Uun} {atomic_weight 269} {electrons 2-8-18-32-32-17-1}}} {Unununium {{symbol Uuu} {atomic_weight 272} {electrons 2-8-18-32-32-18-1}}} {Ununbium {{symbol Uub} {atomic_weight 277} {electrons 2-8-18-32-32-18-2}}} {Ununtrium {{symbol Uut} {atomic_weight undiscovered} {electrons undiscovered}}} {Ununquadium {{symbol Uuq} {atomic_weight 285} {electrons 2-8-18-32-32-18-4}}} {Ununpentium {{symbol Uup} {atomic_weight undiscovered} {electrons undiscovered}}} {Ununhexium {{symbol Uuh} {atomic_weight 289} {electrons 2-8-18-32-32-18-6}}} {Ununseptium {{symbol Uus} {atomic_weight undiscovered} {electrons undiscovered}}} {Ununoctium {{symbol Uuo} {atomic_weight 293} {electrons 2-8-18-32-32-18-8}}}

Retrieving Data the Keyed List

The keyed list is data structure consisting of keys and fields. We can retrieve the keys of a keyed list using the Tclx command, keylkeys. This returns a Tcl list of keys from our keyed list.

syntax: keylkeys keyed_list_name

Following along at your keyboard. Try this:

keylkeys periodic_table_info

You'll see these results. The command keylkeys returned the following list:

Francium Radium Rutherfordium Dubnium Seaborgium Bohrium Hassium Meitnerium Ununnilium Unununium Ununbium Ununtrium Ununquadium Ununpentium Ununhexium Ununseptium Ununoctium

You can examine what fields a keyed list contains by keylkeys and specifying a key. As an example:

syntax: keylkeys keyed_list_name key

keylkeys periodic_table_info Rutherfordium

Returns the following results.

symbol atomic_weight electrons

As we expected, these are the fields of our keyed list, periodic_table_info.

Retrieving Data from the Keyed List Fields

To make the keyed list useful we'll need a way to extract elements from individual fields of the keyed list. The Tclx command, keylget, provides this functionality.

syntax: keylget keyed_list key

set atom_key Rutherfordium
keylget periodic_table_info $atom_key.atomic_weight

Returns the value of 261. Which is the correct atomic weight of Rutherfordium based on our data.

Putting all together, let's add some code to Listing 6 to retrieve all the values we stored in the keylset example. Listing 7 does just this:

foreach atomic_elem [keylkeys periodic_table_info] {
    set atomic_symb [keylget periodic_table_info $atomic_elem.symbol]
    set atomic_weight [keylget periodic_table_info $atomic_elem.atomic_weight]
    set electrons [keylget periodic_table_info $atomic_elem.electrons]
    puts "$atomic_elem $atomic_symb $atomic_weight $electrons"
}

That's it! We now have a powerful means to create and retrieve keyed values in our periodic table database. Now you can go experiment and win your Nobel prize for discovering the anti-gravity properties in Ununpentium.

The keyed list data structure adds the unique functionality you require for creating complex Tcl programs. Common applications you'll use this for are data base updating and querying.

You may be working on a computer graphics program that requires structured data, as an example, a list of vertices in a polygon. Associated with the vertices may be nodes to form a complex 3D shape. You could use keyed lists to store results of your experiment, such as our example periodic_table_info. Keyed lists extend the flexibility of an already powerful scripting tool.

I am thankful for all the hard work the Mac Tcl Sourceforge developers put into making this package available on the Macintosh, and in Aqua!

Michael J. Norton is a software engineer at Cisco Systems.


Return to the Mac DevCenter.