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

(Assume i don't know if there is any :) )
Why there's no function to sort hashes by values (sort() can do on keys). Below is my attempt to implement sort by values :
This function will populate @sorted with sorted keys, so we can list out sorted values using the keys
%unsorted_hash = (k1=> 'v2', k2=> 'v1'); sortvalues(\@sorted, \%unsorted_hash); foreach (@sorted,) { print "$unsorted_hash{$_}\n"; } #---------------------------------- # sorting hashes based on values # in : array of sorted keys (output), hashes to use # out : none #---------------------------------- sub sortvalues($$) { my ($sorted, $hashes) = @_; sub swap($$) { my ($a, $b) = @_; my $temp = $$a; $$a = $$b; $$b = $temp; } for my $loop (0..(scalar @$sorted - 1)) { my $ub = scalar @$sorted - $loop - 2; for my $i (0..$ub) { if (($$hashes{$$sorted[$i]} <=> $$hashes{$$sorted[$i+1]}) > + 0 ) { swap (\$$sorted[$i], \$$sorted[$i+1]); } } } }

Replies are listed 'Best First'.
Re: Want to sort hashes by values, anyone?
by dragonchild (Archbishop) on May 11, 2004 at 14:37 UTC
    my @sorted_values = sort values %unsorted;

    Additionally, your swap function should be written as: sub swap ($$) { @_[0,1] = @_[1,0] } Or, better yet, instead of calling swap, you can do: @sorted[$i, $i+1] = @sorted[$i+1, $i];

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      I like the
      @sorted[$i, $i+1] = @sorted[$i+1, $i];
      Since most language (in my concern) don't have this kind of thing
Re: Want to sort hashes by values, anyone?
by runrig (Abbot) on May 11, 2004 at 14:38 UTC
    Uh...
    my @sorted_keys_by_value = sort { $unsorted_hash{$a} cmp $unsorted_hash{$b} } keys %unsorted_hash;
    And you probably want to use 'cmp' instead of '<=>'. '<=>' is for numerical comparison, 'cmp' is for strings. See perlop.
      just ignore for my purpose (i use numbers). cmp not produce what i expect.
      '9' will be greater than '11'
      This not what i want. Anyway, i realize cmp still more preferrable if multi purposes
        Anyway, i realize cmp still more preferrable if multi purposes

        No, you cannot make general statements like that and be right. Basically, what we're trying to say is you are reinventing the wheel, and poorly at that. Use the tools that have been made for you so you can build what you're being paid to build. (I doubt your employer wants to pay you for sorting hashes by value ...)

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

        So you want to sort numerically, that's okay, but then neither a simple cmp nor <=> will work. <=> would work if your values were suffixed by non-numeric characters, but not prefixed the way you have them. In that case my answer would probably be a Schwartzian Transform, but would vary depending on how generic I want my answer to be, and what I know of the data (e.g. does it always start with a 'v'? do I just want to extract the first number out of it and sort by that? should I consider the character portion in the sort? etc.).

        And your answer does not work. If it does seem to work in some version of perl for some set of data, then it is a matter of luck.

      I never invent wheels, honest!
      Just kidding. I realize that, because i'm just a Perl's 'magot'. But i'm still insist on reinventing, since i enjoy to do it :) (that's not in my task's list, though) Anyway, i made the statement, since i've seen most scalar type can be treated as string, and cmp is suitable for comparison in general. It is safer, although not efficient (need to do conversion).
Re: Want to sort hashes by values, anyone?
by Zaxo (Archbishop) on May 11, 2004 at 14:41 UTC

    Hashes are unordered by design. You may sort on them all you like and havo no effect on the order returned by keys, values, or each. What you can do, is make a sorted array of the keys or values based on the values.

    my @sorted_keys = sort {$hash{$a} cmp $hash{$b}} keys %hash; print join ' ', @hash{@sorted_keys};
    The useful construction in the print statement is called a "hash slice".

    After Compline,
    Zaxo

Re: Want to sort hashes by values, anyone?
by Limbic~Region (Chancellor) on May 11, 2004 at 15:18 UTC
    Doraemon,
    There are several modules on CPAN that will do this for you. I have included one example.
    #!/usr/bin/perl use strict; use warnings; use Tie::Hash::Sorted; my $sort_by_value = sub { my $hash = shift; [ sort {$hash->{$b} cmp $hash->{$a}} keys %$hash ]; } tie my %sorted_hash, 'Tie::Hash::Sorted', 'Sort_Routine' => $sort_by_v +alue; %sorted_hash = (k1=> 'v2', k2=> 'v1'); print "$_ : $sorted_hash{$_}\n" for keys %sorted_hash;
    Cheers - L~R