Perl Part I · Module 6 · Hashes
keys, values and each
Having read one key at a time, the next thing you want is all of them. Two functions give you that: one returns every key as a list and one returns every value. A third walks the pairs one at a time, and it is the one worth being careful with — because it does not return a list, it returns the next pair, which means it has to remember where it got to. That memory belongs to the hash rather than to your loop, so it is shared by every piece of code that walks the same hash. Break out of such a loop early and the marker stays where you left it, so the next walk over that hash starts partway through and quietly visits fewer keys than exist.
Foundation14 min readPerlPart IFundamentalsHashesDebugging
Part I · Module 6 · Chapter 6.5 · keys, values and each
1. keys and values
use strict;
use warnings;
my %marks = (maths => 72, physics => 65, chemistry => 90);
printf "keys : %s\n", join(" ", sort keys %marks);
printf "values : %s\n", join(" ", sort { $a <=> $b } values %marks);
printf "count : %d\n", scalar(keys %marks);keys : chemistry maths physics
values : 65 72 90
count : 3keys %hreturns every key as a list.values %hreturns every value as a list.scalar(keys %h)is the key count, as Chapter 6.1 §4 established.
Both return a list, so everything from Module 5 applies: you can sort them, grep them, map over them, count them, or assign them to an array.
Note that both outputs above are sorted. That is not decoration — keys returns the keys in an order you do not control and which is not stable between runs. Chapter 6.6 is entirely about that, and until then every example in this chapter sorts what it prints.
The standard loop
use strict;
use warnings;
my %marks = (maths => 72, physics => 65, chemistry => 90);
for my $subject (sort keys %marks) {
printf "%-10s %3d\n", $subject, $marks{$subject};
}chemistry 90
maths 72
physics 65for my $k (sort keys %h) is the loop to learn. It gives you the key, from which you get the value with $h{$k}, in an order that is the same every run.
This is the form you will write ninety per cent of the time. The rest of the chapter is the other two functions and why they are the other ten.
2. values Are Aliases
use strict;
use warnings;
my %h = (a => 1, b => 2);
for my $v (values %h) {
$v *= 10;
}
printf "after the loop: %s\n", join(" ", map { "$_=$h{$_}" } sort keys %h);after the loop: a=10 b=20The hash changed. Multiplying the loop variable multiplied the values in %h, with no warning.
values does not return copies — it returns aliases to the values stored in the hash. This is the same alias behaviour Chapter 5.10 §4 measured for $_ inside map, and the same thing a foreach loop variable does over an array.
3. each Returns One Pair at a Time
use strict;
use warnings;
my %marks = (maths => 72, physics => 65, chemistry => 90);
while (my ($subject, $mark) = each %marks) {
printf "%-10s %3d\n", $subject, $mark;
}chemistry 90
physics 65
maths 72Running that script twice produced two different orderings on the same machine with the same input — which is not a quirk of each but a property of the hash, and is Chapter 6.6's subject.
each %h returns the next key/value pair as a two-element list, and an empty list when it runs out — which is what ends the while loop, by Chapter 5.7's emptiness rule.
Its appeal is that it does not build a list of all the keys first, which matters for a hash with millions of entries. For the hashes in a typical flow script, that saving is irrelevant.
Its cost is the hidden state, which is the rest of this chapter.
4. The Hidden Iterator Is Shared With the Hash
each has to remember where it got to between calls. That marker lives in the hash, not in your loop — so every piece of code walking that hash shares one marker.
Breaking out of a loop leaves it mid-way
use strict;
use warnings;
my %h = (a => 1, b => 2, c => 3);
my $first = 0;
while (my ($k) = each %h) {
$first++;
last if $first == 1;
}
my $second = 0;
while (my ($k) = each %h) {
$second++;
}
printf "first loop saw : %d\n", $first;
printf "second loop saw : %d (the hash has %d keys)\n", $second, scalar(keys %h);first loop saw : 1
second loop saw : 2 (the hash has 3 keys)The second loop saw two keys, not three.
The first loop consumed one pair and then last jumped out, leaving the marker pointing at the second entry. The second loop resumed from there, visited the remaining two, and stopped — believing it had seen the whole hash.
Nothing warns. The second loop processed two thirds of the data and reported success.
keys resets the marker
use strict;
use warnings;
my %h = (a => 1, b => 2, c => 3);
my ($k1) = each %h;
keys %h; # called purely for the reset
my ($k2) = each %h;
printf "first each : %s\n", $k1;
printf "second each : %s\n", $k2;
printf "same key : %s\n", ($k1 eq $k2 ? "yes — the iterator was reset" : "no");first each : b
second each : b
same key : yes — the iterator was resetCalling keys %h — even discarding the result — resets the marker to the beginning. So the second each returned the same key as the first.
Note which key that was: b, not a. A second run of the identical script prints a different letter, because the key each reaches first is whichever one the hash happens to put first — and that is not stable between runs. The comparison is the stable part of this output; the key itself is not. Chapter 6.6 is the chapter on exactly that, and this example is a small demonstration of why it needs one.
That is the documented way to reset it, and the fact that a function called for its side effect is the official fix is itself an argument for not getting into the situation.
Two loops over the same hash at once do not work
use strict;
use warnings;
my %h = (a => 1, b => 2, c => 3);
my $outer = 0;
while (my ($k) = each %h) {
$outer++;
last if $outer > 10; # a guard, not part of the logic
my $inner = 0;
while (my ($k2) = each %h) {
$inner++;
last if $inner > 10;
}
}
printf "outer iterations: %d (the hash has %d keys)\n", $outer, scalar(keys %h);outer iterations: 11 (the hash has 3 keys)Eleven, on a three-key hash — and it only stopped because of the guard.
The inner loop shares the outer loop's marker, so each time it runs it consumes the remaining entries and then the exhausted marker resets, letting the outer loop start again. Without last if $outer > 10 this does not terminate.
A nested walk over one hash is a real thing to want — comparing every entry against every other — and each cannot do it. keys can, because each loop gets its own list.
5. Choosing Between Them
| You want | Use |
|---|---|
| every key, in a stable order | for my $k (sort keys %h) |
| the key count | scalar(keys %h) |
| every value, read only | for my $v (values %h) |
| to transform every value in place | for my $v (values %h) { ... } — deliberately |
| a filtered or sorted subset | grep/sort over keys %h |
| one pair at a time from a very large hash | each, with no early exit |
use strict;
use warnings;
my %errors = (alu => 0, fifo => 7, decoder => 2, regfile => 0);
printf "blocks tracked : %d\n", scalar(keys %errors);
printf "total errors : %d\n", eval { my $t = 0; $t += $_ for values %errors; $t };
printf "clean blocks : %s\n", join(" ", sort grep { $errors{$_} == 0 } keys %errors);
printf "worst first : %s\n", join(" ", sort { $errors{$b} <=> $errors{$a} or $a cmp $b } keys %errors);blocks tracked : 4
total errors : 9
clean blocks : alu regfile
worst first : fifo decoder alu regfileFour different questions, all answered through keys or values and Module 5's list functions:
- The count is
scalar(keys %h). - The total sums
values %h. - The filter is
grepoverkeys, comparing with==because the values are counts — Chapter 3.5. - The ordering is a comparator that looks the value up by key, with a tie-break so the result is reproducible — Chapter 5.9 §5.
The eval { ... } on the total line is only there to fit a multi-statement sum into a printf argument; in real code that is a two-line loop.
6. Why This Matters in Engineering Work
for my $k (sort keys %h) is the reporting loop. Every per-block, per-test, per-corner summary is this shape, and the sort is what makes today's report comparable with yesterday's — Chapter 6.6 is the chapter on why.
Summing or averaging values is values. Total errors, total cycles, mean utilisation. No key needed, so no loop over keys.
The alias behaviour is a tool and a trap. Trimming or scaling every value in place through values is the idiomatic form and worth knowing. Doing it accidentally — in a loop you thought was read-only — silently rewrites your data, and use warnings says nothing.
And each is the one function in this module to be wary of. The failure mode is a loop that processes part of a hash and reports success, caused by an early exit in an unrelated loop somewhere else. On a hash small enough to fit in a report — which is all of them in practice — keys costs nothing and has no shared state.
7. Common Mistakes
Printing keys %h unsorted. The order is not stable between runs — Chapter 6.6.
Modifying the loop variable in a values loop by accident. It rewrites the hash.
Expecting keys to alias like values. Assigning to a key loop variable changes nothing in the hash.
Using last inside an each loop. The marker stays put and truncates the next walk over that hash.
Nesting two each loops over one hash. They share the marker and the outer loop does not terminate.
Calling each on a hash someone else may also be walking. The state is in the hash, not in your code.
Reaching for each for efficiency on a small hash. The saving is nil and the risk is not.
8. Exercise
Exercise 1 — the three functions. For a four-key hash, print the sorted keys, the sorted values and the key count.
Exercise 2 — the standard loop. Write for my $k (sort keys %h) over a hash of blocks and error counts, printing an aligned two-column table.
Exercise 3 — the alias. Multiply every value by 10 through a values loop, then print the hash. Confirm it changed and that STDERR is empty. Then do it again via a copied array and confirm the hash is untouched.
Exercise 4 — keys do not alias. In a for my $k (keys %h) loop, assign a new string to $k. Print the hash's keys afterwards and say what changed.
Exercise 5 — the left-behind iterator. Reproduce §4's first script. Report how many keys each loop saw and the hash's real key count. Then insert keys %h; between the two loops and report the second count again.
Exercise 6 — the interleaved loops. Run §4's nested script. Report the outer iteration count against the key count, then rewrite it with keys so a nested walk works correctly.
Exercise 7 — four questions. For a hash of five blocks and error counts, produce the count, the total, the list of clean blocks and the worst-first ordering — each in one statement, each using keys or values.
9. Summary
keys %handvalues %heach return a list, so Module 5's list functions all apply.scalar(keys %h)is the key count.for my $k (sort keys %h)is the loop to write — stable order, no shared state.valuesreturns aliases, so assigning to the loop variable rewrites the hash. That is the idiomatic in-place transform and an easy accident.keysdoes not alias; a key is not a writable location.eachreturns one pair at a time and keeps its marker inside the hash.- Breaking out of an
eachloop truncates the next one — two of three keys, silently. - Two
eachloops over one hash share the marker and the outer one does not terminate. keys %hresets the marker, and needing a side-effect call is itself a reason to preferkeys.
Chapter 6.6 explains why every example in this chapter sorted its output: hash order is not merely unspecified, it genuinely differs from one run of the same script to the next.
Where this fits
Part of the Perl curriculum.
