6.6. Unnecessary Subscripting
Unless you actually need to know the indices of the array elements you're processing, iterate the values of an array directly: for my $client (@clients) { $client->tally_hours( ); $client->bill_hours( ); $client->reset_hours( ); } Iterating the indices and then doing repeated array accesses is significantly slower, and less readable: for my $n (0..$#clients) { $clients[$n]->tally_hours( ); $clients[$n]->bill_hours( ); $clients[$n]->reset_hours( ); } Repeated indexing is repeated computation; duplicated effort that incurs an extra cost but provides no added benefit. Iterating indices is also prone to off-by-one errors. For example: for my $n (1..@clients) { $clients[$n]->tally_hours( ); $clients[$n]->bill_hours( ); $clients[$n]->reset_hours( ); } Likewise, if you're processing the entries of a hash and you need only the values of those entries, don't iterate the keys and then look up the values repeatedly: for my $original_word (keys %translation_for) { if ( $translation_for{$original_word} =~ m/ $PROFANITY /xms) { $translation_for{$original_word} = '[DELETED]'; } } Repeated hash look-ups are even more costly than repeated array indexing. Just iterate the hash values directly: for my $translated_word (values %translation_for) { if ( $translated_word =~ m/ $PROFANITY /xms) { $translated_word = '[DELETED]'; } } Note that this last example works correctly because, in Perl 5.6 and later, the values function returns a list of aliases to the actual values of the hash, rather than just a list of copies (see "Hash Values" in Chapter 8). So if you change the iterator variable (for example, assigning '[DELETED]' to $translated_word), you're actually changing the corresponding original value inside the hash. The only situation where iterating values doesn't work correctly is when you need to delete the entries from the hash: for my $translated_word (values %translation_for) { if ( $translated_word =~ m/ $PROFANITY /xms) { delete $translated_word; # Compile-time error } } Here, aliasing isn't enough, because the delete builtin needs to know the key as well, so it will only accept actual hash look-ups as arguments. The correct solution is to use a hash slice instead (see Chapter 5): my @unacceptable_words = grep {$translation_for{$_} =~ m/ $PROFANITY /xms} keys %translation_for; delete @translation_for{@unacceptable_words}; The grep collects all those keys whose values must be removed, and stores that list in @unacceptable_words. The list of keys is then used to create a slice of the original hash (i.e., a list of hash look-ups), which can be passed to delete.
|