Skip to content
VLSI Mentor

Perl Part I · Module 6 · Hashes

Choosing Between an Array and a Hash

Two modules have given you two containers, and this chapter closes the pair by deciding between them. The usual advice is that order means an array and lookup by name means a hash, which is right as far as it goes — but it is more useful seen from the other side, as what the wrong choice costs you. Pick an array when you need lookup and every lookup becomes a nested loop. Pick a hash when you need order and you do not merely get the items in a different order, you can get a different set of items on each run. There is also a third question the usual advice leaves out, which settles cases the first two do not: whether repeated values are data or noise.

Foundation15 min readPerlPart IFundamentalsHashesArraysData Structures

Part I · Module 6 · Chapter 6.11 · Choosing Between an Array and a Hash

1. Three Questions, Not Two

QuestionArrayHash
Does position mean something?yes — it is the whole pointno — there is no position
Do you look things up by name?only by scanningyes — directly
Do repeats matter?kept, every onecollapsed to one key

The first two are the usual advice. The third decides the cases the first two leave open, and §5 is about it.

Read the table as what each container is for: an array is a sequence, a hash is a mapping. Everything else follows.

2. The Decision, Drawn

One question decides it, and the answer brings its own operations

A decision fork with five blocks. At the middle left is the data you are about to store, with the question of which access pattern you actually need. The upper branch is for data where position is meaningful — asking which item is third, or keeping things in the order they arrived — and it leads to an array, whose natural operations are subscripting by number, pushing onto the end, and sorting. The lower branch is for data where a name is meaningful — asking what status belongs to a particular block — and it leads to a hash, whose natural operations are subscripting by key, testing whether a key exists, and listing the keys. Choosing the branch that does not match the question makes every access awkward.Your datawhich access pattern?Which one is third?order is meaningfulAn array$a[2], push, sortIs this name here?the name is meaningfulA hash$h{alu}, exists, keysby positionby name12
The two branches are not interchangeable implementations of one idea — each makes a different set of operations natural and the other set awkward. The captions list what each container gives you for free, which is the practical form of the decision.

3. An Array Where You Needed a Hash

Azvya Education Pvt. Ltd.VLSI Mentor
array-for-lookup.pl
use strict;
use warnings;

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

print "with an array — a scan per lookup:\n";

for my $wanted (qw(fifo regfile)) {
    my $found;
    for my $row (@rows) {
        my ($name, $status) = split " ", $row;
        if ($name eq $wanted) { $found = $status; last }
    }
    printf "  %-8s %s\n", $wanted, (defined $found ? $found : "not found");
}

my %status = map { split " ", $_ } @rows;

print "with a hash — one lookup each:\n";

for my $wanted (qw(fifo regfile)) {
    printf "  %-8s %s\n", $wanted, ($status{$wanted} // "not found");
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
with an array — a scan per lookup:
  fifo     FAIL
  regfile  FAIL
with a hash — one lookup each:
  fifo     FAIL
  regfile  FAIL

Identical answers. The array version is not wrong — it is what the structure forces you to write.

Count what the array version needs that the hash version does not:

  • a nested loop,
  • a split of every row on every lookup,
  • a $found variable initialised outside the inner loop,
  • a last so it stops at the first match,
  • and a defined check because $found may never be set.

Five things, each a place to get it wrong. The hash version is $status{$wanted}.

And the hash was built in one line — map { split " ", $_ } @rows — using Chapter 5.10's map returning two items per row, which flatten into pairs by Chapter 5.12's rule and become a hash by Chapter 6.2 §1's. Build the hash once, then look up as often as you like. That is Chapter 5.11 §1's point, now with the alternative written out so the cost is visible.

4. A Hash Where You Needed an Array

The reverse is worse, because it does not just make the code awkward — it makes the answer wrong.

Azvya Education Pvt. Ltd.VLSI Mentor
hash-for-order.pl
use strict;
use warnings;

my @lines = ("start", "loading", "running", "done");

printf "array, first three : %s\n", join(" | ", @lines[0 .. 2]);

my %by_line;
$by_line{$_} = 1 for @lines;

printf "hash, first three  : %s\n", join(" | ", (keys %by_line)[0 .. 2]);

Three runs:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — the hash line, three consecutive runs
hash, first three  : start | running | loading
hash, first three  : done | running | start
hash, first three  : running | done | loading
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — the array line, every run
array, first three : start | loading | running

Look carefully at the three hash lines. They are not three orderings of the same three items:

  • Run 1 has start, running, loading.
  • Run 2 has done, running, start — done is in and loading is out.
  • Run 3 has running, done, loading — start is out.

Different sets, not just different orders. "The first three keys of a hash" is not a reordering of the right answer; it is an arbitrary three of four.

Your three runs will show different lines again — repeating this capture produced done | start | running, running | loading | start and loading | done | start, which between them exclude a different item each time. The reproducible fact is that the subset is arbitrary, not which subset you get.

5. The Third Question: Do Repeats Matter?

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

my @runs = ("alu", "fifo", "alu");

my %seen;
$seen{$_} = 1 for @runs;

printf "array : %d entries\n", scalar(@runs);
printf "hash  : %d keys\n", scalar(keys %seen);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
array : 3 entries
hash  : 2 keys

Three in, two out. The second alu is gone — not an error, just the overwrite from Chapter 6.3 §3.

That difference decides cases the first two questions do not:

What you are storingContainerWhy
every test run, including rerunsarrayeach run is a separate fact
the final status per testhashone answer per name
how many times each test ranhash of countsChapter 6.8
which tests ran at allhash as a setChapter 6.9

Note that the last two are hashes because collapsing duplicates is what you want — you have turned the repeats into a number or ignored them deliberately. That is the distinction: a hash is right when repeats are noise or when counting them is the job, and wrong when each occurrence is its own data point.

6. When You Need Both

The honest answer is often that you need order and lookup, and the answer is to keep both.

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

my @order;
my %status;

for my $row ("alu PASS", "fifo FAIL", "decoder PASS") {
    my ($name, $st) = split " ", $row;

    push @order, $name;
    $status{$name} = $st;
}

printf "in file order : %s\n", join(" | ", @order);
printf "lookup fifo   : %s\n", $status{fifo};

print "report:\n";
printf "  %-8s %s\n", $_, $status{$_} for @order;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
in file order : alu | fifo | decoder
lookup fifo   : FAIL
report:
  alu      PASS
  fifo     FAIL
  decoder  PASS

Two lines in the loop and you have both properties. The array remembers the sequence; the hash answers the questions. The report iterates the array and looks up the hash, so it comes out in file order with direct access to each value.

This is a genuinely common and correct pattern, and it is worth recognising as the answer rather than as a workaround. It costs one extra push.

Two details:

  • The array holds keys, not values. @order is a list of names; the data lives once, in the hash. Duplicating the values in both would give you two things to keep in step.
  • If the input can repeat a name, push @order, $name unless exists $status{$name}; keeps the first-seen order and avoids a duplicate row in the report — Chapter 6.9's set guard doing the job.

7. A Decision Checklist

In the order the questions actually settle things:

  1. Is each item a separate occurrence, even if it repeats? → array.
  2. Does the order they arrived in matter? → array, or array-plus-hash if you also need lookup.
  3. Do you look items up by a name? → hash.
  4. Do you need to know how many times each thing appeared? → hash of counts.
  5. Do you only need to know whether you have seen something? → hash as a set.
  6. None of the above — just a bag of values you will process in bulk? → array; it is simpler and it keeps everything.

And the signal that you chose wrong, in either direction:

  • Writing a loop to find something means you wanted a hash.
  • Sorting the keys to recover an order you had on input means you wanted an array — the sort will give you an order, not the order.

8. Why This Matters in Engineering Work

Both containers appear in nearly every real script, usually in exactly §6's pairing: a list of tests in submission order, and a hash of results keyed by test name. Recognising that as the normal shape saves inventing something worse.

The lookup mistake costs performance, visibly. A nested scan over a few hundred rows is imperceptible; over a hundred thousand lines of a timing report with a lookup per line, it is the difference between a script that finishes and one you kill. And the fix is one line that builds a hash.

The order mistake costs correctness, invisibly. §4's measurement is the one to remember: a hash does not give you the items in a shuffled order, it can give you a different subset when you take the first few. A script that reports "the first three failures" from a hash reports three arbitrary failures, and the output looks entirely plausible.

And the duplicates question is the one people skip. A hash keyed on test name silently keeps one result per test, which is right for a final-status report and wrong for a log of every attempt. Deciding that deliberately — rather than discovering it when a rerun's result overwrites the original — is what §5 is for.

9. Common Mistakes

Scanning an array in a loop to find something by name. Build a hash once instead.

Storing a sequence in a hash. The order is not recoverable by sorting, because it was never stored.

Taking (keys %h)[0 .. 2] as "the first three". It is an arbitrary three, and a different three on the next run.

Using a hash for data where each occurrence matters. Repeats collapse and the earlier value is lost.

Duplicating values into both an array and a hash. Keep the keys in the array and the data in the hash, once.

Reaching for a complicated structure when an array would do. If you process everything in bulk and never look anything up, an array is simpler and loses nothing.

Assuming sorting fixes every order problem. It gives a defined order, not the input's order.

10. Exercise

Exercise 1 — the three questions. For each of these, say which container and which of §1's three questions decided it: (a) the lines of a log file; (b) the final status of each test; (c) how many warnings of each type; (d) every warning message emitted, including repeats; (e) the set of blocks that were touched; (f) the order tests were submitted in.

Exercise 2 — count the cost. Reproduce §3's script. Then list every construct the array version needs that the hash version does not, and say how many split calls each version performs in total.

Exercise 3 — the wrong set. Run §4's script three times and record the hash line each time. Report whether the three lines contain the same three items, then say in one sentence why sorting would not fix it.

Exercise 4 — order is gone. Store four log lines in a hash, then try to recover the original order. Say what you would need to have stored to make it possible.

Exercise 5 — duplicates. Store a three-item list containing one repeat in both an array and a hash. Report both counts, then say which you would use to answer "how many runs were there" and which for "what was the final result".

Exercise 6 — both. Reproduce §6's pattern over four rows, then add a duplicate name to the input and confirm the report shows it once, in first-seen position, using the unless exists guard.

Exercise 7 — diagnose a choice. You are shown a script containing a nested loop that searches an array by name, and another that sorts hash keys to print "the first five events". Say what each author should have used and what symptom each mistake produces.

11. Summary

  • Three questions decide it: does position mean something, do you look up by name, and do repeats matter.
  • An array is a sequence; a hash is a mapping. Everything else follows from that.
  • Using an array for lookup forces a nested loop, a split per row per lookup, and four other moving parts — all replaced by one subscript.
  • Using a hash for order loses the order permanently. Taking the first three keys gave a different set of three on each run, not a different order — and no sort recovers the input's sequence.
  • A hash collapses duplicates. That is right when repeats are noise or when counting them is the job, and wrong when each occurrence is a separate fact.
  • Needing both is normal: push the key onto an array for order and assign into a hash for lookup, then iterate the array and look up the hash.
  • Writing a loop to find something means you wanted a hash; sorting keys to recover an input order means you wanted an array.

That closes Module 6, and with it the two containers. You can hold values in order and find them by position, hold them by name and find them directly, tell the three questions about a key apart, walk a hash reproducibly, count and deduplicate in one line each, pass named arguments, and choose the right container for a job.

Module 7 lifts the restriction both modules kept running into. Chapter 5.12 could not return two arrays from a subroutine; Chapter 6.3 could not give one key several values; Chapter 5.8 had to store parsed rows with a construct it could only call a forward pointer; and Chapter 6.10 could not pass two hashes separately. All four need a scalar that stands for a whole array or hash, and that is a reference — which is what makes a table of rows, a list of failures per block, and a record with named fields possible.

Where this fits

Part of the Perl curriculum.