Avoid subscripting arrays or hashes within loops .
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.
By Any Other Name . . .An alias may sometimes seem like magic, but it's based in a very simple idea. In a Perl program, a normal variable consists of two distinct components: a storage location in memory, and a name (such as $foo) by which the program refers to that storage location. In other words, every variable is a box, with a "Hi-my-name-is..." sticker on it. Aliasing is the process of putting a second (or third, or n th) "Hi-my-name-is-also..." sticker on a single box. Perl subroutines do this all the time. For example, if you call get_passwd($user), then inside the call to get_passwd( ) the name $_[0] is temporarily attached to the container whose original name is $user. That container now has two names: one that's used inside the subroutine and one that's used outside. Anything you do to an alias (e.g., get its value, increment it, print it, assign a new value to it) is really being done to the original variablebecause there's really only one variable, no matter how many separate names you give it. |