Perl references and nested data structures
Build hashes of arrays, add to them, and loop over them with references.
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
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
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 |
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.