简体   繁体   中英

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";
    }
}

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.

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

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"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM