Какой самый безопасный способ перебирать ключи Perl-хеша?

79

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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
Теги:
each
hash
iteration

9 ответов

148
Лучший ответ

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

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

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

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

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(). Например, этот код для создания нового набора прописных ключей с удвоенными значениями отлично работает с помощью клавиш():

%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
  • 6
    Пожалуйста, добавьте пустые контекстные ключи% h; перед каждым каждым циклом показывать безопасно с помощью итератора.
  • 5
    Есть еще одна оговорка с каждым. Итератор связан с хешем, а не с контекстом, что означает, что он не является входящим. Например, если вы перебираете хеш и печатаете хеш, perl внутренне сбрасывает итератор, делая этот цикл кода бесконечным: my% hash = (a => 1, b => 2, c => 3,); while (my ($ k, $ v) = каждый% хеш) {print% hash; } Узнайте больше на blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
19

Одна вещь, о которой вам следует знать при использовании 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

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

18

Место, где 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 (так как это сбрасывает итератор). См. Документацию для каждого.

  • 0
    Это может укусить $ $, если не соблюдать осторожность
12

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

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

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

4

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

  • Существует ничего небезопасного ни о каких итераторах хэширования. Что небезопасно, это изменение ключей хеша, когда вы повторяете его. (Совершенно безопасно изменять значения.) Единственный потенциальный побочный эффект, о котором я могу думать, заключается в том, что values возвращает псевдонимы, что означает, что их изменение будет изменять содержимое хэша. Это по дизайну, но может быть не так, как вы хотите в некоторых обстоятельствах.
  • John принятый ответ хорош с одним исключением: в документации ясно, что небезопасно добавлять ключи во время итерации по хэшу. Он может работать для некоторых наборов данных, но не подходит для других в зависимости от хэш-порядка.
  • Как уже отмечалось, безопасно удалять последний ключ, возвращаемый each. Это неверно для keys, поскольку each является итератором, а keys возвращает список.
  • 2
    Скорее, «не верно для ключей»: это не относится к ключам, и любое удаление безопасно. Используемая вами фраза подразумевает, что при использовании ключей никогда не безопасно ничего удалять.
  • 2
    Re: «нет ничего небезопасного ни в одном из хеш-итераторов», другая опасность заключается в предположении, что итератор находится в начале перед началом каждого цикла, как упоминают другие.
4

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

3

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

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

1

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

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

map { print "$_ => $hash{$_}\n" } keys %hash;
  • 5
    не используйте карту, если вы не хотите возвращаемое значение
-2

Я скажу:

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

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

  • Легче определить "общий" код, чтобы вы могли перегруппировать в функции /methiods.
  • Это проще для будущих разработчиков.

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

  • 0
    С keys использование памяти увеличивается на hash-size * avg-key-size . Учитывая, что размер ключа ограничен только памятью (поскольку они являются просто элементами массива, такими как «их» соответствующие значения под капотом), в некоторых ситуациях это может быть чрезмерно дороже как с точки зрения использования памяти, так и времени, затрачиваемого на создание копии.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню