Item 12: Use foreach , map and grep as appropriate.


Item 12: Use foreach , map and grep as appropriate.

In Perl, there are several different ways of iterating over elements in a list.

There is a strong tendency among Perl programmers to avoid using a for loop and subscripts when iterating through a list. Loops using subscripts tend to be slower than loops that don't, because subscripts take a significant amount of time for Perl to evaluate. In addition, subscripts can be used only on named arrays.

Most programmers use foreach , map , or grep instead. The capabilities of foreach , map , and grep overlap somewhat, but each is designed for a primary purpose. These constructs are easy to abuseyou can write pretty much any kind of loop with any one of thembut doing so can confuse you and anyone else who visits your code in the future. You should use them appropriately.

Use foreach to iterate read-only over each element of a list

If all you want to do is to cycle over the elements in a list, use foreach :

 foreach $cost (@cost) {    $total += $cost;  } 

Sum the values in @cost .

 foreach $file (glob '*') {    print "$file\n" if -T $file;  } 

List all the text files in the current directory.

Remember that foreach uses $_ as a control variable by default if none is specified. Also, you can always use the shorter keyword for instead of foreach Perl knows what you mean.

 foreach (1 .. 10) {    print "$_: ", $_ * $_, "\n";  } 

Print the first 10 squares.

 for (@lines) {    print, last if /^From:/;  } 

Print the first line beginning with From: .

Use map to create a list based on the contents of another list

If you want to create a transformed copy of a list, use map :

 @sizes = map { -s $_ } @files; 

Transform a list of filenames into a list of sizes.

The "transform" expression or block is evaluated in a list context. Sometimes it can be useful to return an empty list or a list of more than one element. Using a match operator inside map can be elegant:

Use m// and memory inside map to capture a list of matching substrings.

Both of the following examples use the match operator m// and parentheses inside map . In a list context, m// returns a list of the substrings captured in regular expression memory, or the empty list if the match fails.

 @stem = map { /(.*)\.txt$/ } @files; 

Find all the elements of @files that end in .txt and return a list of their " stems ."

 ($from) =    map /^From:\s+(.*)$/, @message_lines; 

Set $from to the text to the right of the first (hopefully only?) line starting with From: .

For efficiency, $_ is actually an alias for the current element in the iteration. If you modify $_ within the transform expression of a map , you are modifying the list that is being mapped. This is generally considered to be bad style, and, who knows, you may even wind up confusing yourself this way. If you want to modify the contents of a list, use foreach (see below).

You should also make sure that map is returning a sensible valuedon't use map as just a control structure:

map should return a sensible value.

This example breaks two of the rules concerning map . First, tr/// modifies $_ and thus @elems . Worse, the return value from map is a nonsensical list of values from tr/// the number of digits deleted by tr/// in each element.

 map { tr/0-9//d } @elems; 

PROBABLY WRONG

map should not modify its arguments.

This returns a sensible value but tr/// still modifies @elems .

 @digitless = map { tr/0-9//d; $_ } @elems; 

BAD STYLE

If you must use tr/// , s/// or something similar inside map , use a my variable to avoid changes to $_ :

Avoid changes to $_ by using a my variable inside map .

In this example, using tr/// on my $x and then returning $x leaves @elems aloneugly but functional.

 @digitless = map {    (my $x = $_) =~ tr/0-9//d; $x  } @elems; 

Strip digits from elements of @elems .

For more examples using map , see Item 14 and Item 60.

Use foreach to modify elements of a list

If you actually want to modify the elements in a list, use foreach . As with map (and also grep ), the control variable is an alias for the current element in the iteration. Modifying the control variable modifies that element.

 foreach $num (@nums) {    $num *= 2  } 

Multiply all the elements of @nums by 2.

 for (@ary) { tr/0-9//d } 

Strip digits from elements of @ary .

 for (@elems) { s/\d//g } 

Slower version using s/// .

 for ($str1, $str2, $str3) {     $_ = uc $_;  } 

Uppercase $str1 , $str2 , and $str3 .

Use grep to select elements in a list

The grep operator has a particular purpose, which is to select or count elements in a list. You wouldn't know this, though, looking at some of the more creative abuses of grep , which usually originate with programmers who feel that a foreach loop isn't quite as cool as a grep . Hopefully, as you try to write Effective Perl, you will stay closer to the straight and narrow.

Here's a conventional use of grep in a list context:

 print grep /^joseph$/i, @lines; 

Print 'joseph' lines, ignoring case.

 print grep    { lc($_) eq 'joseph' } @lines; 

Probably slightly faster.

By the way, the "selection" expression or block argument to grep is evaluated in a scalar context, unlike map 's transform expression. This will rarely make a difference, but it's nice to know.

In a scalar context, grep returns a count of the selected elements rather than the elements themselves .

 $has_false = grep !$_, @array; 

Returns count of "false" elements.

 $has_undef =    grep !defined($_), @array; 

Returns count of undef elements.



Effective Perl Programming. Writing Better Programs with Perl
Effective Perl Programming: Writing Better Programs with Perl
ISBN: 0201419750
EAN: 2147483647
Year: 1996
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net