QM has asked for the wisdom of the Perl Monks concerning the following question:
I want a custom sort sub that can sort any hash, according to whatever sort mechanism I dream up, with the hash determined at runtime.
The examples I've been able to find all assume a specific global hash. Sort subs don't allow any parameter passing, so I'm wondering how to accomplish this with grace and efficiency. Short of an anonymous sub created for a specific hash, I'm not sure how to proceed.
As a counterexample, this doesn't cut it:
sub by_score { $score{$b} <=> $score{$a} }
Here, %score is hardcoded. I may want to use the same sub on more than 1 hash throughout the script, and would prefer not to write one for each hash.
-QM
--
Quantum Mechanics: The dreams stuff is made of
Re: Custom, Reusable Sort Subroutine for Hashes?
by choroba (Cardinal) on Aug 30, 2017 at 11:09 UTC
|
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
sub sort_hash (&\%) {
my ($sub, $hash) = @_;
sort {
local($a, $b) = @$hash{$a, $b};
$sub->()
} keys %$hash
}
my %hash = ( second => 2, fourth => 4, third => 3, first => 1 );
say for sort_hash { $a <=> $b } %hash;
I used a prototype to make the syntax of sort_hash similar to that of sort. The trick is replacing the magical variables $a and $b with the respective values from the hash.
Update: explanation added.
($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord
}map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
| [reply] [d/l] [select] |
|
| [reply] |
|
sub numerically { $a <=> $b }
say for sort_hash(\&numerically, %hash);
($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord
}map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
| [reply] [d/l] [select] |
|
Re: Custom, Reusable Sort Subroutine for Hashes?
by Corion (Patriarch) on Aug 30, 2017 at 11:15 UTC
|
Personally, I would create an anonymous subroutine that knows the correct hash to use. I think the fancy term would be "currying":
my @list = (qw(
a b c d e f
));
sub by_score {
my( $score ) = @_;
return sub($$) {
my( $a,$b ) = @_;
warn "$a / $b";
$score->{$a} <=> $score->{$b}
}
}
my $by = by_score({ a => 5, b => 4, c => 3, d => 6, e => -1 });
print join ",", sort( {$by->($a,$b)} @list );
$by = by_score({ a => -5, b => 4, c => -3, d => 6, e => -1 });
print join ",", sort( {$by->($a,$b)} @list );
I'm a bit unhappy with the (lack of) syntax that prevents me from inlining $by. I would have liked to write something like:
$by = by_score({ a => -5, b => 4, c => -3, d => 6, e => -1 });
print join ",", sort( $by, @list );
... but that's nothing that Perl likes, as Perl doesn't know whether $by should be part of @list or not. | [reply] [d/l] [select] |
|
Yes, very helpful.
I was anticipating a sub generator of some sort, where the hash and comparison function are passed in, and a new anonymous sub is returned. This sub could then be used in the code block position of the sort command.
Perhaps something like this, which steals some ideas from Choroba's post, though I've not tried it:
sub make_sort_sub {
my $coderef = shift;
my $hashref = shift;
my $sort_sub = sub { something goes here }
return $sort_sub;
}
my %hash = ( a => 5, b => 4, c => 3 );
my $keys_by_value = make_sort_sub(
{ $hashref->{$a} <=> $hashref->{$b} },
\%hash);
my @keys_sorted_by_value = sort $keys_by_value %hash;
Then $keys_by_value can be reused elsewhere on the same hash. But can't be used on a different hash. :(
Hmmm, I'm thinking there's not a really elegant solution.
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [d/l] [select] |
Re: Custom, Reusable Sort Subroutine for Hashes?
by QM (Parson) on Sep 05, 2017 at 16:58 UTC
|
Combining some of these ideas, I came up with the following make_sort_sub:
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw{ say };
use Scalar::Util qw{ looks_like_number };
sub make_sort_sub {
my $code = shift;
my $hashref = shift;
my $sort_sub = eval "sub { $code }";
die $@ if $@;
return $sort_sub;
}
# simple code snippet
my %hash = ( a => 5, b => 4, c => 3, d => 5, e => 4 );
my $keys_by_value = make_sort_sub(
'$hashref->{$a} <=> $hashref->{$b}',
\%hash);
my @keys_sorted_by_value = sort $keys_by_value keys %hash;
say @keys_sorted_by_value;
# Try with curlies now
$keys_by_value = make_sort_sub(
'{$hashref->{$a} <=> $hashref->{$b}}',
\%hash);
@keys_sorted_by_value = sort $keys_by_value keys %hash;
say @keys_sorted_by_value;
# Naive compare by values as numbers, keys as strings
my $keys_by_value_or_keys = make_sort_sub(
'$hashref->{$a} <=> $hashref->{$b} or $a cmp $b',
\%hash);
my @keys_by_value_or_keys = sort $keys_by_value_or_keys keys %hash;
say @keys_by_value_or_keys;
# Compare as numbers then strings, values then keys
my %hash2 = (a => 'a', b => 'b', c => 3, d => 4);
my $keys_by_value_or_keys_mixed = make_sort_sub(
'return $hashref->{$a} <=> $hashref->{$b}
if ((looks_like_number($hashref->{$a}) and looks_like_number(
+$hashref->{$b}))
and ($hashref->{$a} <=> $hashref->{$b}));
return $hashref->{$a} cmp $hashref->{$b}
if ($hashref->{$a} cmp $hashref->{$b});
return $a <=> $b
if ((looks_like_number($a) and looks_like_number($b)) and ($a
+<=> $b));
return $a cmp $b;',
\%hash2);
my @keys_by_value_or_keys_mixed = sort $keys_by_value_or_keys_mixed ke
+ys %hash2;
say @keys_by_value_or_keys_mixed;
exit;
Output:
cbeda
cbeda
cbead
cdab
I couldn't figure out a good way to include a real code block, because it couldn't reference the keys and values. Choroba's suggestion is nice, but can't really handle anything other than $a/$b (AFAIK). Chicken and egg.
As an aside, I can't remember if there's a nice CPAN way to do the last sub ("keys by value or by keys, numbers or strings").
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [d/l] [select] |
|
Sort::Maker?
«The Crux of the Biscuit is the Apostrophe»
perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help
| [reply] [d/l] |
|
++ for trying, but that doesn't seem to be easier.
In fact, I think the doc page is one of those "well formed, but not very informative" examples. I was looking for an example in the synopsis, and it has this:
use Sort::Maker ;
my $sorter = make_sorter( ... );
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [d/l] |
|
A reply falls below the community's threshold of quality. You may see it by logging in. |
|
|