[ Team LiB ] |
5.6 Applying a Bit of IndirectionSome problems that may appear very complex are actually simple once you've seen a solution or two. For example, suppose you want to find the items in a list that have odd digit sums but don't want the items themselves. What you want to know is where they occurred in the original list. All that's required is a bit of indirection.[6]
First, you have a selection problem, so you use a grep. Let's not grep the values themselves but the index for each item: my @input_numbers = (1, 2, 4, 8, 16, 32, 64); my @indices_of_odd_digit_sums = grep { ... } 0..$#input_numbers; Here, the expression 0..$#input_numbers will be a list of indices for the array. Inside the block, $_ is a small integer, from 0 to 6 (seven items total). Now, you don't want to decide whether $_ has an odd digit sum. You want to know whether the array element at that index has an odd digit sum. Instead of using $_ to get the number of interest, use $input_numbers[$_]: my @indices_of_odd_digit_sums = grep { my $number = $input_numbers[$_]; my $sum; $sum += $_ for split //, $number; $sum % 2; } 0..$#input_numbers; The result will be the indices at which 1, 16, and 32 appear in the list: 0, 4, and 5. You could use these indices in an array slice to get the original values again: my @odd_digit_sums = @input_numbers[ @indices_of_odd_digit_sums ]; The strategy here for an indirect grep or map is to think of the $_ values as identifying a particular item of interest, such as the key in a hash or the index of an array, and then use that identification within the block or expression to access the actual values. Here's another example: select the elements of @x that are larger than the corresponding value in @y. Again, you'll use the indices of @x as your $_ items: my @bigger_indices = grep { if ($_ > $#y or $x[$_] > $y[$_]) { 1; # yes, select it } else { 0; # no, don't select it } } 0..$#x; my @bigger = @x[@bigger_indices]; In the grep, $_ varies from 0 to the highest index of @x. If that element is beyond the end of @y, you automatically select it. Otherwise, you look at the individual corresponding values of the two arrays, selecting only the ones that meet your match. However, this is a bit more verbose than it needs to be. You could simply return the boolean expression rather than a separate 1 or 0: my @bigger_indices = grep { $_ > $#y or $x[$_] > $y[$_]; } 0..$#x; my @bigger = @x[@bigger_indices]; More easily, you can skip the step of building the intermediate array by simply returning the items of interest with a map: my @bigger = map { if ($_ > $#y or $x[$_] > $y[$_]) { $x[$_]; } else { ( ); } } 0..$#x; If the index is good, return the resulting array value. If the index is bad, return an empty list, making that item disappear. |
[ Team LiB ] |