| You are not logged in Either you are not a registered user, or your previous session has timed out. |
|||||||
| Home | Jokes | Media | Photo | Travel | Community | Programs | Resume |
| Programs Submenu: Tcl Tips | Projects  | |||||||
This is another element in the heart and soul of Tcl programming, and probably one of the best features of tcl. I won't go over the list commands here, but I will show a few tricks I use all the time.
In newer versions of tcl (8.0 and above I believe) an interesting feature was added to foreach. The old foreach syntax was
foreach var $list {
commands
}
and that was pretty much it.
In tcl 8.0, two new syntaxes were released. I'll go over each one by itself, and then show how they can be mixed and matched.
The first is, you can specify multiple list variables, like this:
foreach {var1 var2} $list {
commands
}
This is going to go through $list, assigning the first element to var1 and the second to var2, then the 3rd to var1 and the 4rth to var2, etc. This is very useful for the following circustance.
set mylist {
"Example title 1" "data 1"
"Example title 2" "data 2"
"Example title 3" "data 3"
}
foreach {title data} $mylist {
puts "$title: $data"
}
Try it. Nothing really astonishing. But it does the same thing, and is simpler and more efficient than:
set mylist {
"Example title 1" "data 1"
"Example title 2" "data 2"
"Example title 3" "data 3"
}
for { set x 0 } { $x < [llength $mylist] } {incr x 2} {
set title [lindex $mylist $x]
set data [lindex $mylist [expr $x+1]]
puts "$title: $data"
}
The next new addition was this:
foreach var1 $list1 var2 $list2 ... varn $listn {
commands
}
This basically lets you step through a set of lists, using the first item in each list, then the second item in each list, then the third, etc. This is equivalent to
for { set a 0 } { $a < [llength $list1] } { incr a } {
set var1 [lindex $list1 $a]
set var2 [lindex $list2 $a]
...
set varn [lindex $listn $a]
commands
}
Not only is the "new" foreach easier to code and understand, but it's faster than the lindex method.
You can, of course, combine the two of them, although I rarely do.
foreach {var1 var2} $lista {var3 var4} $listb {
commands
}
This is a really handy operation. Say you've got a list called $list and you'd like to assign
the first element of the list to $a, the second element to $b and the third element
to $c. The rest, you'd like to just ignore. I used to have a custom tcl proc that did this, but
then Will Webb showed me this little gem, which is clean, correct, and
faster than anything:
foreach {a b c} $list { break }
If you look above to the foreach usage, you can see that this is going to step through $list and assign the first three elements to $a, $b, $c. Then, it's going to assign the next three elements to $a, $b, $c. But wait! There's a break in the foreach body! Which means it's just going to go through ONE TIME and then quit. After the loop exits, you'll have the desired result. Try it! This is really handy for parsing some kinds of strings. Say for example that you've got the current time in a string like "12:31:13", which is 12 hours, 31 minutes and 13 seconds.
You'd like to have hours, minutes and seconds in seperate variables so you can fiddle with them. You know you would.
foreach {hours minutes seconds} [split $time :] {break}
And that's it! You're done.
If you have a list that has duplicate elements, and you want to remove those, here is a simple trick. Note: list order will NOT be retained!
foreach l $list {
set a($l) 1
}
set list [ array names a ]
What basically happens here is we make an array entry for each element in the list. Since there can only be one entry for any given key in an array, we end up with an array that has one entry for each unique item in the list. "array names a" returns a list of all the keys, which is our uniqued list.
Say you want something like the above, but you want to know how many times each item appeared in the original list. We can make a few modifications to the above to handle that.
foreach l $list {
if ![ info exists a($l) ] { set a($l) 0
incr set a($l)
}
foreach i [ array names a ] {
lappend newlist [ list $i $a($i) ]
}
This does the same thing as the uniqeing scrap, but every time it sees an item it's previously seen in the list, it increments a counter. At the end, a "frequency list" is made. Here's an example:
input { This is a list with a multiple word }
output { {with 1} {This 1} {list 1} {is 1} {a 2} {word 1} {multiple 1} }
Depending on how you wish to use this data, the values in the array "a" may be more useful than the output list.
lsort is a terribly useful command. By now you've probably noticed that you can sort in ascending and descending order, or sort numerically, etc. But you've probably had a few occaisons where you've wanted something more, wanted to be able to sort by a more esoteric criteria.
You can actually create your own sort criteria using the -command option to lsort. -command takes as an argument the name of a procedure. The procedure must take two arguments and return a number. The number is less than 0 if the first argument is less than the second, greater than 0 if it's greater, and 0 if they are lexigraphically identical.
Here's an example: Say you'd like to sort the elements in an array by the values, rather than the keys. As an example, consider the array
array set test {
a 15
b 90
c 111
d 6
}
You'd like to get a list of the keys (a, b, c, d) in order by their values (15, 90, 111, 6). In this example, we're obviously looking for the order to be "d a b c", right?
Well, consider this command:
proc ArrayValueSort { arrayname a b } {
upvar $arrayname array
if { $array($a) == $array($b) } {
return 0
} elseif { $array($a) < $array($b) } {
return -1
} else {
return 1
}
}
Now, let's try it:
% puts [lsort -command "ArrayValueSort test" [array names test] ] d a b c
I've done something a litte funny here... ArrayValueSort actually takes 3 arguments, not 2. Why is this? Well, the first argument is used to pass in the name of the array you're looking at. You'll notice that I hardcoded the array name into the lsort invocation. lsort actually just appends the two elements onto the end of the proc you've passed to the -command option, so it comes out as
ArrayValueSort test $elem1 $elem2
There's a lot of stuff like that in tcl... where you provide a little line of code and tcl appends a few extra elements onto the end of it. For example, the argument to trace variable commands, or the command used with fileevent.
| This document last modified: Friday, November 05, 2004 | me@rustybrooks.com |