Perl Part I · Module 6 · Hashes
Reading, Writing and Deleting Keys
Changing a hash is as simple as it looks: assign to a key to add or replace an entry, and call delete to take one out. What is worth the attention is what Perl does not tell you. Writing to a key that already has a value replaces it with no diagnostic, so a loop that builds a hash from rows containing a duplicate name ends up holding only the last of them and reports a count that looks right. Reading a key that was never set hands back undef rather than complaining, so a misspelled key behaves exactly like a key whose value happens to be missing. And there are two different ways to make a value go away, which leave the hash in two different states — a difference the next chapter is entirely about.
Foundation13 min readPerlPart IFundamentalsHashesDebugging
Part I · Module 6 · Chapter 6.3 · Reading, Writing and Deleting Keys
1. Add, Overwrite, Read, Remove
use strict;
use warnings;
my %result = (alu => "PASS", fifo => "FAIL");
$result{decoder} = "PASS";
$result{fifo} = "TIMEOUT";
my $removed = delete $result{alu};
printf "delete returned : %s\n", $removed;
printf "keys now : %s\n", join(" ", sort keys %result);
printf "fifo is now : %s\n", $result{fifo};delete returned : PASS
keys now : decoder fifo
fifo is now : TIMEOUTFour operations, and there is no special syntax for any of them:
- Add — assign to a key that does not exist.
$result{decoder} = "PASS"; - Overwrite — assign to a key that does. Same syntax.
- Read —
$result{fifo}. - Remove —
delete $result{alu}, which returns the value it removed.
Note that adding and overwriting look identical. Perl does not distinguish them and does not tell you which one happened, which is §3's subject.
delete returning the removed value is useful: you can log what you took out, or check it was what you expected, in the same statement.
2. Reading a Key That Was Never Set
Chapter 6.1 §5 measured this and it is worth restating because this is the chapter where you start writing keys from variables:
use strict;
use warnings;
my %result = (alu => "PASS");
for my $key ("alu", "aul", "ALU") {
my $v = $result{$key};
printf "%-4s -> [%s]\n", $key, (defined $v ? $v : "undef");
}alu -> [PASS]
aul -> [undef]
ALU -> [undef]One hit and two misses, with nothing on STDERR. A transposition and a case difference behave exactly as a legitimately absent key would.
There is no option to make a hash complain about an unknown key. The defences are the ones from earlier chapters, now applied to keys:
- Normalise the key on the way in — Chapter 4.6 §6's discipline. If keys are case-insensitive in your data,
lcthem once at the point you build the hash andlcthem once at the point you look up. - Chomp the line first — Chapter 4.9. A key built from an unchomped line carries a newline and will never match a literal.
- Test for the key rather than its value when absence is meaningful, which is Chapter 6.4's
exists.
use strict;
use warnings;
my %result;
for my $row ("ALU,PASS", "Fifo,FAIL") {
my ($name, $status) = split /,/, $row, -1;
$result{ lc $name } = $status;
}
for my $wanted ("alu", "FIFO", "Alu") {
my $v = $result{ lc $wanted };
printf "%-5s -> [%s]\n", $wanted, (defined $v ? $v : "undef");
}alu -> [PASS]
FIFO -> [FAIL]
Alu -> [PASS]Three different spellings, three hits. lc on both sides is all it took, and doing it at exactly two places — build and look up — is what makes it reliable.
3. Overwriting Is Silent, and That Loses Data
use strict;
use warnings;
my @rows = ("alu,PASS", "fifo,FAIL", "alu,TIMEOUT");
my %status;
for my $row (@rows) {
my ($name, $result) = split /,/, $row, -1;
$status{$name} = $result;
}
printf "rows read : %d\n", scalar(@rows);
printf "keys held : %d\n", scalar(keys %status);
printf "alu is : %s\n", $status{alu};rows read : 3
keys held : 2
alu is : TIMEOUTThree rows in, two keys out. The alu row appeared twice and the second assignment overwrote the first, so PASS is gone and only TIMEOUT remains.
Nothing warned. There is no "key already exists" diagnostic in Perl, because overwriting is the normal and intended behaviour of an assignment.
4. Two Ways to Make a Value Go Away
This section sets up the next chapter, and the measurement is the point.
use strict;
use warnings;
my %h = (a => 1, b => 2);
$h{a} = undef;
delete $h{b};
printf "keys remaining : [%s]\n", join(" ", sort keys %h);
printf "key count : %d\n", scalar(keys %h);
printf "a: exists %-3s defined %s\n", (exists $h{a} ? "yes" : "no"), (defined $h{a} ? "yes" : "no");
printf "b: exists %-3s defined %s\n", (exists $h{b} ? "yes" : "no"), (defined $h{b} ? "yes" : "no");keys remaining : [a]
key count : 1
a: exists yes defined no
b: exists no defined noTwo operations that both "remove the value", with two different results:
$h{a} = undefempties the value and keeps the key. The hash still hasain it;keysreports it; its value isundef.delete $h{b}removes the key and the value.keysno longer mentions it.
So keys counts one, not two, even though both values are now undef.
That distinction is not a curiosity. It is the difference between "this test ran and produced no result" and "this test was never run" — two states a report must not confuse. Which one your hash is in depends on which operation you used, and only exists can tell them apart afterwards.
Chapter 6.4 is the chapter about the three questions you can ask — was the key ever set, does it hold a real value, and is that value true — and why one if cannot answer them.
delete on a key that was never there
use strict;
use warnings;
my %h = (a => 1);
my $r = delete $h{zzz};
printf "returned : [%s]\n", (defined $r ? $r : "undef");
printf "key count : %d\n", scalar(keys %h);returned : [undef]
key count : 1undef back, hash unchanged, nothing reported. Deleting something that is not there is not an error — the same tolerance pop on an empty array showed in Chapter 5.4 §3.
That makes delete safe to call unconditionally, which is occasionally useful. It also means delete's return value cannot distinguish "removed a key whose value was undef" from "there was no such key" — both give undef.
5. Emptying a Whole Hash
use strict;
use warnings;
my %h = (a => 1, b => 2, c => 3);
printf "before : %d keys\n", scalar(keys %h);
%h = ();
printf "after : %d keys\n", scalar(keys %h);
printf "boolean: %s\n", (%h ? "non-empty" : "empty");before : 3 keys
after : 0 keys
boolean: empty%h = (); is the readable way to clear a hash, matching @a = (); from Chapter 5.2 §5.
Deleting keys one at a time in a loop also works and is what you want when the condition is selective:
use strict;
use warnings;
my %status = (alu => "PASS", fifo => "FAIL", decoder => "PASS");
for my $key (keys %status) {
delete $status{$key} if $status{$key} eq "PASS";
}
printf "remaining: %s\n", join(" ", sort keys %status);remaining: fifoNote that this deletes from %status while iterating over keys %status. That is safe because keys returns a complete list before the loop starts — it is not a live view of the hash. Chapter 6.5 covers that, and also covers the one iteration form where modifying the hash mid-loop is not safe.
Building a new hash with grep is the alternative, and is usually clearer when the condition is simple:
use strict;
use warnings;
my %status = (alu => "PASS", fifo => "FAIL", decoder => "PASS");
my %failed = map { $_ => $status{$_} } grep { $status{$_} ne "PASS" } keys %status;
printf "failed: %s\n", join(" ", sort keys %failed);failed: fifogrep and map are Chapter 5.10's, and the map { $_ => $status{$_} } form — producing two list items per key — relies on the flattening from Chapter 5.12. It is a dense line and worth recognising; for anything more complex than this, the loop is easier to read.
6. Why This Matters in Engineering Work
Building a result hash from rows is the commonest use, and §3's duplicate case is the commonest surprise. A retried test, a concatenated log, a rerun summary — all produce a repeated key, and the hash silently keeps the last one. The row-count-against-key-count check is two lines and makes it visible.
A misspelled key is indistinguishable from a missing one. Normalising with lc at the two points that matter — build and lookup — removes the whole class for case, and chomp on input removes it for whitespace.
undef against deleted is a real distinction in reporting. "Ran, produced nothing" and "never ran" are different facts about a test, and a hash can represent both — but only if you choose the operation deliberately and test with exists rather than with truth.
And delete's return value is worth using. my $old = delete $h{$k}; lets you log what was removed in the same statement, which turns a cleanup step into an auditable one.
7. Common Mistakes
Expecting an overwrite to warn. It never does. Check the counts if duplicates matter.
Expecting a missing key to warn on read. It gives undef, silently.
Normalising keys in only one place. Both the build and the lookup need the same transformation.
Treating $h{k} = undef as removal. The key remains and keys still reports it.
Treating delete as emptying. It removes the key entirely.
Reading delete's undef as "there was no such key". It also means "the value was undef".
Clearing with a loop when %h = () would do. Use the loop when the deletion is selective.
8. Exercise
Exercise 1 — all four operations. Build a two-key hash, add a third key, overwrite one, delete one, and print what delete returned plus the remaining keys sorted.
Exercise 2 — the silent miss. Look up a correct key, a transposed key and a case-changed key in one loop. Print whether each is defined and confirm STDERR is empty.
Exercise 3 — normalise both ends. Build a hash from rows whose names are mixed-case, then look up three differently-cased spellings and get three hits. Say what would break if you normalised only at build time.
Exercise 4 — the swallowed duplicate. Build a hash from four rows where one name appears twice. Print the row count, the key count and the surviving value. Then add the warning from §3's callout and confirm it fires.
Exercise 5 — undef against delete. Set one key to undef and delete another. Print the remaining keys, the count, and exists/defined for both. State in one sentence which operation keys reports.
Exercise 6 — delete returns undef twice over. Delete a key whose value is undef, and delete a key that does not exist. Record both return values and say why delete's return cannot tell the two cases apart.
Exercise 7 — two ways to filter. Remove every passing entry from a status hash, once with a delete loop and once by building a new hash with grep and map. Confirm both give the same keys, then say which you would commit.
9. Summary
- Assign to a key to add or overwrite — the syntax is identical and Perl does not say which happened.
delete $h{k}removes the key and returns its value.- Reading a key that was never set gives
undef, silently — a transposition or a case difference behaves like genuine absence. - Normalise keys at both the build and the lookup, with
lcandchomp. - Overwriting silently discards the previous value, so a duplicate row leaves only the last one. Compare the row count against the key count.
$h{k} = undefkeeps the key;delete $h{k}removes it.keysreports the first and not the second, and onlyexistsdistinguishes them afterwards.deleteon an absent key returnsundefand changes nothing — and thatundefis ambiguous.%h = ();clears a hash; adeleteloop is for selective removal and is safe becausekeysis a snapshot.
Chapter 6.4 is the chapter this one keeps pointing at: three different questions about a key, why a single if test cannot tell them apart, and which one to ask when.
Where this fits
Part of the Perl curriculum.
