An extremely useful builtin function in Perl is sort, which sorts an array. The default behaviour of sort is to use alphabetic sort. So, for instance, to read a list of phone numbers where each line is of the form name:phone number and print them out in alphabetical order we could write:
while ($line = <PHONE>){ ($name,$phone) = split /:/,$line; $phonehash{$name} = $phone; # Store phone number in a hash } foreach $k (sort keys %phonehash){ print $k, ":", $phonehash{$k},"\n"); }
Here, we sort the list generated by keys %phonehash before printout out the values in the hash.
What if we want to supply a different basis for comparing values? We
can supply sort with the name of a comparison function. Then
comparison function we supply will be invoked by sort. Instead
of using @_
to pass parameters to the comparison function (as
with normal functions), sort will pass the values as $a
and $b
. The comparison function should return if the
first argument, $a
, is smaller than the second argument,
$b$
, 0 if the two arguments are equal, and 1 if the second
argument is smaller than the first. So, we can sort using a numeric
comparison function as follows.
foreach $k (sort bynumber keys %somehash){ ... } sub bynumber { if ($a < $b) {return -1}; if ($a > $b) {return 1}; return 0; }
In fact, this is so common that Perl supplies a builtin ``spaceship'' operator <=> that has this effect.
sub bynumber { return $a <=> $b; # Return -1 if $a < $b, # +1 if $a > $b, # 0 if $a == $b }
The operator cmp achieves the same effect as <=> but using string comparison instead of numeric comparison.
To sort in descending order, we simply invert the position of the arguments in the comparison function.
foreach $k (sort bynumdesc keys %somehash){ ... } sub bynumberdesc {return $b <=> $a;}
We can also use the arguments $a
and $b
in more complex
ways. For instance, to print out a hash based on the numeric sorted order of
its values, we can write:
foreach $k (sort bystrvalue keys %somehash){ ... } sub bystrvalue {return $somehash{$a} cmp $somehash{$b};}
Finally, we can avoid using a separate named comparison function by just supplying the expression that is to be evaluated in braces, as follows:
foreach $k (sort {$a <=> $b} keys %somehash){ # Same as bynumber foreach $k (sort {$b <=> $a} keys %somehash){ # Same as bynumdesc foreach $k (sort {$somehash{$a} cmp $somehash{$b}} keys %somehash){ # Same as bystrvalue