Natalie Natalie - 9 months ago 32
Perl Question

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?

Answer Source

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.