开发者

In Perl,how do you sort a lexical hash by its value with a dedicated subroutine?

开发者 https://www.devze.com 2023-03-17 14:11 出处:网络
This works: my %score = ( a => 1, b => 2); @keys = sort {$score{$a} <=> $score{$b}} keys %score;开发者_如何学Python

This works:

my %score = ( a => 1, b => 2);
@keys = sort {$score{$a} <=> $score{$b}} keys %score;开发者_如何学Python

But how can I put the code inside {..} to a dedicated sub routine?

sub by_num {
  $score{$a} <=> $score{$b}
}

@keys = sort by_num keys %score;

?


The main problem here is having a subroutine that has access to the hash. You either have to create one function per hash you want to sort:

#!/usr/bin/perl

use strict;
use warnings;

{
    my %hash = (
        a => 1,
        b => 2,
        c => 3,
    );

    sub sort_hash_a {
        return $hash{$a} <=> $hash{$b};
    }

    for my $k (sort sort_hash_a keys %hash) {
        print "$k\n";
    }
}

{
    my %hash = (
        x => 1,
        y => 2,
        z => 3,
    );

    sub sort_hash_b {
        return $hash{$a} <=> $hash{$b};
    }

    for my $k (sort sort_hash_b keys %hash) {
        print "$k\n";
    }
}

Or create a higher-order function that creates the functions for you:

#!/usr/bin/perl

use strict;
use warnings;

sub make_hash_sort {
    my $hashref = shift;

    return sub {
        return $hashref->{$a} <=> $hashref->{$b};
    };
}

my %hash = (
    one   => 1,
    two   => 2,
    three => 3,
);

my $sub = make_hash_sort \%hash;

for my $k (sort $sub keys %hash) {
    print "$k\n";
}

But all of that is, generally, wasted effort for both the programmer and the computer. The block syntax is faster and easier to use in almost all cases. The only exceptions would be complicated sorts or highly repetitive code.


A hash produces a list of key/value pairs in list context; you're then using the values as keys, and there is no e.g. $score{90}. Use keys %score to get just the keys.


Example passing the sort routine both key and value, requiring some manipulation both before and after the sort:

sub sordid {
    $a->[1] <=> $b->[1];
}

my %score = ( a => 1, b => 2 );
@keys = map $_->[0], sort sordid map [ $_ => $score{$_} ], keys %score;

or using the alternate element-passing flavor of sort (triggered by a prototype):

sub sordid($$) {
    $_[0][1] <=> $_[1][1];
}

my %score = ( a => 1, b => 2 );
@keys = map $_->[0], sort sordid map [ $_ => $score{$_} ], keys %score;

(This is necessary for sort routines designed to be called from other packages, since $a and $b are package variables in the package where sort is called.)


With a closure!

sub hashsort {
  my %hash = @_;
  return sub { $hash{$a} <=> $hash{$b}; }
}

my $sorter = hashsort(%hash);
@keys = sort $sorter keys %hash;

Ought to work. And no, it would never "remake %hash on every call" because hashsort would only be called once, and the function it returns would be called repeatedly. Though given the increasingly less attractive syntax you might want to abstract the whole thing into a function, i.e.

sub sortkeys (\%) {
  my $hash = shift;
  return sort { $hash->{$a} <=> $hash->{$b} } keys %$hash;
}

@keys = sortkeys %hash;

You may prefer the unprototyped version, and pass the hash as a reference manually (or not as a reference at all).


Don't over-think it, if you want to sort by number, just do that:

%score = ( a => 1, b => 2);

sub keys_sorted_by_num {
  my %h = @_;
  return sort {$h{$a} <=> $h{$b}} keys %h;
}

@keys = keys_sorted_by_num %score;

Less magic makes for clearer code...


Example for sorting in ASC and DESC:

    use strict;

    my %hash = (
        four   => 4,
        one   => 1,
        two   => 2,
        five   => 5,
        three => 3,
    );


    my $sub_asc = make_hash_sort(\%hash,'asc');

    print "--- ASC ---\n";
    for my $k (sort $sub_asc keys %hash) {
        print "$k\t\t$hash{$k}\n";
    }


my $sub_desc = make_hash_sort(\%hash,'desc');

print "\n--- DESC ---\n";
for my $k (sort $sub_desc keys %hash) {
    print "$k\t\t$hash{$k}\n";
}



###  Sort hash by value
sub make_hash_sort {
    my ($hashref, $how) = @_;
    return sub {
        if ($how eq 'asc') {
            return $hashref->{$a} <=> $hashref->{$b};
        } elsif ($how eq 'desc') {
            return $hashref->{$b} <=> $hashref->{$a};
        }
    };
}
0

精彩评论

暂无评论...
验证码 换一张
取 消