Hashes: looking values up by name
Storing values under names instead of positions, counting things as you meet them, and why printing a hash always starts by sorting its keys.
A lookup, not a queue
A hash holds values under names you choose: my %ages = ('Ada' => 36, 'Grace' => 45);. The => is a fat comma, and it is a comma: it separates a key from its value and quietly quotes the word on its left, which is what makes name => 'Ada' read like a sentence.
The whole hash takes a percent sign, and one entry asked out of it takes a dollar sign with curly braces: $ages{'Ada'}. The brackets are the difference between a whole structure and a single value inside it, and it is worth reading a line twice until that feels natural.
A hash has no order
A hash is built to answer questions quickly, and it keeps its keys in an order of its own that can change between two runs of the same program. Printing a hash straight out is therefore a way of asking for a surprise.
Print sort keys %ages instead. keys answers the list of keys, sort puts that list in a predictable order, and the output is then the same on every run. That one habit — always sorting the keys — is the difference between a report and a lottery.
Asking, deleting and counting
exists $ages{'Alonzo'} asks whether a key is there at all, which is not the same question as whether its value is true: an entry holding 0 or an empty piece of text is still there. delete removes an entry, values answers the list of values, and scalar keys %ages counts the entries.
The habit worth taking from this chapter is the counting one. Start with an empty hash and add to an entry with $count{$word}++ each time you meet that word, and Perl fills the entry in for you. A tally of anything at all — words, votes, errors in a log — is four lines long.
What the percent sign promises
A hash is a mapping from keys to values, and it is built for lookup rather than for order. Perl turns each key into a number behind the scenes and uses that number to find the entry, which is why a hash answers a question in about the same time whether it holds three entries or three thousand.
The keys are stored as strings, and each value is one scalar. That is enough to build a record: a person is a hash of name, age and city, and the field names are the keys. Nesting a hash inside a hash needs a reference, which the chapter on references covers.
From a tally to a sorted report
A tally starts as an empty hash and grows one entry at a time. $count{$word}++ reads the entry, treats a missing one as zero, adds one, and stores it back. That single line is why counting words, votes or error levels is a short job in Perl.
Printing the tally needs a sort. sort keys %count orders the names, and a comparator block can order by the counts instead: sort { $count{$b} <=> $count{$a} } keys %count puts the largest count first. Adding || $a cmp $b after the numeric comparison breaks ties by name, so the output is the same on every run.
Reading several keys at once
A hash slice reads several entries in one go: my @ages = @ages{'Ada', 'Grace'}; puts the two values in a list, in the order the keys were written. The at sign is there because the answer is a list, exactly as it is for an array slice.
The each function hands back one key and value at a time, which is how a hash is walked without building a list of its keys first. It keeps a cursor inside the hash, so a loop that stops early leaves that cursor halfway through; for a report, sort keys is still the safer shape.
Names, values, and counting what you meet
use strict;
use warnings;
my %ages = ('Ada' => 36, 'Grace' => 45, 'Alan' => 41);
print "Ada is ", $ages{'Ada'}, "\n";
print "entries: ", scalar keys %ages, "\n";
# A hash has no order: sort the keys.
foreach my $name (sort keys %ages) {
print "$name is $ages{$name}\n";
}
print "names: ", join(', ', sort keys %ages), "\n";
print "values: ", join(' ', sort values %ages), "\n";
my $known = exists $ages{'Alonzo'} ? 'yes' : 'no';
print "Alonzo known: $known\n";
delete $ages{'Alan'};
print "left: ", join(', ', sort keys %ages), "\n";
my %tally;
foreach my $word ('red', 'green', 'red') {
$tally{$word}++;
}
foreach my $word (sort keys %tally) {
print "$word: $tally{$word}\n";
}Three people, and a tally of colours
Run it and read the keys in the order they printed. Then add a fourth person, delete one of the others, and print the entry count again to check the number.
Reading a key that is not there gives you undef rather than an error, because Perl has no opinion about whether you meant to. exists asks that question directly, and use warnings; is what tells you when an undefined value reached your arithmetic.
Three hash mistakes are worth knowing. $ages{Ada} and $ages{'Ada'} are the same key, but %ages is the whole hash and $ages is not a scalar. A key holding undef still exists, so use defined as well as exists when the value matters. And my $n = %ages does not count the entries; ask with scalar keys %ages.
Try these next
- Count the words in a sentence, then print the three most frequent ones.
- Add a value of 0 under a new key and check that
existsand the value itself answer different questions. - Delete a key, print the remaining keys sorted, and print the entry count before and after.
Count the words in a sentence
Run it and find the most frequent word. Then change the sentence, add a word, and check that the counts still add up to the number of words.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Why is sort keys %map the usual way to print a hash?
