Приглашаем посетить
Кантемир (kantemir.lit-info.ru)

Section 4.1.  Performing the Same Task on Many Arrays

Previous
Table of Contents
Next

4.1. Performing the Same Task on Many Arrays

Before the Minnow can leave on an excursion (for example, a three-hour tour), we should check every passenger and crew member to ensure they have all the required trip items in their possession. Let's say that, for maritime safety, every person on-board the Minnow needs to have a life preserver, some sunscreen, a water bottle, and a rain jacket. We can write a bit of code to check for the Skipper's supplies:

my @required = qw(preserver sunscreen water_bottle jacket);
my @skipper  = qw(blue_shirt hat jacket preserver sunscreen);

for my $item (@required) {
  unless (grep $item eq $_, @skipper) { # not found in list?
    print "skipper is missing $item.\n";
  }
}

The grep in a scalar context returns the number of times the expression $item eq $_ returns true, which is 1 if the item is in the list and 0 if not.[*] If the value is 0, it's false, and we print the message.

[*] There are more efficient ways to check list membership for large lists, but for a few items, this is probably the easiest way to do so with just a few lines of code.

Of course, if we want to check on Gilligan and the Professor, we might write the following code:

my @gilligan = qw(red_shirt hat lucky_socks water_bottle);
for my $item (@required) {
  unless (grep $item eq $_, @gilligan) { # not found in list?
    print "gilligan is missing $item.\n";
  }
}

my @professor = qw(sunscreen water_bottle slide_rule batteries radio);
for my $item (@required) {
  unless (grep $item eq $_, @professor) { # not found in list?
    print "professor is missing $item.\n";
  }
}

You may start to notice a lot of repeated code here and think that we should refactor that into a common subroutine that we can reuse (and you'd be right):

sub check_required_items {
  my $who = shift;
  my @required = qw(preserver sunscreen water_bottle jacket);
  for my $item (@required) {
    unless (grep $item eq $_, @_) { # not found in list?
      print "$who is missing $item.\n";
    }
  }
}

my @gilligan = qw(red_shirt hat lucky_socks water_bottle);
check_required_items('gilligan', @gilligan);

Perl gives the subroutine five items in its @_ array initially: the name gilligan and the four items belonging to Gilligan. After the shift, @_ only has the items. Thus, the grep checks each required item against the list.

So far, so good. We can check the Skipper and the Professor with just a bit more code:

my @skipper   = qw(blue_shirt hat jacket preserver sunscreen);
my @professor = qw(sunscreen water_bottle slide_rule batteries radio);
check_required_items('skipper', @skipper);
check_required_items('professor', @professor);

And for the other passengers, we repeat as needed. Although this code meets the initial requirements, we've got two problems to deal with:

  • To create @_, Perl copies the entire contents of the array to be scanned. This is fine for a few items, but if the array is large, it seems a bit wasteful to copy the data just to pass it into a subroutine.

  • Suppose we want to modify the original array to force the provisions list to include the mandatory items. Because we have a copy in the subroutine ("pass by value"), any changes we make to @_ aren't reflected automatically in the corresponding provisions array.[*]

    [*] Actually, assigning new scalars to elements of @_ after the shift modifies the corresponding variable being passed, but that still wouldn't let us extend the array with additional mandatory provisions.

To solve either or both of these problems, we need pass by reference rather than pass by value. And that's just what the doctor (or Professor) ordered.


Previous
Table of Contents
Next