Как проще всего получить ключ с наибольшим значением из хеша в Perl?

Как проще всего получить ключ с наибольшим значением из хеша в Perl?


person syker    schedule 22.05.2010    source источник


Ответы (7)


Пока решение с сортировкой:

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

найденный в некоторых других ответах, довольно элегантен, он не работает так хорошо, как выглядит. Во-первых, сортировка преобразует операцию поиска O(n) в операцию поиска O(n log n). Во-вторых, решение для сортировки имеет n log n хеш-поиска. Поиск хеша очень хорош для определенных операций, но при работе со всем хешем поиск будет медленнее, чем при использовании each, keys или values для итерации по структуре данных. Это связано с тем, что итераторам не нужно вычислять хэши ключей, а также им не нужно многократно проходить по корзинам, чтобы найти значения. И накладные расходы не постоянны, а увеличиваются по мере увеличения хэшей.

Вот несколько более быстрых решений:

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

person Eric Strom    schedule 22.05.2010
comment
Тщательный ответ. Однако один комментарий: амортизированная сложность поиска по хешу составляет O (1), а не O (log n). - person jkasnicki; 22.05.2010
comment
сравнение реальных скоростей поиска хэша с поиском массива по-прежнему показывает нелинейную связь. с 10 элементами массив на 50% быстрее, чем хэш, с 10000 элементами — на 100% быстрее, с 1 000 000 элементов — на 210% быстрее... - person Eric Strom; 22.05.2010

Не знаю, почему все делают это вручную...

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

Следующее более эффективно с точки зрения пространства и будет выполняться за 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 теперь будет ключом, соответствующим максимальному значению.

person jkasnicki    schedule 22.05.2010
comment
В вашем коде предполагается, что не все значения хэша являются отрицательными числами меньше -1. Вы должны просто сделать $max_value значением первого увиденного или что-то в этом роде. - person ; 22.05.2010
comment
Приятно знать, что кто-то все еще ценит эффективность, а не нехватку рук. Тоже хорошее объяснение. - person amphetamachine; 22.05.2010
comment
@Kinopiko: И это можно сделать с помощью чего-то вроде my $max_value = undef; и позже, измените if на if (! defined $max_value || $value > $max_value). - person Robert P; 22.05.2010
comment
@amphetamachine для наборов данных разумного размера, это решение, скорее всего, будет медленнее, чем решение, использующее sort. - person hobbs; 22.05.2010
comment
@hobb, как именно вы заставляете O (n log n) работать быстрее, чем O (n)? - person Alnitak; 22.05.2010
comment
@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) равно десяти миллионам. Более того, f(n) будет меньше, чем g(n), если n меньше десяти в миллионной степени. И это несмотря на то, что f(n) доминирует над g(n). - person hobbs; 22.05.2010
comment
(Следует отметить, что, поскольку log n считается довольно медленно растущей функцией, O (n) и O (n log n), следовательно, не сильно различаются, а это означает, что для O (n) функция, выбивающая O(n log n) при малом n.) - person hobbs; 22.05.2010
comment
@hobbs Я не думаю, что это решение когда-либо будет медленнее, чем решение с сортировкой. Ваш аргумент в целом верен (постоянные множители могут сделать O(n log n) предпочтительным для малых n), но в этом случае постоянный множитель в решении O(n) мал: мы смотрим на каждый элемент ровно один раз и делаем очень небольшое количество вычислений с ним. Наконец, настоящее преимущество этого решения — экономия места. Сортировка займет O(n) пространства, в то время как это решение занимает O(1) пространства. См. ответ @Eric Strom для другого обсуждения и показателей производительности. - person jkasnicki; 22.05.2010
comment
@jkasnicki - хорошо сказано. Конечно, могут быть частные случаи, когда O(n log n) меньше O(n) (для небольших значений n), но это не один из них! - person Alnitak; 22.05.2010
comment
@jkasnicki: поместите оператор короткого замыкания, чтобы определить $max_value при первом проходе: $max_value ||= $value;. Таким образом, вы можете избавиться от -1 предположения - person Zaid; 23.05.2010

Ключи, отсортированные по значению, от меньшего к большему:

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 по вкусу.

person jrockway    schedule 22.05.2010
comment
Почему бы просто не использовать values вместо keys? - person ; 22.05.2010
comment
Потому что ему нужен ключ, а не значение. Значение — это то, по чему сортировать, ключ — это то, что возвращать. Если я неправильно понимаю вопрос. - person jrockway; 22.05.2010
comment
Ах, хорошо, извините, я пропустил это. - person ; 22.05.2010
comment
используйте $hash{$b} <=> $hash{$a} вместо reverse - person knittl; 22.05.2010

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

скорее всего, это то, что вы хотите.

Если у вас очень большой хэш, вы можете использовать что-то вроде преобразования Шварца:

my @array = map {[$hash{$_},$_]} keys %hash;
my $key_with_highest_value = (sort { $a->[0] <=> $b->[0] } @array)[0]->[1]
person David M    schedule 22.05.2010
comment
Это больше печатает, но O(n) вместо O(n log n), что, как правило, хорошо. Если ваш список большой. - person jrockway; 22.05.2010
comment
Преобразование Шварца здесь служит только для уменьшения количества операций поиска в хеш-таблицах и не меняет сложность поиска — это по-прежнему O(n log n). Итеративный подход от @jkasnicki лучше. - person Alnitak; 22.05.2010

Если производительность не является проблемой, я бы предложил более грамотное программирование< /а> решение.

use List::Util qw(max);
max keys %hash;
person Wolf    schedule 24.04.2018

person    schedule
comment
Это возвращает ключ с наивысшим значением. Я предполагаю, что ему нужен ключ, который соответствует наивысшему значению. В противном случае вопрос слишком прост, чтобы его задавать :) (И в таком случае, почему бы просто не перевернуть ключи сортировки %hash?) - person jrockway; 22.05.2010
comment
Это зависит от того, что вы подразумеваете под значением здесь. Обычно хэш рассматривается как пара ключ/значение, поэтому я бы предположил то же самое, что и jrockway. Но это также может означать то, что сказал амфетамин. Спрашивающий должен уточнить. - person ; 22.05.2010
comment
@jrockway - And in that case, why not just "reverse sort keys %hash"? - Потому что это лексическая сортировка, а sort {$b <=> $a} бьет двух зайцев одним выстрелом в том, что это и числовая сортировка, и сортировка в обратном порядке. - person amphetamachine; 22.05.2010
comment
но вы сравниваете сами ключи, а не значения, на которые они сопоставляются. - person Vynce; 01.11.2016