Какой самый безопасный способ перебирать ключи Perl-хеша? - PullRequest
98 голосов
/ 06 августа 2008

Если у меня есть хеш Perl с кучей пар (ключ, значение), какой метод предпочтителен для перебора всех ключей? Я слышал, что использование each может в некотором роде иметь непредвиденные побочные эффекты. Итак, верно ли это, и является ли один из двух следующих методов лучшим или есть лучший способ?

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}

Ответы [ 9 ]

188 голосов
/ 06 августа 2008

Основное правило - использовать функцию, наиболее подходящую для ваших нужд.

Если вы просто хотите получить ключи и не планируете когда-либо прочитать любое из значений, используйте keys ():

foreach my $key (keys %hash) { ... }

Если вы просто хотите получить значения, используйте values ​​():

foreach my $val (values %hash) { ... }

Если вам нужны ключи и значений, используйте каждую ():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }

Если вы планируете каким-либо образом изменить ключи хэша , за исключением для удаления текущего ключа во время итерации, вы не должны использовать each (). Например, этот код для создания нового набора ключей в верхнем регистре с удвоенными значениями работает нормально с помощью keys ():

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}

с получением ожидаемого результирующего хэша:

(a => 1, A => 2, b => 2, B => 4)

Но используя каждый () сделать то же самое:

%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}

дает неверные результаты трудно предсказуемым образом. Например:

(a => 1, A => 2, b => 2, B => 8)

Это, однако, безопасно:

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}

Все это описано в документации perl:

% perldoc -f keys
% perldoc -f each
24 голосов
/ 16 сентября 2008

При использовании each следует помнить одну вещь: побочный эффект добавления «состояния» к вашему хешу (хеш должен помнить что за «следующий» ключ). При использовании кода, подобного приведенным выше фрагментам, который перебирает весь хэш за один раз, это обычно не проблема. Тем не менее, вы столкнетесь с трудностями, чтобы выследить проблемы (я говорю от опыт работы;), при использовании each вместе с такими утверждениями, как last или return для выхода из цикла while ... each перед вами обработали все ключи.

В этом случае хеш запомнит, какие ключи он уже вернул, и когда вы используете each на нем в следующий раз (возможно, в совершенно не связанном код), он продолжит в этой позиции.

Пример:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );

# find key 'baz'
while ( my ($k, $v) = each %hash ) {
    print "found key $k\n";
    last if $k eq 'baz'; # found it!
}

# later ...

print "the hash contains:\n";

# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
    print "$k => $v\n";
}

Это печатает:

found key bar
found key baz
the hash contains:
quux => 4
foo => 1

Что случилось с клавишами "bar" и baz "? Они все еще там, но second each начинается там, где остановился первый, и останавливается, когда достигает конца хэша, поэтому мы никогда не увидим их во втором цикле.

20 голосов
/ 16 сентября 2008

Место, где each может вызвать у вас проблемы, - это настоящий итератор без границ. В качестве примера:

while ( my ($key,$val) = each %a_hash ) {
    print "$key => $val\n";
    last if $val; #exits loop when $val is true
}

# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
    # continues where the last loop left off
    print "$key => $val\n";
}

Если вам нужно убедиться, что each получает все ключи и значения, вам нужно сначала убедиться, что вы используете keys или values (поскольку это сбрасывает итератор). См. документацию для каждого .

13 голосов
/ 12 сентября 2008

Использование каждого синтаксиса предотвратит создание всего набора ключей одновременно. Это может быть важно, если вы используете привязанный хэш к базе данных с миллионами строк. Вы не хотите генерировать весь список ключей сразу и исчерпать свою физическую память. В этом случае каждый из них служит итератором, тогда как ключи фактически генерируют весь массив перед началом цикла.

Таким образом, единственное место, где «каждый» имеет реальное применение, - это когда хеш очень большой (по сравнению с доступной памятью). Это может произойти только в том случае, если сам хэш не находится в самой памяти, если вы не программируете портативное устройство сбора данных или что-то с малой памятью.

Если память не является проблемой, обычно парадигма карты или ключей является более распространенной и более легкой для чтения парадигмой.

5 голосов
/ 16 сентября 2008

Несколько разных мыслей на эту тему:

  1. Нет ничего небезопасного в самих итераторах хеша. Что небезопасно, так это изменение ключей хеша во время итерации по нему. (Изменить значения совершенно безопасно.) Единственный потенциальный побочный эффект, о котором я могу подумать, это то, что values возвращает псевдонимы, что означает, что их изменение приведет к изменению содержимого хэша. Это сделано по замыслу, но может не соответствовать вашим ожиданиям.
  2. Принятый Джоном принятый ответ хорош с одним исключением: из документации ясно, что добавлять ключи при итерации по хешу небезопасно. Это может работать для некоторых наборов данных, но не для других, в зависимости от порядка хеширования.
  3. Как уже отмечалось, безопасно удалить последний ключ, возвращенный each. не верно для keys, поскольку each является итератором, а keys возвращает список.
3 голосов
/ 06 августа 2008

Я всегда использую метод 2. Единственным преимуществом использования каждого из них является то, что если вы просто читаете (а не переназначаете) значение записи хеша, вы не постоянно отменяете ссылку на хеш.

3 голосов
/ 06 августа 2008

Я могу быть укушен этим, но я думаю, что это личное предпочтение. Я не могу найти ссылки в документах, чтобы каждый () отличался от keys () или values ​​() (кроме очевидного ответа «они возвращают разные вещи». На самом деле документы указывают на использование одного и того же итератора, и все возвращать фактические значения списка вместо их копий, и изменение хеша при его итерации по любому вызову плохо.

Все это говорит о том, что я почти всегда использую keys (), потому что для меня обычно более самодокументировано получить доступ к значению ключа через сам хеш. Я иногда использую values ​​(), когда значение является ссылкой на большую структуру, и ключ к хешу уже сохранен в структуре, и в этот момент ключ является избыточным, и он мне не нужен. Я думаю, что я использовал каждый () 2 раза за 10 лет программирования на Perl, и это был, вероятно, неправильный выбор оба раза =)

2 голосов
/ 22 августа 2008

Я обычно использую keys, и я не могу вспомнить, когда в последний раз я использовал или читал использование each.

Не забывайте о map, в зависимости от того, что вы делаете в цикле!

map { print "$_ => $hash{$_}\n" } keys %hash;
0 голосов
/ 20 декабря 2010

Я бы сказал:

  1. Используйте то, что легче всего читать / понимать для большинства людей (так что ключи, как правило, я бы поспорил)
  2. Используйте все, что вы решите последовательно, через всю кодовую базу.

Это дает 2 основных преимущества:

  1. Проще заметить «общий» код, чтобы можно было перефакторизовать функции / метиоды.
  2. Будущим разработчикам легче поддерживать.

Я не думаю, что использование ключей над каждым из них обходится дороже, поэтому не нужно использовать две разные конструкции для одной и той же вещи в вашем коде.

...