LoginSignup
5
5

More than 5 years have passed since last update.

Perl の each の挙動

Last updated at Posted at 2014-10-24

Ruby での Hash の each_pair みたいなのを Perl でやりたくてロクに仕様を知らずに each を使ったらハマった。
Perl のバージョンは 5.21.1。

ちなみに Ruby だと以下のように書ける処理。

h.each_pair {|k, v|
  p "#{k} : #{v}"
}

これだけなら Perl でも普通に書ける。

while (my ($k, $v) = each(%$h)) {
    print "key = $k, value = $v\n";
}

ただ、特定の条件の場合にループを抜ける、みたいなコードを書いてみると、二度目の実行から想定通りに動作しない。

use strict;
use warnings;

my $h = +{
    foo  => 1,
    bar  => 2,
    baz  => 3,
    qux  => 4,
    quux => 5,
};

# 一度目の実行
while (my ($k, $v) = each(%$h)) {
    print "key = $k, value = $v\n";
    last if $k eq "baz";  # 値が一致したらループを抜ける
}

print "--------------------\n";

# もう一度最初からループを実行(したい)
while (my ($k, $v) = each(%$h)) {
    print "key = $k, value = $v\n";
}

実行すると以下のような感じ。

key = qux, value = 4
key = baz, value = 3
--------------------
key = foo, value = 1
key = bar, value = 2
key = quux, value = 5

二度目の実行では途中で抜けるような処理は行っていないにも関わらず、全部の値が出力されていない。

順不同なので結果は実行する度に変わるが、どうも each が二度目の実行時に一度目に繰り返しを進めた位置を覚えているような挙動をしている事が分かる。

この挙動の原因がわからず、散々悩んだ末に検索していたら詳しく解説されている以下の記事を発見。

eachの途中で止めると再開が途中からになる話 - Perl日記
http://r9.hateblo.jp/entry/20130128/p1

明らかに直感的じゃない気がするが、どうも each の仕様という事らしい。

で、結局問題の処理は foreach と keys を使って以下のような感じで書く事にした。

use strict;
use warnings;

my $h = +{
    foo  => 1,
    bar  => 2,
    baz  => 3,
    qux  => 4,
    quux => 5,
};

foreach my $k (keys(%$h)) {
    print "key = $k, value = $h->{$k}\n";
    last if $k eq "baz";
}

print "--------------------\n";

foreach my $k (keys(%$h)) {
    print "key = $k, value = $h->{$k}\n";
}

参考

each - perldoc.perl.org
http://perldoc.perl.org/functions/each.html

keys - perldoc.perl.org
http://perldoc.perl.org/functions/keys.html

5
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5