2012/11/23

リストを任意件数ごとにぬるぬる処理する用のモジュール書いた

なんらかのリストの要素、要素に処理をするなんて場面よくありますよね。んで、そんなとき、リストが長大で 1 件ずつやってたら埒が明かなくて、適当な件数ごとに分割してぬるぬるしないとやってられない、みたいな深遠な事情もありがちですよね。

さて、まあ、そんなのを、毎度書くのに飽きたので、少し楽できそうなモジュール書いてみました。

Sub::Sequence

こんな感じです。

use Sub::Sequence;

my @user_id_list = (1..10_000_000);

seq \@user_id_list, 50, sub {
    my $list = shift;

    my $in_id = join ',', map { int $_; } @{$list};

    # UPDATE table SET status=1 WHERE id IN ($in_id)

    sleep 1;
};

use Sub::Sequence; すると、seq という関数がエクスポートされます。この関数に、処理対象のリストリファレンスと、1回に処理する件数と、コードリファレンスを渡します。

コードリファレンスには、指定された件数で分割されたリストリファレンスが順々に渡ってきます。POD の方に書いてありますが、何回めの処理かとか、オフセット位置なんかも渡ってくるので、必要とあらば受け取って使えます。また、上のソースでは無視していますが、seq からの返り値は、処理ごとの返り値がリストリファレンスになって返ります。

0.02 から次のように seq の返り値の仕様を変えました。
まず、スカラーコンテキストの場合は処理ごとの返り値がリストリファレンスになって返り、リストコンテキストの場合は各処理の返り値がリストリファレンスの場合、ネストしないリストに整形して返します。

例えばこんな感じです。

use Sub::Sequence;
use Data::Dumper;

# scalar context
my $foo = seq [1, 2, 3, 4, 5], 2, sub {
    my @list = @{ $_[0] };
    return \@list;
};
warn Dumper($foo); # [ [1, 2], [3, 4], [5] ]

# list context
my @bar = seq [1, 2, 3, 4, 5], 2, sub {
    my @list = @{ $_[0] };
    return \@list;
};
warn Dumper(\@bar); # [ 1, 2, 3, 4, 5 ]

ところで、インターフェースが似ているのでお気づきの方も少なくないかと思いますが、あらかた Sub::Retry を参考にしました。

@__gfx__@xaicronIterator::GroupedRangeList::MoreUtils の natatime でも同じようなことができるよって教えてもらったんですが、まあたぶんインターフェースが超簡単でタイプ数が少なくてすむのが seq のウリですんで、そこのところ覚えておいてください。テストにでるよ!

あと、英語でこのモジュールの説明ができなくて困ってます。だれか助けてください。 というわけで、上で言及してる似た機能のモジュールから文章ぱくりました。テヘペロ。

CPAN にも上げました。Sub::Sequence

さいごに、splice っていうどんぴしゃの関数があるよ!という言及を頂いたのですが、

while (my @fifteen = splice @hundred, 0, 15) { say join ", ", fizzbuzz @fifteen; }

splice は、以下のように破壊的だから嫌いなんです。

$ perl -le 'my @c=(1,2,3,4,5); splice @c, 0, 2; print "@c";'
3 4 5

あと、splice で書くと、どのみち while とか for とかループ書くことになるので、結局 楽できないと思うんですよね。その点 seq ならまずひとつループを書くのを省略できて、そのループの中でやるべきことに集中できます。

サイト内検索