Perl日記

日々の知ったことのメモなどです。Perlは最近やってないです。

eachの途中で止めると再開が途中からになる話

Rubyのeach_with_indexは添字と要素が一度に取れるのでとても便利なときがある。


Perlで同じことをするには、5.12までは、ループ外に変数を置いて、ループ内でインクリメントするのが一般的だったけれど
5.12からはeach()が配列に対しても使えるようになって、添字と要素が一度に取れるようになった。


が、これを途中で止めて、再度eachを使用すると、その途中からループが始まってしまう
内部的なイテレータの状態を保持しているらしい。

use strict;
use warnings;

package Hoge {
  sub new {
    bless [[1..10],[11..20],[21..30]], __PACKAGE__;
  }

  sub print_key {
    my $self = shift;

    while (my ($i, $elm) = each @$self) {
      while (my ($j, $val) = each @$elm) {
        print "$i: $j: $val\n";
        return if $i == 1 && $j == 5;
      } 
    } 
  }
}

my $hoge = Hoge->new;
$hoge->print_key;
print "-------------------\n";
$hoge->print_key;
0: 0: 1
0: 1: 2
0: 2: 3
0: 3: 4
0: 4: 5
0: 5: 6
0: 6: 7
0: 7: 8
0: 8: 9
0: 9: 10
1: 0: 11
1: 1: 12
1: 2: 13
1: 3: 14
1: 4: 15
1: 5: 16      #=> 1-5でとめる
-------------------
2: 0: 21      #=> 再開は2-0から
2: 1: 22
2: 2: 23
2: 3: 24
2: 4: 25
2: 5: 26
2: 6: 27
2: 7: 28
2: 8: 29
2: 9: 30

再開は2-6と思いきや、2-0からであり、イテレータの保存は外側だけ働いたことが分かる。


ちなみに、イテレータをリセットするにはvoidにkeys()を呼べばよいとのこと。
http://perldoc.perl.org/functions/keys.html


面白いのは(注意しないといけないのは)、外側をリセットすると、ループの再開で内側のイテレータの継続が有効になるということ。

my $hoge = Hoge->new;
$hoge->print_key;
keys @$hoge; #=> リセット!
print "-------------------\n";
$hoge->print_key;
0: 0: 1
0: 1: 2
0: 2: 3
0: 3: 4
0: 4: 5
0: 5: 6
0: 6: 7
0: 7: 8
0: 8: 9
0: 9: 10
1: 0: 11
1: 1: 12
1: 2: 13
1: 3: 14
1: 4: 15
1: 5: 16     #=> 1-5でとめる 
-------------------
0: 0: 1      #=> 再開は0-0
0: 1: 2
0: 2: 3
0: 3: 4
0: 4: 5
0: 5: 6
0: 6: 7
0: 7: 8
0: 8: 9
0: 9: 10
1: 6: 17      #=> 1-1から1-5はスキップされる
1: 7: 18
1: 8: 19
1: 9: 20
2: 0: 21
2: 1: 22
2: 2: 23
2: 3: 24
2: 4: 25
2: 5: 26
2: 6: 27
2: 7: 28
2: 8: 29
2: 9: 30