Reputation: 1792
Similar to this question: Sort by subset of a Perl string
I would like to sort first by value, and then by subset of the key.
My %hash
:
cat_02 => 0
cat_04 => 1
cat_03 => 0
cat_01 => 3
The output (could be an array of the keys in this order):
cat_02 => 0
cat_03 => 0
cat_04 => 1
cat_01 => 3
Bonus: The key secondary comparison would recognize 1234_2_C01 and being smaller than 1234_34_C01 (cmp does not).
Upvotes: 3
Views: 4590
Reputation:
Use:
my %hash = (
cat_02 => 0,
cat_04 => 1,
cat_03 => 0,
cat_01 => 3
);
print "$_ => $hash{$_}\n"
for sort { $hash{$a} <=> $hash{$b} or $a cmp $b } keys %hash;
The sort does numeric comparison of the values, and if they're equal, the part after the or
is executed, which does string comparison of the keys. This gives the output you asked for.
For smart sorting of strings that contain numbers mixed with non-numeric stuff, grab the alphanum comparison function from The Alphanum Algorithm and replace $a cmp $b
with alphanum($a,$b)
.
Upvotes: 10
Reputation: 385655
This can easily be done (quickly!) using the Sort::Key:: modules:
use Sort::Key::Natural qw( );
use Sort::Key::Maker intnatkeysort => qw( integer natural );
my @sorted_keys = intnatkeysort { $hash{$_}, $_ } keys(%hash);
Or you can take advantage of the properties of your data and just use a natural sort:
use Sort::Key::Natural qw( natkeysort );
my @sorted_keys = natkeysort { "$hash{$_}-$_" } keys(%hash);
Upvotes: 3
Reputation: 106385
It may be not worth it in this particular case, but Schwartzian transform technique can be used with multi-criteria sort too. Like this (codepad):
use warnings;
use strict;
my %myhash = (
cat_2 => 0, cat_04 => 1,
cat_03 => 0, dog_02 => 3,
cat_10 => 0, cat_01 => 3,
);
my @sorted =
map { [$_->[0], $myhash{$_->[0]}] }
sort { $a->[1] <=> $b->[1] or $a->[2] <=> $b->[2] }
map { m/([0-9]+)$/ && [$_, $myhash{$_}, $1] }
keys %myhash;
print $_->[0] . ' => ' . $_->[1] . "\n" for @sorted;
Obviously, the key to this technique is using more than one additional element in the cache.
Two things here: 1) @sorted
actually becomes array of arrays (each element of this array is key-value pair); 2) sorting in this example is based on keys' digits suffix (with numeric, not string comparison), but it can be adjusted in any direction if needed.
For example, when keys match pattern XXX_DD_XXX (and it's DD that should be compared), change the second map
clause with this:
map { m/_([0-9]+)_/ && [$_, $myhash{$_}, $1] }
Upvotes: 1
Reputation: 67900
When you have a secondary sort preference, you simply add another level inside the sort routine:
my %hash = (
cat_02 => 0,
cat_04 => 1,
cat_03 => 0,
cat_01 => 3
);
my @sorted = sort { $hash{$a} <=> $hash{$b} || $a cmp $b } keys %hash;
# primary sort method ^^ secondary sort method
for (@sorted) {
print "$_\t=> $hash{$_}\n";
}
Output:
cat_02 => 0
cat_03 => 0
cat_04 => 1
cat_01 => 3
Upvotes: 4