Tcl Part I · Module 9 · Loops and Iteration
foreach With Several Variables per Pass
A great deal of data in Tcl arrives as a flat list in which the elements alternate — a key, then its value, then the next key. Dictionaries are exactly that, array get produces exactly that, and so does a lot of tool output. You can convert such a list into something structured before walking it, and often you do not need to: foreach will take two, three or any number of elements per pass and bind them to separate variables. The syntax for that sits one brace away from the parallel-lists syntax of the previous chapter, the two are easy to confuse, and this chapter puts them side by side so you do not.
Foundation10 min readTclPart ILoopsforeachKey-value data
Part I · Module 9 · Chapter 9.3 · foreach With Several Variables per Pass
1. Two at a time
Put several variable names in one brace group:
foreach {key value} {mode fast runs 3 width 8} {
puts "$key -> $value"
}mode -> fast
runs -> 3
width -> 8Six elements, three passes, two variables bound on each. The brace group is a list of variable names, so {key value} means "take two per pass".
Any group size works:
foreach {name dir width} {clk in 1 rst in 1 data out 32} {
puts [format "%-6s %-4s %s" $name $dir $width]
}clk in 1
rst in 1
data out 322. This is how you walk key-value data in place
Chapter 7.1 established that a dictionary is a flat list of alternating keys and values. So this works directly:
set cfg [dict create mode fast runs 3]
foreach {k v} $cfg {
puts "$k = $v"
}mode = fast
runs = 3No conversion step. The same applies to array get output from Chapter 7.9:
array set limits {retries 3 timeout 60}
foreach {k v} [array get limits] {
puts "$k = $v"
}retries = 3
timeout = 60(The order is the array's, which Chapter 7.10 showed is not defined — sort the keys if the order matters.)
3. When the list does not divide evenly
The same padding rule as Chapter 9.2, now inside a single list:
foreach {k v} {a 1 b} {
puts "k='$k' v='$v'"
}k='a' v='1'
k='b' v=''Two passes. The list had three elements, the last group was short, and v was padded with an empty string. No error.
With a group of three:
foreach {x y z} {1 2 3 4 5} {
puts "x=$x y=$y z='$z'"
}x=1 y=2 z='3'
x=4 y=5 z=''4. The collision worth knowing
These two lines look alike and mean entirely different things:
foreach {a b} $one { ... } ;# several variables, ONE list
foreach a $one b $two { ... } ;# one variable each, TWO listsSame data, both forms, side by side:
set flat {1 2 3 4}
puts "grouped — one list, two per pass:"
foreach {p q} $flat {
puts " p=$p q=$q"
}
puts "parallel — two lists, one each per pass:"
foreach p {1 2} q {3 4} {
puts " p=$p q=$q"
}grouped — one list, two per pass:
p=1 q=2
p=3 q=4
parallel — two lists, one each per pass:
p=1 q=3
p=2 q=4Both produced two passes and two variables, and the pairings are completely different. The distinguishing feature is the braces: one brace group means grouping; separate name-and-list pairs mean parallel.
You can combine them, though it is rarely worth the reader's effort:
foreach {a b} {1 2 3 4} {c d} {5 6 7 8} {
puts "$a $b | $c $d"
}1 2 | 5 6
3 4 | 7 85. A worked example
Reading a flat settings list, applying defaults, and reporting:
set raw {mode fast runs 3 width 8 mode deep}
if {[llength $raw] % 2 != 0} {
error "settings list must have an even number of elements"
}
set settings [dict create runs 1 verbose 0]
foreach {k v} $raw {
dict set settings $k $v
}
foreach k [lsort [dict keys $settings]] {
puts [format " %-8s %s" $k [dict get $settings $k]]
} mode deep
runs 3
verbose 0
width 8Note mode appears twice in the input and the later value won, because each pass overwrites. That is usually what you want from a settings list, and it is worth knowing that it happens silently — a duplicated key is not an error here.
6. Grouping with lassign instead
For a list of records rather than a flat run, Chapter 6.14 is the better fit:
# flat: foreach groups it
foreach {n w} {clk 1 rst 1} { puts "$n $w" }
# records: foreach + lassign
foreach rec {{clk 1} {rst 1}} {
lassign $rec n w
puts "$n $w"
}clk 1
rst 1
clk 1
rst 1Same output, different input shapes. Records are more robust — a missing field cannot shift everything after it, which is exactly what happens to a flat list when one element goes missing.
7. Exercises
Exercise 1. How many passes does foreach {a b c} {1 2 3 4 5 6 7} {...} make, and what are the variables on the last one?
Exercise 2. Write a loop printing key=value for the flat list {x 1 y 2 z 3}.
Exercise 3. Explain the difference between foreach {a b} $l {...} and foreach a $l1 b $l2 {...}.
Exercise 4. Given set flat {1 2 3 4}, what does each print?
foreach {p q} $flat { puts "$p-$q" }
foreach p {1 2} q {3 4} { puts "$p-$q" }Exercise 5. Add a guard rejecting a flat list whose length is not a multiple of three.
Exercise 6. When would you use dict for rather than foreach {k v} over the same dictionary?
Answers
1. Three passes. Seven elements with a group of three gives two full groups and a short one, so the last pass has a=7, b empty and c empty.
2.
foreach {key value} {x 1 y 2 z 3} {
puts "$key=$value"
}x=1
y=2
z=33. The first takes two elements per pass from one list. The second takes one element per pass from each of two lists. The braces are what distinguish them.
4. The first prints 1-2 then 3-4; the second prints 1-3 then 2-4.
5.
proc triples {l} {
if {[llength $l] % 3 != 0} {
error "expected triples, got [llength $l] elements"
}
foreach {a b c} $l { puts "$a $b $c" }
}
triples {1 2 3 4 5 6}
if {[catch {triples {1 2 3 4}} m]} { puts "rejected: $m" }1 2 3
4 5 6
rejected: expected triples, got 4 elements6. Whenever the value really is a dictionary. dict for states the intent, and it validates the structure — a malformed dictionary is reported rather than quietly mis-paired. foreach {k v} is for flat lists that are not dictionaries.
8. Remember
foreach {a b} $list {body}takes several elements per pass from one list; the brace group is a list of variable names.- Any group size works — two, three or more.
- A dictionary is a flat list, so
foreach {k v} $dictwalks it directly, as doesforeach {k v} [array get a]. - Prefer
dict forfor real dictionaries — it states the intent and validates the structure. - A short final group is padded with empty strings, silently. Check
llength % nfirst when complete records are expected. - One brace group means grouping; separate name-and-list pairs mean parallel iteration. They look alike and pair the data differently.
- Duplicate keys in a flat settings list silently overwrite, last one winning.
- For records rather than a flat run, use
foreachpluslassign— a missing field cannot shift the rest.
Where this fits
Part of the Tcl curriculum.
