Skip to content
VLSI Mentor

Perl Part I · Module 6 · Hashes

Counting With a Hash

This is the idiom Perl is known for and it is one line long. Walk a list, increment a hash entry keyed on each item, and at the end the hash holds how many times each distinct item appeared — with no initialisation, no check for whether the key already exists, and no warning about the absent value you just incremented. That last part is worth understanding rather than just using, because Perl is doing two separate favours: creating the entry the moment you increment it, and exempting the increment from the warning it would normally give for using an undefined value. Both have names, both have limits, and one of them does not extend to the operator you might reach for next.

Foundation14 min readPerlPart IFundamentalsHashesIdiomsReports

Part I · Module 6 · Chapter 6.8 · Counting With a Hash

1. The Whole Idiom

Azvya Education Pvt. Ltd.VLSI Mentor
tally.pl
use strict;
use warnings;

my @statuses = qw(PASS FAIL PASS PASS TIMEOUT FAIL PASS);

my %count;

$count{$_}++ for @statuses;

for my $status (sort { $count{$b} <=> $count{$a} or $a cmp $b } keys %count) {
    printf "%-8s %d\n", $status, $count{$status};
}

printf "distinct: %d   total: %d\n", scalar(keys %count), scalar(@statuses);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
PASS     4
FAIL     2
TIMEOUT  1
distinct: 3   total: 7

The counting is one line:

Azvya Education Pvt. Ltd.VLSI Mentor
This is the idiom
$count{$_}++ for @statuses;

No my %count = (); with keys pre-created. No if (exists $count{$_}). No initialisation to zero. Each ++ either increments an existing entry or creates it at 1.

The rest of the script is reporting, and both halves are earlier chapters doing their jobs:

  • sort { $count{$b} <=> $count{$a} or $a cmp $b } orders by count descending with a tie-break, from Chapter 5.9 §5 — and the tie-break is what makes the output reproducible, which Chapter 6.6 §4 showed matters for a hash.
  • scalar(keys %count) is the number of distinct values and scalar(@statuses) the number of observations. Two different numbers that a report usually wants both of.

The for as a statement modifier is Chapter 3.9 §1's form, and $_ is Chapter 2.12 §4's default variable.

2. Why It Needs No Initialisation

Azvya Education Pvt. Ltd.VLSI Mentor
first-increment.pl
use strict;
use warnings;

my %count;

printf "before : %d key(s)\n", scalar(keys %count);

$count{alu}++;

printf "after  : %d key(s), alu = %d\n", scalar(keys %count), $count{alu};
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDOUT
before : 0 key(s)
after  : 1 key(s), alu = 1
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

An empty hash, one ++, and now there is a key with the value 1. Nothing on STDERR.

Two things happened, and they are separate:

  • Perl created the entry. The key alu did not exist and now it does. This is called autovivification — Perl bringing a container entry into existence because you used it in a way that implies it should be there. Chapter 7.9 covers it properly, including the nested case where it does considerably more work.
  • The increment did not warn. $count{alu} was undefined, and ++ on an undefined value gives 1 without complaint.

That second point is the one worth checking rather than assuming, because Chapter 1.11 has been insisting all along that using an undefined value produces a diagnostic.

3. The Exemption Follows the Operator, Not the Container

Azvya Education Pvt. Ltd.VLSI Mentor
three-operators.pl
use strict;
use warnings;

my %a;
$a{k}++;
printf "++   : value=%d  keys=%d\n", $a{k}, scalar(keys %a);

my %b;
$b{k} += 1;
printf "+=   : value=%d  keys=%d\n", $b{k}, scalar(keys %b);

my %c;
my $sum = $c{k} + 1;
printf "+    : value=%d  keys=%d\n", $sum, scalar(keys %c);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDOUT
++   : value=1  keys=1
+=   : value=1  keys=1
+    : value=1  keys=0
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
Use of uninitialized value $c{"k"} in addition (+) at three-operators.pl line 13.

Three operators, three different behaviours:

OperatorWarns?Creates the key?
$h{k}++noyes
$h{k} += 1noyes
$h{k} + 1yesno

The first two are assignments to the element, so Perl creates it and exempts the operator from the undefined-value warning. The third only reads the element — the result goes somewhere else — so there is nothing to create, and reading an undefined value warns as normal.

4. Counting Things Other Than Items in a List

The idiom generalises to anything you can turn into a key.

Azvya Education Pvt. Ltd.VLSI Mentor
count-by-field.pl
use strict;
use warnings;

my @rows = (
    "alu,PASS",
    "fifo,FAIL",
    "decoder,PASS",
    "regfile,TIMEOUT",
    "cpu,FAIL",
    "dma,PASS",
);

my %by_status;
my %by_initial;

for my $row (@rows) {
    my ($block, $status) = split /,/, $row, -1;

    $by_status{$status}++;
    $by_initial{ substr($block, 0, 1) }++;
}

printf "by status:\n";
printf "  %-8s %d\n", $_, $by_status{$_} for sort keys %by_status;

printf "by first letter:\n";
printf "  %-8s %d\n", $_, $by_initial{$_} for sort keys %by_initial;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
by status:
  FAIL     2
  PASS     3
  TIMEOUT  1
by first letter:
  a        1
  c        1
  d        2
  f        1
  r        1

Two tallies from one pass. The key can be a field, a computed value, a substr — anything that produces a string.

Note both reports use sort keys, for Chapter 6.6's reason, and substr is Chapter 4.5's.

Summing rather than counting

The same shape with += adds up values instead of occurrences:

Azvya Education Pvt. Ltd.VLSI Mentor
sum-by-key.pl
use strict;
use warnings;

my @rows = ("alu,184", "fifo,92", "alu,56", "decoder,1240", "fifo,8");

my %total;
my %runs;

for my $row (@rows) {
    my ($block, $tests) = split /,/, $row, -1;

    $total{$block} += $tests;
    $runs{$block}++;
}

for my $block (sort keys %total) {
    printf "%-10s %5d test(s) over %d run(s)\n", $block, $total{$block}, $runs{$block};
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
alu          240 test(s) over 2 run(s)
decoder     1240 test(s) over 1 run(s)
fifo         100 test(s) over 2 run(s)

Two hashes keyed the same way give a total and a count, and dividing one by the other would give a mean. This is the shape of most aggregation a flow script needs, and it is the honest answer to Chapter 6.3 §3's duplicate-row problem: when a key appears twice and both values matter, accumulate rather than overwrite.

5. Reading a Count That Might Not Exist

Once the tally is built, looking up a key that never appeared is Chapter 6.4's territory, and the right answer depends on the question.

Azvya Education Pvt. Ltd.VLSI Mentor
reading-counts.pl
use strict;
use warnings;
use v5.10;

my @statuses = qw(PASS FAIL PASS);

my %count;
$count{$_}++ for @statuses;

for my $status (qw(PASS FAIL TIMEOUT)) {
    printf "%-8s seen %d time(s)   (exists: %s)\n",
        $status,
        $count{$status} // 0,
        (exists $count{$status} ? "yes" : "no");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
PASS     seen 2 time(s)   (exists: yes)
FAIL     seen 1 time(s)   (exists: yes)
TIMEOUT  seen 0 time(s)   (exists: no)

$count{$status} // 0 reads as "the count, or zero if there is no entry" — the defined-or operator from Chapter 3.4 §4, which is correct here because a count of zero and a missing entry mean the same thing for a tally.

That is a case where // and || would both work, since a count is never legitimately empty-string and a stored 0 cannot occur in a tally built by ++. Use // anyway — it is right for the general case, and a tally built with += from data can contain a real zero.

What // does not do is tell you that TIMEOUT never appeared, which is why the exists column is there. For a report listing every expected status, "seen 0 times" is the right output. For one asking "were any statuses present that we did not expect", exists over the keys is the test.

6. Why This Matters in Engineering Work

Tallying is most of what a summary script does. Failures per block, violations per type, warnings per category, tests per corner. All of it is $count{$key}++ in a loop, and the two numbers it produces — distinct keys and total observations — are both report lines.

Distinct-count is free. scalar(keys %count) answers "how many different kinds of warning did we see", which is often the more useful number than the total. No extra work.

Accumulating is the honest answer to duplicate keys. Chapter 6.3 §3 showed a hash silently keeping the last of two rows with the same key. If both rows are real data, += into a total and ++ into a run count keeps everything, and the two hashes together say more than either alone.

And the sort is not optional. A tally printed with an unsorted keys walk changes order between runs — Chapter 6.6 — so the report cannot be diffed. Sorting by count descending with a tie-break gives both the useful order and reproducibility.

7. Common Mistakes

Initialising before incrementing. $count{$k} //= 0; then $count{$k}++ is harmless noise; ++ handles it.

Expecting ++ on an absent key to warn. It does not, and it creates the key.

Expecting a plain + to behave the same. $h{k} + 1 warns and creates nothing.

Printing a tally with unsorted keys. The order changes between runs.

Sorting by count without a tie-break. Equal counts leave the order unspecified.

Using || to default a count read from a += tally. A legitimate zero total would be replaced. Use //.

Overwriting when you meant to accumulate. $total{$k} = $v loses the earlier value; += keeps both.

8. Exercise

Exercise 1 — the idiom. Tally a list of eight status strings with one line, then print the counts ordered highest-first with a tie-break, plus the distinct and total counts.

Exercise 2 — no initialisation. Start with an empty hash, increment one key, and print the key count and value. Confirm STDERR is empty, then name the two separate favours Perl did.

Exercise 3 — three operators. Reproduce §3's script. Record which operators warned and which created a key, then fill in the three-row table from your own output.

Exercise 4 — the related exemption. Say which chapter measured the same exemption for .= on a plain scalar, and state the rule in one sentence using the word "operator".

Exercise 5 — two tallies in one pass. From six rows of block,status, build a count by status and a count by the block's first letter. Print both, sorted.

Exercise 6 — total and mean. From five rows of block,count where two blocks repeat, build a total and a run count per block, then print the mean per run to one decimal place.

Exercise 7 — the absent key. After tallying three statuses, report on four — including one that never appeared — using // 0 for the count and exists for whether it was seen. Confirm the absent one reads "0" and "no".

9. Summary

  • $count{$thing}++ is the whole counting idiom — one line, no initialisation, no existence check.
  • Perl creates the entry on first use. That is autovivification, and Chapter 7.9 covers it fully.
  • ++ and += on an absent key do not warn and do create it. Plain + warns and creates nothing.
  • The exemption belongs to the operator, not the hash — the same rule Chapter 3.4 §3 measured for .=.
  • scalar(keys %count) is the distinct count; the list's length is the total. Both are useful report lines.
  • += sums instead of counting, and a second hash with ++ gives the number of contributions — which is the right answer when a duplicate key carries real data.
  • Read a possibly-absent count with // 0, and use exists when "never seen" is a distinct outcome.
  • Sort the output, with a tie-break, or the report is not reproducible.

Chapter 6.9 uses the same structure for a different purpose — ignoring the values entirely and treating the keys as a set, which is how Perl answers "have I seen this before" and removes duplicates from a list.

Where this fits

Part of the Perl curriculum.