sort with lexicographic order
-
06-07-2019 - |
Question
I see the results from the following code, but I don't understand exactly how the or
knows what to do in the following sort
example:
use Data::Dumper;
$animals{'man'}{'name'} = 'paul';
$animals{'man'}{'legs'} = 2;
$animals{'cheeta'}{'name'} = 'mike';
$animals{'cheeta'}{'legs'} = 3;
$animals{'zebra'}{'name'} = 'steve';
$animals{'zebra'}{'legs'} = 4;
$animals{'cat'}{'name'} = '';
$animals{'cat'}{'legs'} = 3;
$animals{'dog'}{'name'} = '';
$animals{'dog'}{'legs'} = 4;
$animals{'rat'}{'name'} = '';
$animals{'rat'}{'legs'} = 5;
@animals = sort {
$animals{$a}{'name'} cmp $animals{$b}{'name'}
or $animals{$a}{'legs'} <=> $animals{$b}{'legs'}
} keys %animals;
print Dumper(\@animals);
Solution
or
is a short-circuit evaluator, so it will return the value of the left-hand side if it's true (which is any non-zero value), and otherwise will evaluate the right-hand side.
So in this case, if the animals' names compare as equal, (0 - false), the number of legs will be counted for sorting purposes.
OTHER TIPS
The sortsub (the stuff in {}
after the sort
) defines a two-tier sort: first by name, then by number of legs. The or
implements the cross-over between the two criteria. It's easier to see if you format the code differently:
@animals = sort {
$animals{$a}{'name'} cmp $animals{$b}{'name'} or
$animals{$a}{'legs'} <=> $animals{$b}{'legs'}
} keys %animals;
The cmp
and <=>
operators return one of three values (-1, 0, or 1) depending on whether the left argument is less than, equal to, or greater than the right argument. (cmp
does a string comparison, <=>
does a numeric one.) In Perl, 0 is false while -1 and 1 are true. If the cmp
returns a true value, the or
returns that value immediately, and sort
reorders the elements appropriately. If the cmp
returns false, the <=>
is evaluated and its result is returned instead.
When doing multi-layer sorts, it's common to use a "map-sort-map" technique (a.k.a. Schwartzian Transform):
@animals =
map { $_->[0] }
sort {
$a->[1] cmp $b->[1] ||
$a->[2] <=> $b->[2]
}
map { [$_, $animal{$_}{name}, $animal{$_}{legs}] }
keys %animal;
It's not as clear but because it usually has better performance it's a common idiom. This is especially important when the operands to the comparison are functions -- this technique prevents an unnecessary (and possibly expensive) recalculation for every comparison. For example, if you're sorting strings by length you only need to compute the length of each string once.
May I suggest Sort::Key
as an alternative for the present code altogether?
use Sort::Key::Multi qw(sikeysort); # sort keyed on (string, integer)
@animals = sikeysort { $animals{$_}{name}, $animals{$_}{legs} } keys %animals;
# alternately,
use Sort::Key::Maker sort_by_name_then_legs =>
sub { $animals{$_}{name}, $animals{$_}{legs} }, qw(string integer);
@animals = sort_by_name_then_legs keys %animals;