views:

2169

answers:

4

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?

+8  A: 
my @hash1s = sort {$a->{count} <=> $b->{count}} values %hash2;
Chris Jester-Young
I tried that way but get the following warning:[warning]: Use of uninitialized value in numeric comparison (<=>) at
systemsfault
The code snippet both for my answer, and for my comment to your answer, work for me when tested (even with -w and "use strict").
Chris Jester-Young
you are getting that warning because one or more of your hashes has no value for the 'count' key. If you want to count those as 0, you could do sort {($a->{count} || 0) <=> ($b->{count} ||0)} values %hash2;
nohat
no all my count values have 0 as a default value. But in my production code the key's name wasn't count it was total_cnt, but sort algorithm sorts in ascending order i need to sort in descending order.
systemsfault
@holydiver: Use $b->{count} <=> $a->{count} for descending order (i.e., swap a and b).
Chris Jester-Young
ok i sorted in descending order by using $b->{count} <=> $a->{count} instead of the above one. Thanx everyone :D.
systemsfault
+1  A: 

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};
}
Jagmal
Chris Jester-Young
I assume this will work this way also, won't it?
Jagmal
A: 

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";
lexu
what is '0+' for?
systemsfault
The 0+ is supposed to coerce the value to a numeric, however, <=> already does that, so the 0+ really is redundant. :-P
Chris Jester-Young
Well, the value that comes out of cmp or <=> are already numeric, being -1, 0, or 1. It doesn't matter what the data is. sort() needs -1, 0, or 1 to decide how to order things.
brian d foy
As Chris points out, I put the 0+ there to coerce the keys array into a scalar (thus holding the length of the array). Apparently <=> already does that .. I will continue to do so, since it makes it obvious to me, when re-reading the code, what is expected to happen. YMMD
lexu
+5  A: 

From perlfaq4, the answer to "http://faq.perl.org/perlfaq4.html#How_do_I_sort_a_hash" 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;
brian d foy
wow great explanation thanx.
systemsfault