2010-03-07 3 views
11

С perldoc -f each мы читаем:Можно ли использовать каждую функцию perl?

Существует один итератор для каждого хэша, разделяемого всех 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 в целом?

+5

Я бы предположил, что общие обходные пути включают в себя оценку «ключей HASH» или «значений HASH» –

ответ

11

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

while (my ($k,$v) = each %h) { 
    say "$k = $v"; 
} 

В вашем примере вы можете сбросить итератор, добавив keys %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 each также позволит итерацию по принципу массив.

2

использовать функцию keys() для сброса итератора. Смотрите faq для получения дополнительной информации

8

Я считаю 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 продолжится навсегда, что, безусловно, является серьезной ошибкой.

7

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

Ключи void-context() (или значения, но согласованность приятна) до начала цикла является единственным «обходным» решением; есть ли какая-то причина, по которой вы ищете какое-то другое решение?

+0

Отличная точка! Это лучшая (только?) Причина использовать «каждый», о котором я могу думать. – daotoad

1

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

6

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

keys и values всегда безопасны, поэтому просто используйте их. keys облегчает перемещение хэша в детерминированном порядке, так или иначе, что почти всегда полезно. (for my $key (sort keys %hash) { ... })

+0

использовать много глобальных хэшей, не так ли? – ysth

+2

Не имеет значения, глобально это или нет. Даже частные атрибуты класса уязвимы для этой проблемы. Все, что возвращает ссылку на хеш, затрагивается. – jrockway

1

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

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

Конечно one() может быть закреплен с помощью keys, чтобы очистить итератор в начале и в конце подпрограммы. Если ты помнишь. Если все ваши коллеги помнят. Это абсолютно безопасно, пока никто не забывает.

Я не знаю о вас, но я просто буду придерживаться keys и values.

1

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

Смежные вопросы