От perldoc-f каждый мы читаем:
Существует единственный итератор для каждого хеша, совместно использованного всеми
each
,keys
, иvalues
вызовы функции в программе; это может быть сброшено путем чтения всех элементов из хеша, или путем оценкиkeys HASH
илиvalues HASH
.
Итератор не сбрасывается при отъезде объема, содержащего each()
, и это может привести к ошибкам:
my %h = map { $_, 1 } qw(1 2 3);
while (my $k = each %h) { print "1: $k\n"; last }
while (my $k = each %h) { print "2: $k\n" }
Вывод:
1: 1
2: 3
2: 2
Каковы общие обходные решения для этого поведения? И действительно ли это стоит использовать each
в целом?
Я думаю, что его стоит использовать, если вы об этом знаете. Это идеально, когда вам нужны и ключ, и значение в итерации:
while (my ($k,$v) = each %h) {
say "$k = $v";
}
В вашем примере вы можете сбросить итератор, добавив ключей% h;
следующим образом:
my %h = map { $_ => 1 } qw/1 2 3/;
while (my $k = each %h) { print "1: $k\n"; last }
keys %h; # reset %h
while (my $k = each %h) { print "2: $k\n" }
Из Perl 5.12 каждый
также разрешит итерацию по массиву.
каждый
имеет встроенную скрытую глобальную переменную, которая может вам навредить. Если вам не нужно такое поведение, безопаснее просто использовать ключи
.
Рассмотрим этот пример, в котором мы хотим сгруппировать наши пары k / v (да, я знаю, что printf
подойдет лучше):
#!perl
use strict;
use warnings;
use Test::More 'no_plan';
{ my %foo = map { ($_) x 2 } (1..15);
is( one( \%foo ), one( \%foo ), 'Calling one twice works with 15 keys' );
is( two( \%foo ), two( \%foo ), 'Calling two twice works with 15 keys' );
}
{ my %foo = map { ($_) x 2 } (1..105);
is( one( \%foo ), one( \%foo ), 'Calling one twice works with 105 keys' );
is( two( \%foo ), two( \%foo ), 'Calling two twice works with 105 keys' );
}
sub one {
my $foo = shift;
my $r = '';
for( 1..9 ) {
last unless my ($k, $v) = each %$foo;
$r .= " $_: $k -> $v\n";
}
for( 10..99 ) {
last unless my ($k, $v) = each %$foo;
$r .= " $_: $k -> $v\n";
}
return $r;
}
sub two {
my $foo = shift;
my $r = '';
my @k = keys %$foo;
for( 1..9 ) {
last unless @k;
my $k = shift @k;
$r .= " $_: $k -> $foo->{$k}\n";
}
for( 10..99 ) {
last unless @k;
my $k = shift @k;
$r .= " $_: $k -> $foo->{$k}\n";
}
return $r;
}
Отладка ошибки, показанной в приведенных выше тестах, в реальном приложении могла бы быть ужасно болезненным. (Для лучшего вывода используйте Test :: Differences
eq_or_diff
вместо равно
.)
Конечно one ()
можно исправить с помощью ключи
для очистки итератора в начале и в конце подпрограммы. Если ты помнишь. Если все твои коллеги помнят. Это совершенно безопасно, пока никто не забудет.
Не знаю, как вы, но я просто буду использовать ключи
и значения
.
Лучше всего использовать его по названию: каждый
. Возможно, это неправильное использование, если вы имеете в виду "дайте мне первую пару ключ-значение", или "дайте мне первые две пары", или что-то еще. Просто имейте в виду, что идея достаточно гибкая, чтобы каждый раз, когда вы ее вызываете, вы получали следующую пару (или ключ в скалярном контексте).
each() может быть более эффективной, если вы выполняете итерацию по связанному хэшу, например, по базе данных, содержащей миллионы ключей; таким образом вам не придется загружать все ключи в память.
каждый
слишком опасен для использования, и многие руководства по стилю полностью запрещают его использование. . Опасность заключается в том, что если цикл каждый
прерывается до окончания хеширования, следующий цикл начнется там. Это может вызвать очень трудно воспроизводимые ошибки; поведение одной части программы будет зависеть от совершенно не связанной с ней другой части программы. Вы можете правильно использовать каждый
, но как насчет каждого когда-либо написанного модуля, который может использовать ваш хэш (или hashref; это одно и то же)?
ключи
и значения
всегда безопасны, поэтому просто используйте их. keys
в любом случае упрощают обход хеша в детерминированном порядке, что почти всегда более полезно. ( для моего $ key (sort keys% hash) {...}
)
каждый из них не только стоит использовать, но и в значительной степени обязателен, если вы хотите перебрать весь связанный хэш, слишком большой для памяти.
Ключи пустого контекста () (или значения, но согласованность - это хорошо) перед началом цикла - единственное необходимое «обходное решение»; есть ли причина, по которой вы ищете другое решение?
Я нахожу each
очень удобным для таких идиом, как эта:
my $hashref = some_really_complicated_method_that_builds_a_large_and_deep_structure();
while (my ($key, $value) = each %$hashref)
{
# code that does stuff with both $key and $value
}
Противопоставьте этот код этому:
my $hashref = ...same call as above
foreach my $key (keys %$hashref)
{
my $value = $hashref->{$key};
# more code here...
}
В первом случае и $key
, и $value
сразу доступны в теле цикла. Во втором случае сначала нужно получить $value
. Кроме того, список ключей $hashref
может быть очень большим, что занимает память. Это иногда является проблемой. each
не несет таких накладных расходов.
Однако недостатки each
проявляются не сразу: при досрочном прерывании цикла итератор хэша не сбрасывается. Кроме того (и я считаю этот недостаток более серьезным и менее заметным): вы не можете вызвать keys()
, values()
или другой each()
из этого цикла. Это приведет к сбросу итератора, и вы потеряете свое место в цикле while. Цикл while будет продолжаться вечно, что, безусловно, является серьезной ошибкой.