Search code examples
sortingperlhash

Perl Sorting Hash by Value of Value


I have a hash that looks like


'Jarrod' => {
                                'Age' => '25 ',
                                'Occupation' => Student
                              },
'Elizabeth' => {
                                'Age' => '18',
                                'Occupation' => Student
                                },
'Nick' => {
                                'Age' => '32 ',
                                'Occupation' => Lawyer
                               },

I am trying to sort them by age so it will look like

'Nick' => {
                                'Age' => '32 ',
                                'Occupation' => Lawyer
                               },
'Jarrod' => {
                                'Age' => '25 ',
                                'Occupation' => Student
                              },
'Elizabeth' => {
                                'Age' => '18',
                                'Occupation' => Student
                                },

But I can't seem to figure out how to access anything past Age. How can I access the value of a value when ordering hash keys?


Solution

  • A hash variable %h with the shown data can be processed in the desired order as

    use Data::Dump qw(pp);  # to print out a complex data structure
    
    say "$_->[0] => ", pp($_) for 
        map { [ $_, $h{$_} ] } 
            sort { $h{$b}->{Age} <=> $h{$a}->{Age} } 
                 keys %h;
    

    what prints (from a complete program below)

    Nick => { Age => "32 ", Occupation => "Lawyer" }
    Jarrod => { Age => "25 ", Occupation => "Student" }
    Elizabeth => { Age => 18, Occupation => "Student" }
    

    Note though that we cannot "sort a hash" and then have it be that way, as hashes are inherently random with order. But we can of course go through and process the elements in a particular order, as above for example.

    Explanation: sort takes pairs of elements of a submitted list in turn, available in variables $a and $b, and then runs the block of code we provide, so to compare them as prescribed. Here we have it compare, and thus sort, the elements by the value at key Age.

    The output, though, is just those keys sorted as such! So we then pass that through a map, which combines each key with its hashref value and returns those pairs, each in an arrayref. That is used to print them, as a place holder for the actual processing.

    A complete program

    use warnings;
    use strict;
    use feature 'say';
    use Data::Dump qw(dd pp);
    
    my %h = ( 
        'Jarrod' => {
            'Age' => '25 ',
            'Occupation' => 'Student'
        },  
        'Elizabeth' => {
            'Age' => '18',
            'Occupation' => 'Student'
        },  
        'Nick' => {
            'Age' => '32 ',
            'Occupation' => 'Lawyer'
        },  
    );
    
    say "$_->[0] => ", pp($_->[1]) for 
        map { [ $_, $h{$_} ] } sort { $h{$b}->{Age} <=> $h{$a}->{Age} } keys %h; 
    

    Or, for a workable template, change to something like

    my @sorted_pairs = 
        map { [ $_, $h{$_} ] } sort { $h{$b}->{Age} <=> $h{$a}->{Age} } keys %h;
    
    for my $key_data (@sorted_pairs) {
        say $key_data->[0], ' => ', pp $key_data->[1];  # or just: dd $key_data;
    
        # my ($name, $data) = @$key_data;
        # Process $data (a hashref) for each $name
    }
    

    Once we start building more suitable data structures for ordered data then there are various options, including one-person hashrefs for each name, stored in an array in the right order. Ultimately, all that can be very nicely organized in a class.


    Note how Sort::Key makes the sorting part considerably less cumbersome

    use Sort::Key qw(rnkeysort);  # rn... -> Reverse Numerical
    
    my @pairs = map { [ $_, $h{$_} ] } rnkeysort { $h{$_}->{Age} } keys %h;
    

    The more complex -- or specific -- the sorting the more benefit from this module, with its many specific functions for generic criteria.

    If these ages are given as integers then there is ikeysort (and rikeysort) and then those are hopefully unsigned integers, for which there is ukeysort (and rukeysort).


    See keys and perlsec, for example.