Sort and the Schwartzian transform
| Default sort | |
| Numbers need a block | |
| Sort by a field | |
| Sort a hash by value | |
| The Schwartzian transform |
Default sort
sort with no block compares elements as strings, using the cmp operator. That is what you want for names and paths, and wrong for numbers.
my @names = qw(grace ada linus);
my @sorted = sort @names;
print "@sorted\n"; # ada grace linus
perl demo.pl
ada grace linus
sort returns a new list and leaves the original alone, which is one more reason to keep it inside an expression. To sort in place, assign the result back to the same array.
Numbers need a block
As strings, 10 comes before 9. Give sort a block that compares with the spaceship operator and the numbers line up.
my @nums = (10, 9, 100);
print join(" ", sort @nums), "\n"; # 10 100 9
print join(" ", sort { $a <=> $b } @nums), "\n"; # 9 10 100
perl demo.pl
10 100 9
9 10 100
Inside the block the two values being compared are $a and $b, package variables that sort fills in. Swap them for a descending order: $b <=> $a.
Sort by a field
For records, the block compares the field that matters. A tie-breaker in the same block keeps equal values in a stable order instead of an arbitrary one.
my @users = (
{ name => "Ada", age => 36 },
{ name => "Grace", age => 45 },
{ name => "Alan", age => 41 },
);
my @by_age = sort { $a->{age} <=> $b->{age} } @users;
print join(", ", map { $_->{name} } @by_age), "\n"; # Ada, Alan, Grace
perl demo.pl
Ada, Alan, Grace
Sort a hash by value
Hashes have no order, but keys can be sorted by their values. The classic form sorts keys and reads the hash inside the block.
my %hits = (index => 10, about => 2, blog => 7);
my @top = sort { $hits{$b} <=> $hits{$a} } keys %hits;
print "@top\n"; # index blog about
perl demo.pl
index blog about
Comparing $b with $a reverses the order, so the biggest number comes first. The same trick sorts any hash by any numeric value.
The Schwartzian transform
A sort block runs many times, so an expensive key function is called again and again. Build each key once, sort the pairs, then throw the keys away. The idiom is named after Randal Schwartz.
my @files = qw(b.log a.txt c.md);
my @by_ext = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, ($_ =~ /\.(\w+)$/)[0] ] }
@files;
print "@by_ext\n"; # b.log c.md a.txt
perl demo.pl
b.log c.md a.txt
Read it from the bottom: map builds pairs, sort compares the second slot, map takes the first slot back. For plain string sorts the extra machinery is not worth it, so use it only when the key is expensive.
Next: Read and write files
Article author: Arthur Isaev