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 を使う,という判断基準です。