Подтвердить что ты не робот

Должен ли perl использовать каждую функцию?

Из perldoc -f каждый мы читаем:

Существует один итератор для каждого хэша, который разделяется всеми each, keys и values вызовами функций в программе; он может быть reset, читая все элементы из хэша или оценивая keys HASH или values HASH.

Итератор не reset, когда вы покидаете область, содержащую 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?

4b9b3361

Ответ 1

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

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

В вашем примере вы можете reset итератора, добавив 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

Я нахожу 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 не кажутся очевидными: если прервать из цикла раньше, хеш-итератор не reset. Кроме того (и я считаю это еще более серьезным и даже менее заметным): вы не можете вызвать keys(), values() или другой each() из этого цикла. Для этого итератор reset, и вы потеряете свое место в цикле while. Цикл while будет продолжаться вечно, что определенно является серьезной ошибкой.

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

Ответ 7

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::Differences eq_or_diff вместо is.)

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

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

Ответ 8

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