http://www.perlmonks.org?node_id=348119

coldfingertips has asked for the wisdom of the Perl Monks concerning the following question:

I have a hash I need sorted..TWICE. I asked this in the chat box but that got confusing, so here's the problem.

I need to sort a hash by values (the values are all whole numbers) but in some cases, many of the hash keys have the same value. How can I then sort these alphabetically?

Unsorted

rock => 3 candle => 25 bug => 3 rain => 12 dust => 17 spider => 12
Using foreach (sort {$saved_key{$b} cmp $saved_key{$a}} keys %saved_key)
candle => 25 dust => 17 spider => 12 rain => 12 rock => 3 bug => 3
Desired:
candle => 25 dust => 17 rain => 12 spider => 12 bug => 3 rock => 3
If two or more values are the same, I want to sort them alphanumerically. Any help would be much appreciated.

Replies are listed 'Best First'.
Re: Twice the pleasure of sorting a hash
by broquaint (Abbot) on Apr 26, 2004 at 09:52 UTC
    A double comparison should do it e.g
    my %h = ( rock => 3, candle => 25, bug => 3, rain => 12, dust => 17, spider => 12, ); for( sort { $h{$b} <=> $h{$a} || $a cmp $b } keys %h ) { print "$_ => $h{$_}\n"; } __output__ candle => 25 dust => 17 rain => 12 spider => 12 bug => 3 rock => 3
    So firstly we sort by value in descending order then optionally sort by key value.
    HTH

    _________
    broquaint

      That works perfectly, you're the best!! Thanks so much for your help broquaint!!
Re: Twice the pleasure of sorting a hash
by Limbic~Region (Chancellor) on Apr 26, 2004 at 12:14 UTC
    coldfingertips,
    It seems that people often want to use a hash so they can have key lookup functionality but also want the hash to come out in a specific order as well.

    If you want the hash to come out in the order it was created, than Tie::IxHash is probably for you ( and if you need speed look at Tie::Hash::Indexed which is written in XS ). Do not use these modules if you need your hash sorted though. While it does provide 3 methods to resort the hash (user provided key list or ASCIIBetically by keys/values), it does not provide any mechanism for retaining auto-sorting new keys/values.

    This is one of the reasons I wrote Tie::Hash::Sorted. It allows the user to define their own sort routine (even using lexicals the module wouldn't normally see) as well as numerous optimization options to choose from. Your code would look something like:
    #!/usr/bin/perl use strict; use warnings; use Tie::Hash::Sorted; my $sort = sub { my $hash = shift; [ sort { $hash->{$b} <=> $hash->{$a} || $a cmp $b } keys %$hash ]; }; tie my %sorted_data, 'Tie::Hash::Sorted', 'Sort_Routine' => $sort; %sorted_data = ( rock => 3, candle => 25, bug => 3, rain => 12, dust => 17, spider => 12 ); print "$_ : $sorted_data{$_}\n" for keys %sorted_data;
    Modifying the hash does not require any additional work to keep the sorted order.

    Cheers - L~R

Re: Twice the pleasure of sorting a hash
by ph0enix (Friar) on Apr 26, 2004 at 09:57 UTC

    Use following code for sorting

    sort {$tmp = $saved_key{$b} <=> $saved_key{$a}; $tmp = $a cmp $b if ($tmp == 0); $tmp} keys %saved_key

    Temporary variable is not needed of course

      While your code works, it's one of those occasions where you're making code less readable by being more explicit.
      ... sort { $h{$b} <=> $h{$a} || $a cmp $b } keys %saved_key;
      Unlike C or Java, the Perl operator || keeps the actual true value. (C or Java would just return 1; but they don't have <=> either.) This means (among other things) that Perl can cascade a number of possible criteria in a single statement. By avoiding the visual clutter of temporary variables and unnecessary checks against zero, your code can be more readable.

      This is the Perl idiom. List each criteria from most important to least important. The first criteria which returns a -1 or 1 will decide the sort order; any criteria returning 0 will fall through to the next part of the expression.

      my @sorted = sort { $hash{$b} <=> $hash{$a} || $a cmp $b || func($a) <=> func($b) || $other{name}{$b} cmp $other{name}{$a} } @unsorted;
      I find that a newline after each criteria helps the reader immediately understand what's going on. (The extra spacing on each line is optional.)

      This newline on each phase technique is useful of any of the Perl-style "list pipelines" which are common, such as those with colorful names like the Schwartzian Transform or the Orcish Maneuver.

      --
      [ e d @ h a l l e y . c c ]

Re: Twice the pleasure of sorting a hash
by Anonymous Monk on Apr 26, 2004 at 19:00 UTC
    I used: sort {$saved_key{$b} <=> $saved_key{$a} || $a cmp $b} The first term returns zero if the values are the same (note the numeric compare, the string compare does not provide the sort show in the example). This in turn triggers the text compare on the keys. Worked for me.
Re: Twice the pleasure of sorting a hash
by Anonymous Monk on Apr 26, 2004 at 20:19 UTC
    try this:
    
    foreach (sort { ($saved_key{$b} <=> $saved_key{$a}) || ($a cmp $b) } keys %saved_key)
    
    
    That sorts the hash by values (numerically), and then by keys (alphabetically)