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

sort{$tallies{my $a} <=> $tallies{my $b}}

this is a snippet I copied from a perl site to sort a hash by value. Can someone explain wth this does? I (don't have a book that seems to explain it)/(know what the hell to look up?) Any links or explanation would be great.. thanks monks

Replies are listed 'Best First'.
Re: question re: hash sorting
by hardburn (Abbot) on Aug 25, 2003 at 19:30 UTC

    The code as it stands doesn't do anything. It seems that the author wanted to sort the %tallies hash, but is it doesn't quite have enough information there to get the job done (and I'm not sure why those my statements are there). It should be something like:

    my @sorted_data = sort { $tallies{$a} <=> $tallies{$b} } keys %tallies +;

    See also sort.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

      right .. i put it out of context it was in a foreach.
      foreach my $key (*funky sort algo*)
      to return the sorted list as the list to do the foreach over.

      RTFM to the rescue... sorry guys..
Re: question re: hash sorting
by demerphq (Chancellor) on Aug 25, 2003 at 23:06 UTC
    sort{$tallies{my $a} <=> $tallies{my $b}}

    This code flat out doesnt work. To see why lets take a look at this snippet:

    #!perl -l my %tallies=(h=>30,a=>10,j=>1,p=>20); print "Keys:",keys %tallies; print "Bad :",sort{$tallies{my $a} <=> $tallies{my $b}} keys %tallies; print "Good:",sort{$tallies{$a} <=> $tallies{$b}} keys %tallies; __END__ Keys:phaj Bad :phaj Good:japh

    So we see that in fact the code as written can be simplified down to

    keys %tallies;

    which obviously isnt what you want out of a sort as keys returns the keys in an arbitrary order that has to do with the values of the keys, the size of the hash and the insertion order, and later versions of perl throw in some initialization randomization for good measure.

    Why is this so?

    Because as stated the in-order function used resolves to undef <=> undef (Which you would have noticed if only warnings were enabled.) Why is that? Its because inside of the lexical scope of the sort you created two brand new variables named $a and $b which werent initialized at all. Its the equivelent of

    sort {my ($a,$b); $a <=> $b } keys %tallies;

    Which is much more obviously wrong. First off we use undef to access the value of the hash, this raises a warning and as it is string context is treated as the empty string. Since its unlikely you have that as a key the hash returns undef (although the end result would be the same whatever it returned) which is then in numeric context and thus coerced to a 0 while also raising warnings. So the end result is that every time sort asks if two keys are inorder it always gets 0 back from the comparison (indicating equality between the keys), and so it assumes its already inorder, and thus probably does not alter order of the list at all. (Im not sure about the stability of the sort in this case, given some tests it looks stable to me, but I wouldnt swear by it.)

    Now to fix it you remove those my's from the picture. Now you are playing with something totally different. The variables $a and $b (along with a bunch more) are special cased global variables. They are special cased not to throw warnings when used without declaration under strict. And this is because the internal sort routine uses them to pass out the values of the two keys it is comparing. I think the others have covered satisfactorily the rest of whats going on, but I didnt notice anyone outright noting the error in the code so I thought I should point it out.

    HTH


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
Re: question re: hash sorting
by sgifford (Prior) on Aug 25, 2003 at 20:04 UTC
    Assuming that this is called like:
    @sorted=sort{$tallies{my $a} <=> $tallies{my $b}} keys %tallies
    , here's how it works.

    keys %tallies returns a list of all of the keys in %tallies. sort sorts the provided list using the provided sort routine

    The provided sort routine is given two items from the provided list (in this case the keys for %tallies), in $a and $b, and returns 1 if $a is greater, -1 if $b is greater, or 0 if they are equal. This particular sort routine takes the $a and $b from the list, looks them up in the %tallies hash, then compares the numeric values of the results using the spaceship operator (<=>).

    By calling the sort routine repeatedly, Perl sorts the list, and sort returns the sorted list.

    You can learn more about this by reading about sort and keys in perlfunc(1), and about <=> in perlop(1).

Re: question re: hash sorting
by bart (Canon) on Aug 26, 2003 at 01:07 UTC
    The "my" obviously should be scrapped. demerphq went into that. $a and $b must be package/global variables. That's what sort counts on.

    And for a more immediate answer to your question, check out these FAQ entries:

    For some more examples, check out perldoc on sort.

    The gist of it all is this: the sort block is called to compare two items from the list-to-sort. It will actually be called many times for this one sort statement, in order to establish a complete sorting order for the whole list. The value returned by the block reflects whether the first item ($a) is to be considered bigger or smaller than the second item ($b), depending on the sign of the result. If they are equal, return 0. Your code block should return the same sign as cmp and <=> return, which is not a coincidence, but by design. You might as well use - (minus) for comparing numbers, the results would be quite the same.

    print join " ", sort { $a - $b } 1, 3, 2, 5, 4;
    This code snippet simply numerically sorts the passed numbers, resulting in a rather boring list.