12

perldoc -f eachから、以下を読みました:

ハッシュごとに 1 つの反復子があり、プログラム内のすべて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そして、それは一般的に使用する価値がありますか?

4

8 に答える 8

10

そこを意識して使う価値はあると思います。反復でキーと値の両方が必要な場合に理想的です。

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は、配列での反復も可能になります。

于 2010-03-07T14:32:17.817 に答える
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がループの本体ですぐに使用できます。2 番目のケースでは、$value最初に取得する必要があります。さらに、 のキーのリストは$hashref非常に大きく、メモリを占有する場合があります。これは時々問題になります。eachそのようなオーバーヘッドは発生しません。

ただし、 の欠点はeachすぐには明らかになりません。ループから早期に中止すると、ハッシュの反復子はリセットされません。さらに(そして、これはより深刻で、さらに目立たないことがわかります):このループ内からまたは別のものを呼び出すことはできませんkeys()values()each()。これを行うとイテレータがリセットされ、while ループ内での場所が失われます。while ループは永久に続くため、これは間違いなく重大なバグです。

于 2010-03-07T17:07:35.030 に答える
7

それぞれが使用する価値があるだけでなく、メモリに対して大きすぎるすべての関連付けられたハッシュをループしたい場合は、ほとんど必須です。

ループを開始する前に void-context keys() (または値、ただし一貫性は良好) が必要な唯一の「回避策」です。他の回避策を探している理由はありますか?

于 2010-03-08T01:37:02.833 に答える
2

関数を使用しkeys()て反復子をリセットします。詳細については、よくある質問を参照してください

于 2010-03-07T14:36:19.540 に答える
2

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覚えていれば。すべての同僚が覚えている場合。誰も忘れない限り、完全に安全です。

あなたのことはわかりませんが、 and の使用に固執しkeysますvalues

于 2010-03-08T08:16:03.687 に答える
1

each() は、数百万のキーを含むデータベースなど、関連付けられたハッシュを反復処理する場合により効率的です。そうすれば、すべてのキーをメモリにロードする必要がなくなります。

于 2010-03-08T12:28:32.020 に答える
1

その名の通りに使用するのが最善です: each. 「最初のキーと値のペアをください」または「最初の 2 つのペアをください」などの意味で使用するのは、おそらく間違っています。このアイデアは十分に柔軟であるため、呼び出すたびに次のペア (またはスカラー コンテキストのキー) を取得できることに注意してください。

于 2010-03-08T03:10:35.677 に答える