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

StackOverflow https://stackoverflow.com/questions/3033

  •  08-06-2019
  •  | 
  •  

Вопрос

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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
Это было полезно?

Решение

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

Если вы просто хотите получить ключи и не планируете никогда Читать любое из значений, используйте 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)

Но используя each(), чтобы сделать то же самое:

%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

Другие советы

Одна вещь, о которой вы должны знать при использовании 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"?Они все еще там, но второй each начинается с того места, где закончился первый цикл, и останавливается, когда достигает конца хэша, поэтому мы никогда не видим их во втором цикле.

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

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

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

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

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

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

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

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

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

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

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

map { print "$_ => $hash{$_}\n" } keys %hash;

Я бы сказал:

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top