foreachとmapとgrepを使い分ける話

Created by @techno_neko

read-onlyの処理にはforeachを使うべし


    my @ary = 1..10;
    my $sum = 0;
    foreach ( @ary ) {
        $sum += $_;
    }
    say $sum; # => 55

    my $sum2 = 0;
    $sum2 += $_ for @ary;
    say $sum2; # => 55
					

でも、mapでも書けるよ?


    my @ary = 1..10;
    my $sum = 0;
    map { $sum += $_; } @ary;

    say $sum; # => 55
					

これが不適当な理由は後ほど。

List::Utilにsumがあるんだけどね


    use List::Util qw/sum/;

    my @ary = 1..10;
    say sum(@ary); # => 55
					

List::Utilはコアモジュールなので、
cpanmコマンドでインストールしなくても使えるよ!

mapは、あるリストから別のリストを作るときに使うべし!


    my @odd_numbers = ( 1, 3, 5, 7 );
    my @even_numbers = map { $_ + 1; } @odd_numbers;

    say join(', ', @even_numbers); # => 2, 4, 6, 8
					

でも、foreachでも書けるよ?


    my @odd_numbers = ( 1, 3, 5, 7 );
    my @even_numbers = ();
    foreach ( @odd_numbers ) {
        push @even_numbers, $_ + 1;
    }

    say join(', ', @even_numbers); # => 2, 4, 6, 8
					

これだったら、mapの方が楽でいいね!

mapは意味のある値を返すべき!


    # foreachの代わりに・・・
    my @ary = 1..5;
    map { $_++; } @ary;
    say join(', ', @ary); # => 2, 3, 4, 5, 6
					

だって、これだとmapである必要がないよね。

mapの引数を書き変えちゃダメ!


    my @ary = 1..5;
    my @new_ary = map { $_++; $_; } @ary;
    say join(', ', @ary); # => 2, 3, 4, 5, 6
    say join(', ', @new_ary); # => 2, 3, 4, 5, 6
					

mapの中で書き換えるのは、お行儀が悪い。

myを使って、引数の書き換えを回避しよう


    my @ary = 1..5;
    my @new_ary = map { my $wk = $_; $wk++; $wk; } @ary;
    say join(', ', @ary); # => 1, 2, 3, 4, 5
    say join(', ', @new_ary); # => 2, 3, 4, 5, 6
					

s///で置換した結果を取得する場合など。
s///rを使うと、一時変数に格納する必要がない。
非破壊置換修飾子(rオプション)はv5.14から。

リストを書き換えるならforeachを使おう


    my @ary = 1..5;
    foreach ( @ary ) {
        $_++;
    }
    say join(', ', @ary); # => 2, 3, 4, 5, 6
					

この場合でも書き変わるよ


    my @ary = 1..5;
    foreach my $val ( @ary ) {
        $val++;
    }
    say join(', ', @ary); # => 2, 3, 4, 5, 6
					

$_の代わりに、my $valを使ってる点に注意。

これでも良いけどね!


    my @ary = 1..5;
    for (my $i=0; $i<scalar(@ary); $i++) {
        $ary[$i] += $i;
    }
    say join(', ', @ary); # => 1, 3, 5, 7, 9
					

grepは要素の選択に使うべし!


    my @ary = 1..5;
    my @even = grep { $_ % 2 == 0 } @ary;
    say join(', ', @even); # => 2, 4
					

スカラーで結果を受け取ると?


    my @ary = 1..5;
    my $number_of_even = grep { $_ % 2 == 0 } @ary;
    my @even_numbers = grep { $_ % 2 == 0 } @ary;
    say join(', ', @even_numbers); # => 2, 4
    say $number_of_even; # => 2
    my $number_of_odd = grep { $_ % 2 == 1 } @ary;
    my @odd_numbers = grep { $_ % 2 == 1 } @ary;
    say join(', ', @odd_numbers); # => 1, 3, 5
    say $number_of_odd; # => 3
					

リスト(@foo)ではなくスカラー($bar)で受け取ると、
要素数が返ってくる。

もちろんforeachでも書ける


    my @ary = 1..5;
    my @even = ();
    foreach my $val ( @ary ) {
        push @even, $val if ( $val % 2 == 0 );
    }
    say join(', ', @even); # => 2, 4
					

これなら、grepを使った方が良いね!

こういう風にgrepを使うのはよくない!


    my @ary = 1..5;
    grep { $_++; } @ary; # 何やってるの!?
    say join(', ', @ary); # => 2, 3, 4, 5, 6
						

TMTOWTDI(やり方はひとつじゃない)にもほどがある!

こんなコードを書いていた時期もありました


    my @ary = 1..5;
    map {
        $ary[$_]++;
    } 0..(scalar(@ary) - 1);
    say join(', ', @ary); # => 2, 3, 4, 5, 6