4.7 Autovivification
Let's look again at
the provisions list. Suppose you were reading the data from a file,
in the format:
The Skipper
blue_shirt
hat
jacket
preserver
sunscreen
Professor
sunscreen
water_bottle
slide_rule
Gilligan
red_shirt
hat
lucky_socks
water_bottle
Provisions
are indented with some whitespace, following a nonindented line with
the person's name. Let's construct
a hash of provisions. The keys of the hash will be the
person's name, and the value will be an array
reference to an array containing a list of provisions.
Initially, you might gather the data using a simple loop:
my %provisions;
my $person;
while (<>) {
if (/^(\S.*)/) { # a person's name (no leading whitespace)
$person = $1;
$provisions{$person} = [ ] unless exists $provisions{$person};
} elsif (/^\s+(\S.*)/) { # a provision
die "No person yet!" unless defined $person;
push @{ $provisions{$person} }, $1;
} else {
die "I don't understand: $_";
}
}
First, you declare the variables for the resulting hash of provisions
and the current person. For each line that is read, determine if
it's a person or a provision. If
it's a person, remember the name and create the hash
element for that person. The unless exists test
ensures that you won't delete
someone's provision list if his list is split in two
places in the data file. For example, suppose that
"The Skipper" and
" sextant" (note the leading
whitespace) are at the end of the data file in order to list an
additional data item.
The key is the person's name, and the value is
initially a reference to an empty anonymous array. If the line is a
provision, push it to the end of the correct array, using the array
reference.
This code works fine, but it actually says
more than it needs to. Why? Because you can leave out the line that
initializes the hash element's value to a reference
to an empty array:
my %provisions;
my $person;
while (<>) {
if (/^(\S.*)/) { # a person's name (no leading whitespace)
$person = $1;
## $provisions{$person} = [ ] unless exists $provisions{$person};
} elsif (/^\s+(\S.*)/) { # a provision
die "No person yet!" unless defined $person;
push @{ $provisions{$person} }, $1;
} else {
die "I don't understand: $_";
}
}
What happens when you try to store that blue shirt for the Skipper?
While looking at the second line of input, you'll
end up with this effect:
push @{ $provisions{"The Skipper"} }, "blue_shirt";
At this point, $provisions{"The Skipper"}
doesn't exist, but you're trying to
use it as an array reference. To resolve the situation, Perl
automatically inserts a reference to a new empty anonymous array into
the variable and continues the operation. In this case, the reference
to the newly created empty array is dereferenced, and you push the
blue shirt to the provisions list.
This process is called autovivification. Any
nonexisting variable, or a variable containing
undef, which is dereferenced while looking for a
variable location (technically called an lvalue
context), is automatically stuffed with the appropriate
reference to an empty item, and the operation is allowed to proceed.
This is actually the same behavior
you've probably been using in Perl all along. Perl
creates new variables as needed. Before that statement,
$provisions{"The Skipper"}
didn't exist, so Perl created it. Then @{
$provisions{"The Skipper"} } didn't exist,
so Perl created it as well.
For example, this works:
my $not_yet; # new undefined variable
@$not_yet = (1, 2, 3);
Here, you dereference the value $not_yet as if it
were an array reference. But since it's initially
undef, Perl acts as if you had said:
my $not_yet;
$not_yet = [ ]; # inserted through autovivification
@$not_yet = (1, 2, 3);
In other words, an initially empty array becomes an array of three
elements.
This autovivification also works for multiple levels of assignment:
my $top;
$top->[2]->[4] = "lee-lou";
Initially, $top contains undef,
but because it is dereferenced as if it were an array reference, Perl
inserts a reference to an empty anonymous array into
$top. The third element (index value 2) is then
accessed, which causes Perl to grow the array to be three elements
long. That element is also undef, so it is stuffed
with a reference to another empty anonymous array. We then spin out
along that newly created array, setting the fifth element to
lee-lou.
|