Home > front end >  Perl Sorting Hash by Value of Value
Perl Sorting Hash by Value of Value

Time:02-02

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?

CodePudding user response:

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.


See keys and perlsec, for example.

CodePudding user response:

You can't sort a hash. A hash's elements are inherently unordered.


If you just want to visit the elements of hash in order, you can do that by getting and sorting the keys.

for my $name (
   sort { $people{ $b }{ age } <=> $people{ $a }{ age } }
      keys( %people ) 
) {
   my $person = $people{ $name };
   ...
}

or

use Sort::Key qw( rikeysort );

for my $name (
   rikeysort { $people{ $_ }{ age } }
      keys( %people ) 
) {
   my $person = $people{ $name };
   ...
}

If you need an ordered structure, you could start by converting the data to an array of people.

my @unordered_people =
   map {  { name => $_, %{ $people{ $_ } } }
      keys( %people );

Then sorting that.

my @ordered_people =
   sort { $b->{ age } <=> $a->{ age } }
      @unordered_people;

or

use Sort::Key qw( rikeysort );

my @ordered_people =
   rikeysort { $_->{ age } }
      @unordered_people;
  •  Tags:  
  • Related