Perl · Course navigation

Perl references and nested data structures

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

On this page
Before you start

Perl arrays and hashes.

  • Build hashes of arrays, add to them, and loop over them with references.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

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.

Code example

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

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
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

Behind the scenes

How Perl processes this code

Perl compiles the script internally before executing it. strict and warnings help identify mistakes.

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