Perl references and nested data structures
Build hashes of arrays, add to them, and loop over them with references.
On this page
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
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";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
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";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
| Step / line | What happens | State after step | Output 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.