Perl FAQ 5.6: How do I sort an associative array by value instead of by key?

Perl FAQ 5.6

How do I sort an associative array by value instead of by key?

You have to declare a sort subroutine to do this, or use an inline function. Let's assume you want an ASCII sort on the values of the associative array %ary. You could do so this way:

    foreach $key (sort by_value keys %ary) {
        print $key, '=', $ary{$key}, "\n";
    sub by_value { $ary{$a} cmp $ary{$b}; }

If you wanted a descending numeric sort, you could do this:

    sub by_value { $ary{$b} <=> $ary{$a}; }

You can also inline your sort function, like this, at least if you have a relatively recent patchlevel of perl4 or are running perl5:

    foreach $key ( sort { $ary{$b} <=> $ary{$a} } keys %ary ) {
        print $key, '=', $ary{$key}, "\n";

If you wanted a function that didn't have the array name hard-wired into it, you could so this:

    foreach $key (&sort_by_value(*ary)) {
        print $key, '=', $ary{$key}, "\n";
    sub sort_by_value {
        local(*x) = @_;
        sub _by_value { $x{$a} cmp $x{$b}; } 
        sort _by_value keys %x;

If you want neither an alphabetic nor a numeric sort, then you'll have to code in your own logic instead of relying on the built-in signed comparison operators ``cmp'' and ``<=>''.

Note that if you're sorting on just a part of the value, such as a piece you might extract via split, unpack, pattern-matching, or substr, then rather than performing that operation inside your sort routine on each call to it, it is significantly more efficient to build a parallel array of just those portions you're sorting on, sort the indices of this parallel array, and then to subscript your original array using the newly sorted indices. This method works on both regular and associative arrays, since both @ary[@idx] and @ary{@idx} make sense. See page 245 in the Camel Book on ``Sorting an Array by a Computable Field'' for a simple example of this.

For example, here's an efficient case-insensitive comparison:

    @idx = ();
    for (@data) { push (@idx, "\U$_") }
    @sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0..$#data];

Other resources at this site: