each ではまった
Perl には,ハッシュのキーと値を対にしてとることのできる each というオペレータというか関数というかがあるのですが,
my %h = ( 'a' => 1, 'b' => 2, ... ); foreach my $key (keys %h) { print $key, " => ", $h{$key}, "\n"; } # equivalent to: while (my ($key, $value) = each %h) { print $key, " => ", $value, "\n"; }
この機能は,ハッシュに内在するイテレータを内部的に操作しています。すべてのハッシュの走査が終わるとイテレータはリセットされます(先頭に戻ります)。それでこのイテレータは keys, values などの関数でも使っています。ですから上記の each の下のブロックで keys や values を使うと破綻するそうです(詳しくは perldoc -f each を参照のこと)。
といった話をしばらく前にどなたかのブログfbisさんのところ(でした!ブクマコメントサンクス)の米欄で教えて頂いて(場所を失念してしまいました…)頭の隅に残っていたはずなのですが,やってしまいました。
my %h = ( ... ); sub print_hash { while (my ($key, $value) = each %h) { if ($key eq 'b') { print "${key} => ${value}\n"; return; } } }
この print_hash() を何回も呼び出すと,2回に1回しかうまく動かない状況になってしまいました。これは,return で戻った時にイテレータがリセットされずに,次に呼び出されたときに前回の続きから走査するからなんですね。
このイテレータは上で書いているように keys や values と共用されているので,対処法としては,
sub print_hash { while (my ($key, $value) = each %h) { if ($key eq 'b') { print "${key} => ${value}\n"; keys %h; # reset iterator return; } } }
のように,keys などでイテレータをリセットしてやる…というのは冗談で,複雑なロジックになってきたらすっぱり each を使うのをあきらめたほうがよいと思います。個人的には単純に key と value の組がほしくて全データを走査するとわかっているときに,見た目のロジックがすっきりするなら each を使う,という判断基準です。