Perlハッシュのキーを反復処理する最も安全な方法は何ですか?


107

(キー、値)のペアのペアを持つPerlハッシュがある場合、すべてのキーを反復処理する好ましい方法は何ですか?使用eachすると、意図しない副作用が発生する可能性があると聞いています。それで、それは本当ですか、そして次の2つの方法のうちの1つが最善ですか、それとももっと良い方法がありますか?

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}

回答:


199

経験則では、ニーズに最も適した関数を使用します。

キーだけが必要で、値を読み取る予定がない場合は、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()を使用しないでください。たとえば、2倍の値を持つ大文字のキーの新しいセットを作成する次のコードは、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

6
voidコンテキストキー%hを追加してください。各ループの前に、イテレータを使用して安全に表示します。
ysth、2008

5
それぞれに別の警告があります。イテレータは、コンテキストではなくハッシュにバインドされています。つまり、リエントラントではありません。たとえば、ハッシュをループしてハッシュを出力すると、perlは内部的にイテレータをリセットし、このコードを無限にループさせます。my%hash =(a => 1、b => 2、c => 3、); while(my($ k、$ v)= each%hash){print%hash; } blogs.perl.org/users/rurban/2014/04/do-not-use-each.htmlで
Rawler

28

使用時に注意する必要があるのeachは、ハッシュに「状態」を追加するという副作用があることです(ハッシュは「次の」キーが何であるかを覚えておく必要があります)。上記のスニペットのようなコードを使用する場合、ハッシュ全体を一度に繰り返しますが、これは通常問題ではありません。ただし、すべてのキーを処理する前にループのeachようなステートメントと一緒に使用しlastたりreturnwhile ... 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 "はどうなりましたか?それらはまだそこにありますが、2番目eachは最初の1つが終了したところから始まり、ハッシュの最後に到達すると停止するため、2番目のループでそれらが表示されることはありません。


22

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最初に使用する必要があります(イテレータをリセットするため)。それぞれドキュメントを参照してください。


14

各構文を使用すると、キーのセット全体が一度に生成されなくなります。これは、数百万行のデータベースに結合ハッシュを使用している場合に重要になる可能性があります。キーのリスト全体を一度に生成して、物理メモリを使い果たしたくはありません。この場合、それぞれがイテレータとして機能しますが、キーは実際にはループが始まる前に配列全体を生成します。

したがって、「それぞれ」が実際に使用される唯一の場所は、ハッシュが非常に大きい(使用可能なメモリと比較して)場合です。これは、ハンドヘルドデータコレクションデバイスまたは小さなメモリで何かをプログラミングしていない限り、ハッシュ自体がメモリ自体に存在しない場合にのみ発生する可能性があります。

メモリが問題でない場合は、通常、マップまたはキーパラダイムがより一般的で読みやすいパラダイムです。


6

このトピックに関するいくつかの雑感:

  1. ハッシュイテレータ自体について安全なものはありません。安全でないのは、ハッシュを反復している間にハッシュのキーを変更することです。(値を変更することは完全に安全です。)私が考えることができる唯一の潜在的な副作用は、valuesエイリアスを返すことです。つまり、それらを変更するとハッシュの内容が変更されます。これは仕様によるものですが、状況によっては希望とは異なる場合があります。
  2. ジョンの受け入れられた答えは1つの例外を除いて良いです:文書はハッシュを反復する間にキーを追加することが安全でないことは明らかです。一部のデータセットでは機能する可能性がありますが、ハッシュの順序によっては失敗するデータセットもあります。
  3. すでに述べたように、によって返された最後のキーを削除しても安全eachです。これがあるないため、真keyseachしながら、イテレータであるkeysリストを返します。

2
「キーには当てはまらない」と言うよりは、キーには適用されず、削除しても安全です。使用する言い回しは、キーを使用しているときに何かを削除しても安全ではないことを意味します。
ys、2008

2
Re:「ハッシュイテレータのどれも安全ではない」という別の危険性は、イテレータが各ループを開始する前に最初にあると想定していることです。
ys、2008

3

私は常に方法2も使用します。それぞれを使用する唯一の利点は、ハッシュエントリの値を(再割り当てではなく)読み取るだけの場合、常にハッシュを逆参照しないことです。


3

私はこれに噛まれるかもしれませんが、それは個人的な好みだと思います。各ドキュメントへの参照が、keys()またはvalues()と異なることはわかりません(明らかな「それらは異なるものを返す」という回答を除く)。実際、ドキュメントでは、同じイテレータとそれらのコピーの代わりに実際のリストの値を返します、そして任意の呼び出しを使用してそれを反復しながらハッシュを変更することは悪いです。

そうは言っても、ほとんどの場合、私はkeys()を使用します。なぜなら、私にとっては通常、ハッシュ自体を介してキーの値にアクセスする方が自己文書化するからです。値が大きな構造への参照であり、ハッシュへのキーがすでに構造に格納されている場合、values()をときどき使用します。その時点で、キーは冗長であり、必要ありません。私は、Perlプログラミングの10年間でeach()を2回使用したと思いますが、両方の場合、おそらく間違った選択でした=)


2

私は通常使用keysしていますが、最後にを使用したり、使用方法を読んだりすることはできませんeach

mapループで何をしているかに応じて、を忘れないでください!

map { print "$_ => $hash{$_}\n" } keys %hash;

6
戻り値が必要でない限り、マップを使用しないでください
ko-dos

-1

私は言う:

  1. ほとんどの人にとって最も読みやすい/理解しやすいものを使用してください(キーは、通常、私は主張します)
  2. コードベース全体で一貫して決定したものを使用します。

これには2つの大きな利点があります。

  1. 関数/メチオッドにリファクタリングできるように、「共通」コードを見つけるのは簡単です。
  2. 将来の開発者が保守しやすくなります。

それぞれにキーを使用する方がコストがかかるとは思わないので、コード内の同じものに対して2つの異なる構成要素を使用する必要はありません。


1
keysメモリ使用量が増加しhash-size * avg-key-sizeます。(彼らはボンネットの下に「自分」に対応する値のような単なる配列の要素だと)、キーサイズはメモリによってのみ制限されることを考えると、いくつかの状況ではそれが可能法外コピーを作成するために取らメモリ使用量と時間の両方で、より高価。
エイドリアン・ギュンター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.