5.7. Selecting and Altering Complex Data
You
can use these operators on more complex data. Taking the provisions
list from Chapter 4:
my %provisions = (
"The Skipper" =>
[qw(blue_shirt hat jacket preserver sunscreen)],
"The Professor" =>
[qw(sunscreen water_bottle slide_rule batteries radio)],
"Gilligan" =>
[qw(red_shirt hat lucky_socks water_bottle)],
);
In this case, $provisions{"The Professor"} gives
an array reference of the provisions brought by the Professor, and
$provisions{"Gilligan"}[-1] gives the last item
Gilligan thought to bring.
Run a few queries against this data. Who brought fewer than five
items?
my @packed_light = grep @{ $provisions{$_} } < 5, keys %provisions;
In this case, $_ is the name of a person. Take
that name, look up the array reference of the provisions for that
person, dereference that in a scalar context to get the count of
provisions, and then compare it to 5. And wouldn't
you know it; the only name is Gilligan.
Here's a trickier one. Who brought a water bottle?
my @all_wet = grep {
my @items = @{ $provisions{$_} };
grep $_ eq "water_bottle", @items;
} keys %provisions;
Starting with the list of names
again (keys %provisions), pull up all the packed
items first, and then use that list in an inner
grep to count the number of those items that equal
water_bottle. If the count is 0,
there's no bottle, so the result is false for the
outer grep. If the count is nonzero, you have a bottle, so the result
is true for the outer grep. Now you see that the
Skipper will be a bit thirsty later, without any relief.
You can also perform transformations. For
example, turn this hash into a list of array references, with each
array containing two items. The first is the original
person's name; the second is a reference to an array
of the provisions for that person:
my @remapped_list = map {
[ $_ => $provisions{$_} ];
} keys %provisions;
The keys of
%provisions are names of the people. For each
name, construct a two-element list of the name and the corresponding
provisions array reference. This list is inside an anonymous array
constructor, so you get back a reference to a newly created array for
each person. Three names in; three references out.[26] [26]If
you had left the inner brackets off, you'd end up
with six items out. That's not very useful, unless
you're creating a different hash from them.
Or, let's go a different way. Turn the input hash
into a series of references to arrays. Each array will have a
person's name and one of the items they brought:
my @person_item_pairs = map {
my $person = $_;
my @items = @{ $provisions{$person} };
map [$person => $_], @items;
} keys %provisions;
Yes, a map within a
map. The outer map selects one
person at a time. The name is saved into $person,
and then the item list is extracted from the hash. The inner
map walks over this item list, executing the
expression to construct an anonymous array reference for each item.
The anonymous array contains the person's name and
the provision item.
You had to use $person here to hold the outer
$_ temporarily. Otherwise, you
can't refer to both temporary values for the outer
map and the inner
map.
 |  |  | 5.6. Applying a Bit of Indirection |  | 5.8. Exercises |
Copyright © 2003 O'Reilly & Associates. All rights reserved.
|