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

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

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
6.08.2008 02:53:13
9 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

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

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

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
195
4.06.2009 07:05:59
Пожалуйста, добавьте пустые контекстные ключи% h; перед каждым каждым циклом показывать безопасно с помощью итератора.
ysth 1.12.2008 03:01:38
Есть еще одна оговорка с каждым. Итератор связан с хешем, а не с контекстом, что означает, что он не является входящим. Например, если вы перебираете хеш и печатаете хеш, 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
Rawler 8.04.2014 09:01:43

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

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

3
12.10.2008 12:29:16

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

3
6.08.2008 04:04:16

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

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

map { print "$_ => $hash{$_}\n" } keys %hash;
2
22.08.2008 15:31:20
не используйте карту, если вы не хотите возвращаемое значение
ko-dos 9.11.2009 23:11:44

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

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

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

14
11.09.2008 22:04:22

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

  1. Нет ничего небезопасного в самих итераторах хеша. Что небезопасно, так это изменение ключей хеша, пока вы выполняете итерации по нему. (Совершенно безопасно изменять значения.) Единственный потенциальный побочный эффект, о котором я могу подумать, это то, что он valuesвозвращает псевдонимы, что означает, что их изменение приведет к изменению содержимого хэша. Это сделано по замыслу, но может не соответствовать вашим ожиданиям в некоторых обстоятельствах.
  2. Принятый ответ Джона хорош с одним исключением: из документации ясно, что добавлять ключи при переборе хешей небезопасно. Это может работать для некоторых наборов данных, но не для других, в зависимости от порядка хеширования.
  3. Как уже отмечалось, безопасно удалить последний ключ, возвращенный each. Это не так, keysпоскольку eachитератор keysвозвращает список.
6
23.05.2017 10:31:28
«Верно для ключей», скорее: это не относится к ключам, и любое удаление безопасно. Используемая вами фраза подразумевает, что при использовании ключей никогда не безопасно ничего удалять.
ysth 1.12.2008 02:58:15
Re: «нет ничего небезопасного ни в одном из итераторов хеша», другая опасность заключается в том, что итератор находится в начале перед началом каждого цикла, как упоминают другие.
ysth 1.12.2008 02:59:38

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

27
15.09.2008 23:37:14

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

21
16.09.2008 14:35:43

Я бы сказал:

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

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

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

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

-1
20.12.2010 12:46:21
С keysувеличением использования памяти увеличивается hash-size * avg-key-size. Учитывая, что размер ключа ограничен только памятью (поскольку они являются просто элементами массива, такими как «их» соответствующие значения под капотом), в некоторых ситуациях это может быть чрезмерно дороже как с точки зрения использования памяти, так и времени, затрачиваемого на создание копии.
Adrian Günter 8.07.2017 19:09:56