Skip to content
VLSI Mentor

Tcl Part I · Module 7 · Dictionaries and Arrays

dict for: Iterating Key and Value Together

The last chapter ended with a loop over the keys that had to look each value up again inside the body. That works and it is one step longer than it needs to be. dict for hands you both halves of every pair at once, in the order you built the dictionary, and it reads exactly like what it does. Three things about it are worth knowing before you rely on it: it insists on exactly two variable names, it takes a value where you may expect a name, and it walks a snapshot rather than a live view — which is the difference between a loop that terminates and one that does not.

Foundation11 min readTclPart IDictionariesdict forIteration

Part I · Module 7 · Chapter 7.5 · dict for: Iterating Key and Value Together

1. The shape

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set cfg [dict create mode fast runs 3 width 8]

dict for {key value} $cfg {
    puts "$key = $value"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode = fast
runs = 3
width = 8

Three parts: a two-element list of variable names, the dictionary, and a body. On each pass Tcl sets both variables and runs the body.

Compare it with the version from Chapter 7.4 §7:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# the two-step way
foreach key [dict keys $cfg] {
    puts "$key = [dict get $cfg $key]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode = fast
runs = 3
width = 8

Same output. dict for says it in one step, and it does not look the key up a second time.

2. Exactly two names

Not one, not three:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {key} $cfg { puts $key }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
must have exactly two variable names
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {key value extra} $cfg { puts $key }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
must have exactly two variable names

This differs from foreach, which happily takes any number of variables and strides through the list in groups. dict for is about pairs, so it is always two.

3. It takes a value

Like dict get and unlike dict set, the second argument is the dictionary itself:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {k v} $cfg { ... }     ;# correct — a value
dict for {k v} cfg  { ... }     ;# wrong  — a name

The wrong form produces a message that does not obviously point at the mistake:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set cfg [dict create mode fast]
dict for {k v} cfg { puts $k }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
missing value to go with key

That reads like a complaint about your data. It is not. Tcl took the literal word cfg as the dictionary, and a single word is an odd number of elements, so it is not a valid dictionary. The error is correct and the cause is the missing $.

4. The order is guaranteed

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create]
foreach k {zebra apple mango} {
    dict set d $k 1
}

dict for {k v} $d { puts -nonewline "$k " }
puts ""
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
zebra apple mango

Insertion order, as Chapter 7.2 promised. This is worth more than it sounds: it means a report generated by dict for comes out in an order you controlled, and it means two runs over the same dictionary produce identical output. Neither is true of arrays — Chapter 7.10 shows the contrast.

5. break, continue and return

All three behave the way they do in foreach:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create a 1 b 2 c 3 d 4]

dict for {k v} $d {
    if {$v == 3} break
    puts "saw $k"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
saw a
saw b
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {k v} $d {
    if {$v % 2} continue
    puts "even: $k = $v"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
even: b = 2
even: d = 4

And return leaves the enclosing procedure, which makes a search read nicely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc key_for {d want} {
    dict for {k v} $d {
        if {$v eq $want} { return $k }
    }
    return ""
}

puts [key_for [dict create a 1 b 2 c 3] 2]
puts "[key_for [dict create a 1] 9]<- not found"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
b
<- not found

6. It walks a snapshot

This is the behaviour worth knowing before you need it. dict for takes a value — so it iterates the dictionary as it was when the loop started, even if the variable changes underneath:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set live [dict create a 1 b 2]

dict for {k v} $live {
    dict set live zzz 9
    puts "iterating $k (size is now [dict size $live])"
}

puts "after: $live"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
iterating a (size is now 3)
iterating b (size is now 3)
after: a 1 b 2 zzz 9

The loop ran exactly twice. The key added inside the body really was added — the final line proves it — and the loop never saw it. This is a direct consequence of dict for taking $live rather than live: the value was handed over once, at the start.

That is the behaviour you want. A loop that grew while you fed it would never finish.

7. The loop variables outlive the loop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create a 1 b 2]
dict for {k v} $d { }
puts "k = $k, v = $v"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
k = b, v = 2

They hold whatever the last iteration left. This is the same as foreach and it is occasionally useful, but relying on it makes code fragile — an empty dictionary leaves them untouched or unset, and the loop then silently reuses a stale value.

8. A worked example

A report with a total and an aligned layout:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set runs [dict create smoke 120 regress 4500 nightly 18000 lint 60]

set total 0
puts [format "%-10s %8s" "RUN" "CYCLES"]
dict for {name cycles} $runs {
    puts [format "%-10s %8d" $name $cycles]
    incr total $cycles
}
puts [format "%-10s %8d" "TOTAL" $total]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
RUN          CYCLES
smoke           120
regress        4500
nightly       18000
lint             60
TOTAL         22680

The rows came out in the order the dictionary was built, which is the order a reader expects. Add lsort on the keys if you want alphabetical instead — but then you are back to foreach and a dict get, which is a fair trade and worth seeing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach name [lsort [dict keys $runs]] {
    puts [format "%-10s %8d" $name [dict get $runs $name]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
lint             60
nightly       18000
regress        4500
smoke           120

9. Exercises

Exercise 1. What error does this produce, and why?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {k} [dict create a 1] { puts $k }

Exercise 2. Write a loop that prints only the pairs whose value is greater than 100, given set d [dict create a 5 b 500 c 50 d 5000].

Exercise 3. Explain why dict for {k v} cfg {...} gives missing value to go with key rather than something about variables.

Exercise 4. Predict the number of iterations:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create a 1 b 2]
set n 0
dict for {k v} $d { dict set d new$n 1 ; incr n }
puts $n

Exercise 5. Write invert, which takes a dictionary and returns a new one with keys and values swapped. Say what happens if two keys share a value.

Exercise 6. Rewrite this with dict for and say what improves:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach k [dict keys $cfg] {
    set v [dict get $cfg $k]
    puts "$k -> $v"
}

Answers

1. must have exactly two variable names. dict for iterates pairs, so the variable list is always two long — unlike foreach, which accepts any number.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set d [dict create a 5 b 500 c 50 d 5000]
dict for {k v} $d {
    if {$v > 100} { puts "$k = $v" }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
b = 500
d = 5000

3. Because the missing $ makes the literal word cfg the dictionary. One word is an odd number of elements, so it fails the dictionary-structure check before any key is ever looked at. The message is about the value you accidentally supplied.

4. 2. dict for was handed the value as it stood at the start, so the keys added during the loop are not visited — though they really are added to d.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc invert {d} {
    set out [dict create]
    dict for {k v} $d { dict set out $v $k }
    return $out
}
puts [invert [dict create a 1 b 2]]
puts [invert [dict create a 1 b 1]]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 a 2 b
1 b

If two keys share a value, the later one wins and the earlier is lost — the inverted dictionary has fewer pairs than the original.

6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
dict for {k v} $cfg {
    puts "$k -> $v"
}

It drops a line, removes a second lookup of a key you already have, and states that you want pairs rather than making a reader infer it.

10. Remember

  • dict for {key value} $d {body} hands you both halves of each pair, in insertion order.
  • Exactly two variable names — anything else gives must have exactly two variable names.
  • It takes a value, not a name. Forgetting the $ gives missing value to go with key, which is about the word you passed, not your data.
  • break, continue and return work normally.
  • It iterates a snapshot — keys added during the loop are added but never visited, so the loop always terminates.
  • To build a changed dictionary, write into a separate one. Clearer than mutating while looping.
  • The loop variables survive the loop, holding the last pair. Do not build on that.
  • Use foreach over lsort [dict keys $d] when you need an order other than insertion order.

Where this fits

Part of the Tcl curriculum.