Effective Perl by Joseph N. Hall

Observations and Tips from the author of Effective Perl Programming

Monday, January 30, 2006

Tip: Creating a String of Random Hex Digits

Every once in a while a string of random hex digits comes in handy*, perhaps when you need a random alphanumeric name for something. The unpack operator seems like it should come in handy for this purpose, given that it can translate character values into hex strings, and so it does. Here's a snippet that creates a string of 32 random hex digits:

my $rand_hex = join "", map { unpack "H*", chr(rand(256)) } 1..16;

rand(256) produces floating-point numbers 0.0 <= n < 256.0. unpack "H2" turns a character (a single-character string to be precise) into a 2-character MSB-first hex string - the chr is needed to turn the numeric value from rand into a string. The map { ... } 1..16 creates a list of 16 different 2-character strings, and join gloms them all together.

Here's another - simpler - way to generate 32 random hex digits:

use Digest::MD5 qw(md5_hex);
my $rand_hex2 = md5_hex(rand);

*Or maybe you think that Perl looks like random hex digits all on its own. Most people, however, think it looks like modem line noise $$817 d3#@ ss NO CARRIER

Monday, January 23, 2006

Tip: Reading a Few Lines from a File

How would you read a specific number of lines - say, 10 - from standard input? There's a plain old way:

my @lines;
for (my $i = 0; $i < 10; $i++) {
my $line = <STDIN>;
push @lines, $line;
}

This reads in 10 lines, one at a time, and appends them in order to the array @lines. Another perhaps cleverer way might be:

my @lines = map <STDIN>, 1..10;

I say "might" because this doesn't actually work. It looks as if you are using the ten numbers 1 through 10 to read 10 lines from standard input, which seems reasonable at first, because the .. ("dot-dot") operator returns a list of numbers when used in a list context. But the left hand expression you pass to map is also evaluated in a list context, and so the <STDIN> operator returns a list of all the remaining lines in the file. In other words, you ask for 10 lines but you get them all. You need to force a scalar context on the first argument in order to make <STDIN> to return one line at a time:

my @lines = map scalar(<STDIN>), 1..10;

Should there be fewer than 10 lines in your input, the array @lines will be padded with enough undefs to make up the difference because your attempts to read past end of file will return undef.

Wednesday, January 18, 2006

Plans for a New Edition of Effective Perl Programming?

I've been really pleased with all the great reviews that Effective Perl Programming has received over the years. The good sales are nice too - I'm still getting reasonable royalty checks.

I've been thinking of writing another edition for some time. At present I'm negotiating to do just that. Hopefully my work can be done by the end of the year, or maybe a little sooner.

Once I get going, I'll be posting bits of work in progress here. I will also, at some point, post the entire PDF of the current version. I may also ask for ideas about what to include. I think the rewrite will be somewhat different in its focus than the original.

Monday, January 16, 2006

Idiomatic Perl: Counting the Number of Times a Character Occurs in a String

From time to time you may want to count the number of occurrences of a character inside a string. You could loop from the beginning of the string to the end:
my $s = "Testing one two three.";

my $m = 0;
for (my $i = 0; $i < length($s); $i++) {
$m++ if (substr($s, $i, 1) eq 'e');
}
Ugly. This is going to take all day with all those calls to substr.* You could split the string. There's a special split pattern // that divides the input string into characters. So perhaps:
my $n = 0;
for (split //, $s) {
$n++ if $_ eq 'e';
}
Maybe this doesn't seem quite right to you yet. There must be a shorter way to do this in Perl, right? Yes. The tr/// (transliterate characters) operator can count characters for you. The tr/// operator returns the number of characters it changed. For example:
my $example = "Mixed Case";
my $changed = $example =~ tr/a-z/A-Z/; # make everything uppercase
print $changed, "\n"; # 9
The value returned from tr/// above is 9, which is the number of characters changed to uppercase. Armed with this knowledge, you might think of:
my $c = $example =~ tr/e/e/;  # change 'e' to 'e' ...
which will work just fine. This is perfectly reasonable, but there's a shortcut. If you omit the second argument to tr///, no characters are changed, but tr/// still returns a count of the character(s) in its first argument.
my $c = $example =~ tr/e//;  # counts the number of 'e' just as above

my $c_lower = $example =~ tr/a-z//; # counts lowercase letters in $example
tr/// is so smart, in fact, that it knows when you aren't changing the string, meaning that you can write things like this:
my $count_ignore_case = lc($example) =~ tr/t//;
Normally, tr/// requires a modifiable value, but if it won't be making changes, you can supply a constant argument like a result from an expression.

-joseph

*Not really, but ouch.

Saturday, January 14, 2006

Idiomatic Perl: The Hash as a Set

It's often useful to know whether a particular element or value exists in a set of items. For example, you might want to know whether the color 'red' exists in a list of colors. If you have this list in an array, you have to iterate over the contents of the array with a loop:
my @color = qw(green blue yellow cyan violet puce taupe lilac);
for my $item @color (
if ($item eq 'red') {
print "Found red.\n";
exit;
}
}
print "Found no red.\n";
This isn't terribly exciting and it is horribly inefficient with larger arrays. The idiomatic way of looking for something in a set in Perl is seeing whether it exists in a hash:
# retaining @color from above
my %color;
for my $item (@color) {
$color{$item} = 1; # 1 or any "true" value
}

if ($color{'red'}) {
print "Red found!\n";
}
Here I loaded the hash %color with the items in the original array @color. The items themselves are the keys. The values are something "true" - it doesn't matter what, just as long as it's "true," so I use 1. Now, $color{$foo} is true if the hash %color contains the item $foo.

There's a succinct way to create the hash above:
my %color = map { $_, 1 } @color;
This is essentially the same as the for loop, but shorter and more idiomatic.