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