Natalie
Natalie

Reputation: 35

Find values of nested hash matching a specific key

I've created a hash of hashes in perl, where this is an example of what the hash ends up looking like:

my %grades;
$grades{"Foo Bar"}{Mathematics}   = 97;
$grades{"Foo Bar"}{Literature}    = 67;
$grades{"Peti Bar"}{Literature}   = 88;
$grades{"Peti Bar"}{Mathematics}  = 82;
$grades{"Peti Bar"}{Art}          = 99;

and to print the entire hash, I'm using:

foreach my $name (sort keys %grades) {
    foreach my $subject (keys %{ $grades{$name} }) {
        print "$name, $subject: $grades{$name}{$subject}\n";
    }
}

I need to print just the inner hash referring to "Peti Bar" and find the highest value, so theoretically, I should just parse through Peti Bar, Literature; Peti Bar, Mathematics; and Peti Bar, Art and end up returning Art, since it has the highest value. Is there a way to do this or do I need to parse through the entire 2d hash?

Upvotes: 3

Views: 815

Answers (3)

clt60
clt60

Reputation: 63932

As a Perl beginner I would use the List::Util core module:

use 5.014;
use List::Util 'reduce';
my $k='Peti Bar';
say reduce { $grades{$k}{$a} > $grades{$k}{$b} ? $a : $b } keys %{$grades{$k}};

Upvotes: 0

Borodin
Borodin

Reputation: 126732

This is trivial using the List::UtilsBy module

The code is made clearer by extracting a reference to the inner hash that we're interested in. The max_by is called to return the keys of that hash that has the maximum value

use strict;
use warnings 'all';
use feature 'say';

use List::UtilsBy 'max_by';

my %grades = (
    'Foo Bar'  => { Literature => 67, Mathematics => 97 },
    'Peti Bar' => { Literature => 88, Mathematics => 82, Art => 99 },
);

my $pb_grades = $grades{'Peti Bar'};

say max_by { $pb_grades->{$_} } keys %$pb_grades;

output

Art

Upvotes: 2

simbabque
simbabque

Reputation: 54333

You don't need to parse through the first level if you know the key that you're interested. Just leave out the first loop and access it directly. To get the highest value, you have to look at each subject once.

Keep track of the highest value and the key that goes with it, and then print.

my $max_value = 0;
my $max_key;
foreach my $subject (keys %{ $grades{'Peti Bar'} }) {
    if ($grades{'Peti Bar'}{$subject} > $max_value){
        $max_value = $grades{'Peti Bar'}{$subject};
        $max_key = $subject;
    }
}
print $max_key;

This will output

Art

An alternative implementation with sort would look like this:

print +(
    sort { $grades{'Peti Bar'}{$b} <=> $grades{'Peti Bar'}{$a} }
        keys %{ $grades{'Peti Bar'} }
)[0];

The + in +( ... ) tells Perl that the parenthesis () are not meant for the function call to print, but to construct a list. The sort sorts on the keys, descending, because it has $b first. It returns a list, and we take the first value (index 0).

Note that this is more expensive than the first implementation, and not necessarily more concise. Unless you're building a one-liner or your ; is broken I wouldn't recommend the second solution.

Upvotes: 2

Related Questions