CS student
CS student

Reputation: 305

Find key for greatest value in hash of hashes in Perl

I have a hash of hashes containing keys, values, and counts of the form ((k1, v1), c1). I am trying to write a subroutine that returns the value of the key passed as a parameter with the greatest count. For example, if I had:

%hash = (
    "a" => {
        "b" => 2,
        "c" => 1,
    },
    "d" => {
        "e" => 4,
    },
);

and made the call:

print &function("a");

it should print "b" because key "a" has the highest count of 2 with "b" as its value. Here is the code I have so far:

sub function() {
    $key = $_[0];
    if(exists($hash{$key})) {
        while (my ($value, $count) = each %{$hash{$key}}) {
            #logic goes here
        }

    } else {
        return "$key does not exist";
    }
}

Upvotes: 1

Views: 673

Answers (3)

ikegami
ikegami

Reputation: 385829

The sub doesn't need to know anything about the outer hash, so it makes far more sense to call the sub as follows:

print key_with_highest_val($hash{a});

The sub simply needs to iterate over all the elements of that hash, keeping track of the highest value seen, and the key at which it was seen.

sub key_with_highest_val {
   my ($h) = @_;
   my $hi_v;
   my $hi_k;
   for my $k (keys(%$h)) {
      my $v = $h->{$k};
      if (!defined($hi_v) || $v > $hi_v) {
         $hi_v = $v;
         $hi_k = $k;
      }
   }

   return $hi_k;
}

As Chris Charley points out, List::Util's reduce can simply this function. With the calling convention I recommended above, the reduce solution becomes the following:

use List::Util qw( reduce );

sub key_with_highest_val {
   my ($h) = @_;
   return reduce { $h->{$a} >= $h->{$b} ? $a : $b } keys(%$h);
}

Both versions return an arbitrary key among those that tied when there's a tie.

Upvotes: 5

Matt Jacob
Matt Jacob

Reputation: 6553

This code makes the following assumptions:

  1. The values of your nested hashes are always numeric.
  2. You don't have duplicate values.

Anything else is left as an exercise for the reader.

use strict;
use warnings;

use Data::Dump;
use List::Util qw(max);

my %hash = (
    a => {
        b => 2,
        c => 1,
    },
    d => {
        e => 4,
    },
);

dd(max_hash_value(\%hash, $_)) for 'a' .. 'd';

sub max_hash_value {
    my ($hash_ref, $search_key) = @_;
    return unless $hash_ref->{$search_key};

    my %lookup;

    while (my ($key, $value) = each(%{$hash_ref->{$search_key}})) {
        $lookup{$value} = $key;
    }

    return $lookup{max(keys(%lookup))};
}

Output:

"b"
()
()
"e"

Upvotes: 0

Chris Charley
Chris Charley

Reputation: 6573

Use the reduce function from List::Util (which is part of core perl).

#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw/reduce/;

my %hash = (
    "a" => {
        "b" => 2,
        "c" => 1,
    },
    "d" => {
        "e" => 4,
    },
);


my $key = 'a';
print  "For key: $key, max key is ", max_key($key, %hash), "\n";


sub max_key {
    my ($key, %hash) = @_;
    return "$key does not exist" unless exists $hash{$key};

    my $href = $hash{$key};
    return reduce { $href->{$a} > $href->{$b} ? $a : $b } keys %$href;
}

You should always include use strict and use warnings at the top of your programs to catch errors so you can find and fix them. This requires declaring of your variables with my, like my $key = 'a';, for example and my %hash = ...

This program prints:

For key: a, max key is b

Upvotes: 0

Related Questions