views:

554

answers:

3

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);
+8  A: 

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.

Blair Conrad
+13  A: 

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.

Michael Carman
+1  A: 

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;
ephemient