Eric Fossum
Eric Fossum

Reputation: 2472

In Perl, how can I sort hash keys using a custom ordering?

I am trying to do work on a hash of files and the work has to be done in a specific order. Most would say the list can be ordered like so:

for my $k (sort keys %my_hash)
{
    print "$k=>$my_hash{$k}, ";
}

However, I need nonalphabetical order, in fact the keys start with a word then _ and they go G to digits to L to any of M,P,R,T or D (eg. word_G.txt,word_2.txt,...,word_P.txt). Is there any way to sort by custom order?

Upvotes: 5

Views: 4761

Answers (3)

xafwodahs
xafwodahs

Reputation: 31

I had a specific use case where I wanted to sort with certain values first, other values last, then everything else alphabetically in the middle.

Here's my solution:

my @sorted = sort {
    my @order = qw(Mike Dave - Tom Joe);
    my ($x,$y) = (undef,undef);
    for (my $i = 0; $i <= $#order; $i++) {
        my $token = $order[$i];
        $x = $i if ($token eq $a or (not defined $x and $token eq "-"));
        $y = $i if ($token eq $b or (not defined $y and $token eq "-"));
    }
    $x <=> $y or
    $a cmp $b
} @ARGV;

Output:

$ perl customsort.pl Tom Z Mike A Joe X Dave G
Mike Dave A G X Z Tom Joe

Upvotes: 0

daxim
daxim

Reputation: 39158

use 5.014;

sub rank {
    my ($word) = @_;
    $word =~ s{\A \w+ _}{}msx;
    return do {
        given ($word) {
            0 when /\A G/msx;
            1 when /\A [0-9]/msx;
            2 when /\A L/msx;
            3 when /\A [MPRTD]/msx;
            default { 1000 };
        }
    };
}

say for sort { rank($a) <=> rank($b) } qw(word_P.txt word_2.txt word_G.txt);

Output:

word_G.txt
word_2.txt
word_P.txt

Edit: Before Perl 5.14, use a temporary variable.

use 5.010;
⋮
return do {
    my $dummy;
    given ($word) {
        $dummy = 0 when /\A G/msx;
        $dummy = 1 when /\A [0-9]/msx;
        $dummy = 2 when /\A L/msx;
        $dummy = 3 when /\A [MPRTD]/msx;
        default { $dummy = 1000 };
    }
    $dummy;
};

Upvotes: 4

Sinan &#220;n&#252;r
Sinan &#220;n&#252;r

Reputation: 118166

Is there any way to sort by custom order?

Yes. See sort.

For example:

#!/usr/bin/env perl

use warnings; use strict;

my @order = qw(G 1 2 3 L M P R T D);

my %order_map = map { $order[$_] => $_ } 0 .. $#order;

my $pat = join '|', @order;

my @input = qw(word_P.txt word_2.txt word_G.txt);

my @sorted = sort {
    my ($x, $y) = map /^word_($pat)[.]txt\z/, $a, $b;
    $order_map{$x} <=> $order_map{$y}
} @input;

print "@sorted\n";

Upvotes: 13

Related Questions