【问题标题】:What is the easiest way to get a key with the highest value from a hash in Perl?从 Perl 中的哈希中获取具有最高值的键的最简单方法是什么?
【发布时间】:2011-02-22 15:10:55
【问题描述】:

在 Perl 中从哈希中获取具有最高值的键的最简单方法是什么?

【问题讨论】:

    标签: perl hash max


    【解决方案1】:

    虽然用排序的解决方案:

    (sort {$hash{$a} <=> $hash{$b}} keys %hash)[0]
    

    在其他一些答案中发现非常优雅,但它的性能不如看起来那么好。首先,排序将O(n) 搜索操作转换为O(n log n) 操作。其次,排序解决方案具有n log n 哈希查找。哈希查找对于某些操作非常有用,但是在处理整个哈希时,查找将比使用eachkeysvalues 来遍历数据结构要慢。这是因为迭代器不需要计算键的哈希值,也不需要重复遍历 bin 来查找值。并且开销不是恒定的,而是随着哈希值变大而增加。

    这里有一些更快的解决方案:

    use strict;
    use warnings;
    
    my %hash = (
        small   => 1,
        medium  => 5,
        largest => 10,
        large   => 8,
        tiny    => 0.1,
    );
    

    这是使用each 迭代器的解决方案(O(1) 操作完成n 次):

    sub largest_value (\%) {
        my $hash = shift;
        keys %$hash;       # reset the each iterator
    
        my ($large_key, $large_val) = each %$hash;
    
        while (my ($key, $val) = each %$hash) {
            if ($val > $large_val) {
                $large_val = $val;
                $large_key = $key;
            }
        }
        $large_key
    }
    
    print largest_value %hash; # prints 'largest'
    

    或者以内存换取速度的更快版本(它会复制哈希):

    sub largest_value_mem (\%) {
        my $hash   = shift;
        my ($key, @keys) = keys   %$hash;
        my ($big, @vals) = values %$hash;
    
        for (0 .. $#keys) {
            if ($vals[$_] > $big) {
                $big = $vals[$_];
                $key = $keys[$_];
            }
        }
        $key
    }
    
    print largest_value_mem %hash; # prints 'largest'
    

    以下是各种哈希大小的性能:

    10 keys:              Rate largest_with_sort largest_value largest_value_mem
    largest_with_sort 111565/s                --           -8%              -13%
    largest_value     121743/s                9%            --               -5%
    largest_value_mem 127783/s               15%            5%                --
    
    50 keys:             Rate  largest_with_sort largest_value largest_value_mem
    largest_with_sort 24912/s                 --          -37%              -40%
    largest_value     39361/s                58%            --               -6%
    largest_value_mem 41810/s                68%            6%                --
    
    100 keys:            Rate  largest_with_sort largest_value largest_value_mem
    largest_with_sort  9894/s                 --          -50%              -56%
    largest_value     19680/s                99%            --              -12%
    largest_value_mem 22371/s               126%           14%                --
    
    1,000 keys:         Rate   largest_with_sort largest_value largest_value_mem
    largest_with_sort  668/s                  --          -69%              -71%
    largest_value     2183/s                227%            --               -7%
    largest_value_mem 2341/s                250%            7%                --
    
    10,000 keys:        Rate   largest_with_sort largest_value largest_value_mem
    largest_with_sort 46.5/s                  --          -79%              -81%
    largest_value      216/s                365%            --              -11%
    largest_value_mem  242/s                421%           12%                --
    

    如您所见,如果内存不是什么大问题,则具有内部数组的版本最快,紧随其后的是 each 迭代器,排在第三位……sort

    【讨论】:

    • 彻底的回答。不过有一条评论:哈希查找的摊销复杂度是 O(1),而不是 O(log n)。
    • 比较真实世界的哈希查找速度和数组查找速度仍然显示出非线性关系。有 10 个元素的数组比散列快 %50,有 10000 个元素的数组快 %100,有 1,000,000 个元素的数组快 210%...
    【解决方案2】:

    不知道为什么每个人都手工做这个......

    use List::Util qw( reduce );
    my $max_val_key = reduce { $hash{$a} > $hash{$b} ? $a : $b } keys %hash;
    

    【讨论】:

      【解决方案3】:

      与对哈希进行排序的其他答案相比,以下内容更节省空间,并且将以 O(n) 而不是 O(n log n) 运行。它假定值是大于 0 的整数,并且哈希不为空,但应该很容易针对您的情况进行扩展。

      my $key_for_max_value;
      my $max_value = -1;
      while ((my $key, my $value) = each %hash) {
        if ($value > $max_value) {
          $max_value = $value;
          $max_key = $key;
        }
      }
      

      $key_for_max_value 现在将是对应于最大值的键。

      【讨论】:

      • 在您的代码中有一个假设,即哈希值并非都是小于 -1 的负数。你应该让 $max_value 成为第一眼看到的东西的价值。
      • 很高兴认识外面的某人仍然欣赏效率胜过人手不足。解释也很好。
      • @amphetamachine 对于合理大小的数据集,此解决方案很可能比使用 sort 的解决方案慢。
      • @Alnitak 通过具有较小的常数因子。令 f(n) = n * log(n) / log(10) 且 g(n) = n * 1000000。f(n) = O(n log n) 且 g(n) = O(n)。现在让 n = 10。f(10) 是十,而 g(10) 是一千万。此外,只要 n 小于 10 的百万次方,f(n) 就会小于 g(n)。尽管事实上 f(n) 支配 g(n)。
      • @hobbs 我认为这个解决方案永远不会比涉及排序的解决方案慢。您的论点通常是有效的(常数因子可以使 O(n log n) 对于小 n 更可取),但在这种情况下,O(n) 解决方案的常数因子很小:我们只查看每个元素一次并执行它的计算量非常小。最后,该解决方案的真正优势在于节省空间。排序将占用 O(n) 空间,而此解决方案占用 O(1) 空间。有关其他讨论和性能数字,请参阅 @Eric Strom 答案。
      【解决方案4】:

      按值排序的键,从低到高:

      sort { $hash{$a} <=> $hash{$b} } keys %hash
      

      按值排序的键,从高到低:

      reverse sort { $hash{$a} <=> $hash{$b} } keys %hash
      

      还有第一个元素

      (reverse sort { $hash{$a} <=> $hash{$b} } keys %hash)[0]
      

      把宇宙飞船换成cmp来尝尝。

      【讨论】:

      • 为什么不直接使用values 而不是keys
      • 因为他想要的是钥匙,而不是价值。 value 是排序依据,key 是返回什么。除非我误读了这个问题。
      • 啊,好吧,对不起,我错过了。
      • 使用$hash{$b} &lt;=&gt; $hash{$a} 而不是reverse
      【解决方案5】:
      my ($max_key, $max_val) = each %hash or die "hash is empty";
      while (my ($key, $val) = each %hash) {
        $max_key = $key, $max_val = $val if $val > $max_val;
      }
      

      【讨论】:

        【解决方案6】:
        my $highest_val = (sort { $hash{$a} <=> $hash{$b} } keys %hash)[0];
        

        很可能是你想要的。

        如果你有一个非常大的散列,你可能想要使用类似 Schwartzian 变换的东西:

        my @array = map {[$hash{$_},$_]} keys %hash;
        my $key_with_highest_value = (sort { $a->[0] <=> $b->[0] } @array)[0]->[1]
        

        【讨论】:

        • 这更多的是打字,但是是 O(n) 而不是 O(n log n),这通常是一件好事。如果您的列表很大。
        • 这里的 Schwartzian 变换仅用于减少哈希表查找的次数,并且不会改变搜索的复杂性 - 它仍然是 O(n log n)。 @jkasnicki 的迭代方法更胜一筹。
        【解决方案7】:
        my $highest_val = (keys {$hash{$b} <=> $hash{$a}} keys %hash)[0];
        

        【讨论】:

        • 返回最大值的键。我假设他想要映射到最高值的键。否则,这个问题太简单了:)(在这种情况下,为什么不只是“反向排序键 %hash”?)
        • 这取决于您在这里所说的“价值”是什么意思。通常哈希被认为是键/值对,所以我假设与 jrockway 相同。但这也可能意味着 amphetamachine 所说的。提问者应该澄清。
        • @jrockway - And in that case, why not just "reverse sort keys %hash"? - 因为这是一种词汇排序,而sort {$b &lt;=&gt; $a} 用一块石头击中了两只鸟,因为它既是数字排序又是相反的。
        • 但您是在比较键本身,而不是它们映射到的值。
        【解决方案8】:

        如果性能不是问题,我会建议更多 literate programming 解决方案。

        use List::Util qw(max);
        max keys %hash;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-21
          • 2020-11-24
          • 2010-09-05
          • 1970-01-01
          • 2010-10-11
          • 2023-03-06
          • 2011-11-17
          • 2011-06-23
          相关资源
          最近更新 更多