Perl hash sort - How to sort a Perl hash by the hash value

Perl hash sorting FAQ: How do I sort a Perl hash by the hash value?

Before getting into this article, you if just need to sort a Perl hash by the hash key, this is a pretty well-known recipe. It's covered in another Q&A article titled "How to sort a hash by the hash key".

Sorting a Perl hash by the hash value

Sorting a Perl hash by the hash value is a bit more difficult than sorting by the key, but it's not too bad. It just requires a small "helper" function. This is easiest to demonstrate by example.

Suppose we have a class of five students. Rather than give them names, we'll call them student1, student2, etc. Suppose these students just took a test, and we stored their grades in a hash (called associative arrays prior to the release of Perl 5) named grades.

The hash definition might look like this:

%grades = (
  student1 => 90,
  student2 => 75,
  student3 => 96,
  student4 => 55,
  student5 => 76,
);

If you're familiar with hashes, you know that the student names are the keys, and the test scores are the hash values.

The key to sorting a hash by value is the function you create to help the sortcommand perform it's function. Following the format defined by the creators of Perl, you create a function I call a helper function that tells Perl how to sort the list it's about to receive. In the case of the program you're about to see, I've created two helper functions named hashValueDescendingNum(sort by hash value in descending numeric order) and hashValueAscendingNum(sort by hash value in ascending numeric order).

A complete Perl hash sort sample program

Here's a complete Perl hash sort sample program that prints the contents of the grades hash, sorted numerically by the hash value:

#!/usr/bin/perl -w

#----------------------------------------------------------------------#
#  printHashByValue.pl                                                 #
#----------------------------------------------------------------------#

#----------------------------------------------------------------------#
#  FUNCTION:  hashValueAscendingNum                                    #
#                                                                      #
#  PURPOSE:   Help sort a hash by the hash 'value', not the 'key'.     #
#             Values are returned in ascending numeric order (lowest   #
#             to highest).                                             #
#----------------------------------------------------------------------#

sub hashValueAscendingNum {
   $grades{$a} <=> $grades{$b};
}


#----------------------------------------------------------------------#
#  FUNCTION:  hashValueDescendingNum                                   #
#                                                                      #
#  PURPOSE:   Help sort a hash by the hash 'value', not the 'key'.     #
#             Values are returned in descending numeric order          #
#             (highest to lowest).                                     #
#----------------------------------------------------------------------#

sub hashValueDescendingNum {
   $grades{$b} <=> $grades{$a};
}

%grades = (
  student1 => 90,
  student2 => 75,
  student3 => 96,
  student4 => 55,
  student5 => 76,
);

print "\nGRADES IN ASCENDING NUMERIC ORDER:\n";
foreach $key (sort hashValueAscendingNum (keys(%grades))) {
   print "\t$grades{$key} \t\t $key\n";
}

print "\nGRADES IN DESCENDING NUMERIC ORDER:\n";
foreach $key (sort hashValueDescendingNum (keys(%grades))) {
   print "\t$grades{$key} \t\t $key\n";
}

Although this "Perl hash sort by value" program is fairly lengthy, you can see at the bottom of the code where the student grades are printed in ascending and descending numeric value.

The output of the program looks like this:

GRADES IN ASCENDING NUMERIC ORDER:
	55 		 student4
	75 		 student2
	76 		 student5
	90 		 student1
	96 		 student3

GRADES IN DESCENDING NUMERIC ORDER:
	96 		 student3
	90 		 student1
	76 		 student5
	75 		 student2
	55 		 student4

Related Perl hash tutorials

I hope you found this Perl hash tutorial helpful. We have many more hash tutorials on this site, including the following:

Getting started Perl hash tutorials:

More advanced hash tutorials:

Sorting a hash by key or value:

Very helpful. Thanks a lot!

Very helpful. Thanks a lot!

Thank you for this

Thank you for this program.
It's very helpful.
it seems so simple, once the solution is given

This crashes when I include

This crashes when I include "use strict." I'm using perl 5.8.9 on a mac. I get a stack of error messages like the following:

Global symbol "%grades" requires explicit package name at ./temp.plx line 17

If I try to define the hash within the sub, though, it's treated as a different hash than the one that I am trying to sort (b/c the scope is different), and the sorting doesn't work. Have others had this issue?

Thanks.

For oneliners....

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

Great solution

Smart and simple solution.

Thanks for this

Error

Hey guys I am trying to figure out this error I get after using this bit of code :
sub hashValueAscendingNum {
$grades{$a} <=> $grades{$b};
}

error:
Use of uninitialized value $a in hash element at ./inventory line 52, line 3.
Use of uninitialized value $b in hash element at ./inventory line 52, line 3.
Use of uninitialized value in string comparison (cmp) at ./inventory line 52, line 3.
Use of uninitialized value in string comparison (cmp) at ./inventory line 52, line 3.

I am not sure why I am getting it

Code fails under "strict" conditions

Let's run this as a strict perl script with diagnostics enabled:

perl -Mdiagnostics -Mstrict printHashByValue.pl

<<<<<<<<<<<<<<<
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 16.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 16.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 29.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 29.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 33.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 42.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 42.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 43.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 43.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 43.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 47.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 47.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 48.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 48.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 48.
Execution of printHashByValue.pl aborted due to compilation errors (#1)
(F) You've said "use strict" or "use strict vars", which indicates
that all variables must either be lexically scoped (using "my" or "state"),
declared beforehand using "our", or explicitly qualified to say
which package the global variable is in (using "::").

BEGIN not safe after errors--compilation aborted at /usr/share/perl/5.10/Carp/Heavy.pm line 5.
Compilation failed in require at /usr/share/perl/5.10/Carp.pm line 33.
>>>>>>>>>>>>>>>

It requires scoping the %grades hash, but if you put "my" in there, the scope fails to cover the sub functions:

<<<<<<<<<<<<<<<
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 16.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 16.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 29.
Global symbol "%grades" requires explicit package name at printHashByValue.pl line 29.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 42.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 43.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 43.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 47.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 48.
Global symbol "$key" requires explicit package name at printHashByValue.pl line 48.
Execution of printHashByValue.pl aborted due to compilation errors (#1)
(F) You've said "use strict" or "use strict vars", which indicates
that all variables must either be lexically scoped (using "my" or "state"),
declared beforehand using "our", or explicitly qualified to say
which package the global variable is in (using "::").

BEGIN not safe after errors--compilation aborted at /usr/share/perl/5.10/Carp/Heavy.pm line 5.
Compilation failed in require at /usr/share/perl/5.10/Carp.pm line 33.
>>>>>>>>>>>>>>>

Re: Code fails under "strict" conditions

Found the solution using http://perldoc.perl.org/functions/sort.html as a reference. Here's a rewrite of your code, complete with how to do it without the subfunctions. I don't recommend using diagnostics unless you want help debugging (better to use on the command line with -Mdiagnostics), but it's here (commented) for reference/completeness.

#!/usr/bin/perl

use warnings;
use strict;
#use diagnostics;

#----------------------------------------------------------------------#
#  printHashByValue.pl                                                 #
#----------------------------------------------------------------------#

#----------------------------------------------------------------------#
#  FUNCTION:  hashValueAscendingNum                                    #
#                                                                      #
#  PURPOSE:   Help sort a hash by the hash 'value', not the 'key'.     #
#             Values are returned in ascending numeric order (lowest   #
#             to highest).                                             #
#----------------------------------------------------------------------#

sub hashValueAscendingNum {
   my (%hash) = @_;
   return $hash{$a} <=> $hash{$b};
}


#----------------------------------------------------------------------#
#  FUNCTION:  hashValueDescendingNum                                   #
#                                                                      #
#  PURPOSE:   Help sort a hash by the hash 'value', not the 'key'.     #
#             Values are returned in descending numeric order          #
#             (highest to lowest).                                     #
#----------------------------------------------------------------------#

sub hashValueDecendingNum {
   my (%hash) = @_;
   return $hash{$b} <=> $hash{$a};
}


my %grades = (
        student1 => 90,
        student2 => 75,
        student3 => 96,
        student4 => 55,
        student5 => 76,
);

print "\nGRADES IN ASCENDING NUMERIC ORDER:\n";
foreach my $key (sort { hashValueAscendingNum (%grades) } keys %grades) {
   print "\t$grades{$key} \t\t $key\n";
}

print "\nGRADES IN DESCENDING NUMERIC ORDER:\n";
foreach my $key (sort { hashValueDecendingNum (%grades) } keys %grades) {
   print "\t$grades{$key} \t\t $key\n";
}

print "\nGRADES IN ASCENDING NUMERIC ORDER (no function):\n";
foreach my $key (sort { $grades{$a} <=> $grades{$b} } keys %grades) {
   print "\t$grades{$key} \t\t $key\n";
}

print "\nGRADES IN DESCENDING NUMERIC ORDER (no function):\n";
foreach my $key (sort { $grades{$b} <=> $grades{$a} } keys %grades) {
   print "\t$grades{$key} \t\t $key\n";
}

what about deeper hashes (hash-refs)?

$student->{'gabriel'}->{'age'}= 18;
$student->{'gabriel'}->{'awg'}= 3.2;
$student->{'gabriel'}->{'eyecolor'}= 'brown';

$student->{'max'}->{'age'}= 17;
$student->{'max'}->{'awg'}= 4.1;
$student->{'max'}->{'eyecolor'}= 'red';

$student->{'alex'}->{'age'}= 19;
$student->{'alex'}->{'awg'}= 3.7;
$student->{'alex'}->{'eyecolor'}= 'pink';

How can i sort it by age(s)?
Tnx!

Thank you for this straight

Thank you for this straight forward and clear explanation,
it's very useful!

Excellent, excellent!

Excellent, excellent!

Thanks for your Perl hash sorting comments

Sorry for the long delay here, I'm way behind in catching up on comments and emails, but many thanks to everyone for reporting that this fails under "use strict", and thanks to Adam Katz for a solution. My original solution (probably written in 1996-'98) required the "grades" hash to be used as a global variable, and Adam's fix (currently the 8th comment down) is much better.

but what if the value is not numeric?

This will crash if your values are not simple numerics (like dates, floats, etc)

In that case, instead of the <=> operator, use cmp. For example:

print "\nGRADES IN DESCENDING NUMERIC ORDER (no function):\n";
foreach my $key (sort { $grades{$b} cmp $grades{$a} } keys %grades) {
print "\t$grades{$key} \t\t $key\n";
}

Thanks for the tutorial, and Adam's fixes. They helped me a lot.

Post new comment

The content of this field is kept private and will not be shown publicly.