systemsfault
systemsfault

Reputation: 15527

How can I sort a hash of hashes by key in Perl?

I want to sort a hash which actually has a hash as a value. For instance:

my %hash1=(
   field1=>"",
   field2=>"",
   count=>0,
);
my %hash2;
$hash2{"asd"}={%hash1};

and I inserted lots of hashes to %hash2 with different count values of %hash2.

How can I sort the %hash1 according to a count value of hash1?

Is there a way of doing this without implementing quicksort manually, for instance with the sort function of Perl?

Upvotes: 6

Views: 13673

Answers (5)

brian d foy
brian d foy

Reputation: 132783

From perlfaq4, the answer to How do I sort a hash (optionally by value instead of key)? has most of the information you need to put together your code.

You might also want to see the chapter on Sorting in Learning Perl.

Chris has a completely fine answer, although I hate using values like that. A more familiar way to do the same thing is to go through the keys of the top-level hash but sort by the second-level key:

my @sorted_hashes = 
    sort { $hash2->{$a}{count} <=> $hash2->{$b}{count} } 
    keys %hash2;

I do it this way because it's a little less mind-bending.


How do I sort a hash (optionally by value instead of key)?

(contributed by brian d foy)

To sort a hash, start with the keys. In this example, we give the list of keys to the sort function which then compares them ASCIIbetically (which might be affected by your locale settings). The output list has the keys in ASCIIbetical order. Once we have the keys, we can go through them to create a report which lists the keys in ASCIIbetical order.

my @keys = sort { $a cmp $b } keys %hash;

foreach my $key ( @keys )
    {
    printf "%-20s %6d\n", $key, $hash{$key};
    }

We could get more fancy in the sort() block though. Instead of comparing the keys, we can compute a value with them and use that value as the comparison.

For instance, to make our report order case-insensitive, we use the \L sequence in a double-quoted string to make everything lowercase. The sort() block then compares the lowercased values to determine in which order to put the keys.

my @keys = sort { "\L$a" cmp "\L$b" } keys %hash;

Note: if the computation is expensive or the hash has many elements, you may want to look at the Schwartzian Transform to cache the computation results.

If we want to sort by the hash value instead, we use the hash key to look it up. We still get out a list of keys, but this time they are ordered by their value.

my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;

From there we can get more complex. If the hash values are the same, we can provide a secondary sort on the hash key.

my @keys = sort {
    $hash{$a} <=> $hash{$b}
        or
    "\L$a" cmp "\L$b"
    } keys %hash;

Upvotes: 6

Lance Cleveland
Lance Cleveland

Reputation: 3204

To sort by numeric use <=> and for string use cmp.

# sort by the numeric count field on inner hash
#
foreach my $key (sort {$hash2{$a}->{'count'} <=> $hash2{$b}->{'count'}} keys %hash2) {
   print $key,$hash2{$key}->{'count'},"\n";
}

# sort by the string field1 (or field2) on the inner hash
#
foreach my $key (sort {$hash2{$a}->{'field1'} cmp $hash2{$b}->{'field1'}} keys %hash2) {
   print $key,$hash2{$key}->{'field1'},"\n";
}

To reverse order simply swap $a and $b:

# sort by the numeric count field on inner hash
#
foreach my $key (sort {$hash2{$a}->{'count'} <=> $hash2{$b}->{'count'}} keys %hash2) {
   print $key,$hash2{$key}->{'count'},"\n";
}

# sort by the string field1 (or field2) on the inner hash
#
foreach my $key (sort {$hash2{$a}->{'field1'} cmp $hash2{$b}->{'field1'}} keys %hash2) {
   print $key,$hash2{$key}->{'field1'},"\n";
}

Upvotes: 0

lexu
lexu

Reputation: 8849

See http://perldoc.perl.org/functions/sort.html for lot's of context how sort works in Perl.

And here's an example .. trying to be readable, not perlish.

#!/usr/bin/perl
# Sort Hash of Hashes by sub-hash's element count.
use warnings;
use strict;


my $hash= {
            A=>{C=>"D",0=>"r",T=>"q"}
           ,B=>{}
           ,C=>{E=>"F",G=>"H"}
          };

sub compareHashKeys {0+(keys %{$hash->{$a}}) <=> 0+(keys %{$hash->{$b}}) }

my @SortedKeys = sort compareHashKeys keys %{$hash};
print join ("," , @SortedKeys) ."\n";

Upvotes: 0

Jagmal
Jagmal

Reputation: 5966

If you want to get the list of hashes (like hash1) sorted by the count from the values in hash2, this may help:

@sorted_hash1_list = sort sort_hash_by_count_key($a, $b) (values (%hash2);


# This method can have any logic you want
sub sort_hash_by_count_key {
    my ($a, $b) = @_;
    return $a->{count} <=> $b->{count};
}

Upvotes: 1

C. K. Young
C. K. Young

Reputation: 223003

my @hash1s = sort {$a->{count} <=> $b->{count}} values %hash2;

Upvotes: 10

Related Questions