Perl references and nested data structures

Build hashes of arrays, add to them, and loop over them with references.

Perl
use strict;
use warnings;
use List::Util qw(sum);

my %scores = (Ada => [90, 85], Bo => [70], Cy => []);
push @{ $scores{Bo} }, 95;
$scores{Di}[0] = 60;

for my $name (sort keys %scores) {
    my @s = @{ $scores{$name} };
    my $average = @s ? sprintf("%.1f", sum(@s) / @s) : "n/a";
    print "$name: ", scalar(@s), " scores, average $average\n";
}

print join(" ", ref(\1), ref([]), ref({}), ref(sub { 1 }), ref(\\1)), "\n";

my %copy = %scores;
delete $copy{Cy};
print exists $scores{Cy} ? "Cy still in the original\n" : "Cy gone\n";
print join(",", sort keys %copy), "\n";
my @both = @scores{qw(Ada Bo)};
print scalar(@{ $both[0] }) + scalar(@{ $both[1] }), " scores for Ada and Bo\n";
Output
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
Cy still in the original
Ada,Bo,Di
4 scores for Ada and Bo

Explanation

Arrays and hashes hold only scalars, so complex data is built from references: a reference is a scalar that points at an array, a hash, a subroutine, or another scalar. [1, 2, 3] creates a reference to a new array, {a => 1} creates a reference to a new hash, and \@list refers to an existing array. @{ $ref } turns an array reference back into the array, and $ref->[0] and $ref->{key} access single elements.

Perl creates intermediate structures automatically when you assign into them, which is called autovivification: $scores{Di}[0] = 60 builds the array for Di. That is convenient, but just testing exists $a{b}{c} also creates $a{b}. The ref function tells you what kind of reference you have, which is useful in generic code.

Explore the dry run
Guided walkthroughReady
1use strict;
2use warnings;
3use List::Util qw(sum);
4
5my %scores = (Ada => [90, 85], Bo => [70], Cy => []);
6push @{ $scores{Bo} }, 95;
7$scores{Di}[0] = 60;
8
9for my $name (sort keys %scores) {
10 my @s = @{ $scores{$name} };
11 my $average = @s ? sprintf("%.1f", sum(@s) / @s) : "n/a";
12 print "$name: ", scalar(@s), " scores, average $average\n";
13}
14
15print join(" ", ref(\1), ref([]), ref({}), ref(sub { 1 }), ref(\\1)), "\n";
16
17my %copy = %scores;
18delete $copy{Cy};
19print exists $scores{Cy} ? "Cy still in the original\n" : "Cy gone\n";
20print join(",", sort keys %copy), "\n";
21my @both = @scores{qw(Ada Bo)};
22print scalar(@{ $both[0] }) + scalar(@{ $both[1] }), " scores for Ada and Bo\n";

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Each hash value is a reference to an array of scores.—
—
@{ ... } dereferences the array so that push can add to it.—
—
Autovivification: Perl creates the missing array for Di.—
—
Dereference the stored array reference into a real array.—
—
Averages are formatted to one decimal place. Cy has no scores.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
ref reports the kind of reference. A reference to a reference is REF.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
Deleting from the copy does not affect the original hash.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
The original still has Cy.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
Cy still in the original
The copy lost its Cy key.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
Cy still in the original
Ada,Bo,Di
A hash slice fetches several values at once: 2 + 2.—
Ada: 2 scores, average 87.5
Bo: 2 scores, average 82.5
Cy: 0 scores, average n/a
Di: 1 scores, average 60.0
SCALAR ARRAY HASH CODE REF
Cy still in the original
Ada,Bo,Di
4 scores for Ada and Bo

Cost and common mistakes

Each hash or array access is O(1) on average; the loop is O(n).

  • %copy = %scores is a shallow copy: the array references are shared, so push @{$copy{Ada}}, 1 would also change $scores{Ada}.
  • Dividing by the number of scores when there are none is a division by zero error, so the code checks @s first.
  • Always use strict. Without it a typo in a variable name silently creates a new global variable.

Try it yourself

How do you read the first score of Ada?

Reveal answer and reasoning

$scores{Ada}[0] or $scores{Ada}->[0]; the arrow is optional between subscripts.

Further reading