高度なリスト操作 grep

今度は少し高度なリスト操作を行なう関数を勉強してみます。Perl のトリッキーなコードには必ず出てくるのが、このリスト操作関数の類。grep や map などです。意味は文脈から何となく想像していたのですが、ちゃんと勉強したいと思います♪
手始めは「grep」関数。UNIX系のOSでは「ファイルから指定したパターンが含まれる行を抽出する」コマンドとしてお馴染みですね。
Perlgrep 関数は、第一引数に「式」もしくは「ブロック」をとり、第二引数に「リスト」をとります。そして、foreach のようにリストの各要素を $_ にセットしながら、第一引数の式・ブロックを評価していきます。評価した結果が真なら、新しいリストに $_ を加え、偽なら加えません。
リストコンテキストで grep 関数を呼び出した場合、出来上がった新しいリストが返ってきます*1。つまり、「条件に合う要素をリストから抽出する」という事です。UNIXgrepとそっくりですね。(^_^)
早速使ってみます♪

use strict;
 
my @fruits = qw(pine banana apple lemon grape mango);
print "@fruits\n";
 
my @e_fruits = grep /e$/, @fruits;
print "@e_fruits\n";

実行結果は以下の通りです。

pine banana apple lemon grape mango
pine apple grape

フルーツの名前のリスト @fruits を「/e$/」という条件で絞り込んでいます。「/e$/」は「最後についている"e"」を表す正規表現の検索パターンです。検索パターンだけ書いた場合、「$_」が検索の対象になるのでした。以下と等価です。

my @e_fruits = grep $_ =~ /e$/, @fruits;

つまり、この grep は @fruits の中から「最後に e がつくフルーツだけを抽出」している事になります。for(each) ループを使って書き直した場合、こんな感じでしょうか。

my @e_fruits = ();
for (@fruits) { if (/e$/) { push @e_fruits, $_; } }

grep を使っていませんが、これに置き換えて実行しても同じ結果になります。@fruits の各要素からパターンにマッチするものを @e_fruits に push しています。速度や効率はどちらの方がいいんでしょうね?


上の例では grep に「式」を与えましたが、「ブロック」を渡す事も可能です。「{ }」(中カッコ)で囲んだ部分がブロックになります。*2
$_ にリストの要素が入った状態でブロックが実行されるので、ブロックの中から真偽を返します。

use strict;
 
my @numbers = (1, 1, 2, 3, 5, 8, 13, 21, 34);
print "@numbers\n";
 
my @evens = grep { ($_ % 2) == 0; } @numbers;
print "@evens\n";

「{ ($_ % 2) == 0; }」が grep に渡しているブロックです。ブロックを関数に渡す場合、ブロックの後ろの「,」(カンマ)は必要ないので注意しましょう。
サブルーチン(関数)から返り値を返すには「return」を使っていましたが、このブロックの中では使えません。実は、Perl ではブロックを評価した場合、ブロックの最後の式(値)が返り値となるので、それを利用して真偽を返します。
「($_ % 2) == 0」は、「$_ を 2 で割った余りが 0($_ が 2 で割り切れる)」なら真を、違うなら偽を返す論理式ですが、ブロックの最後にあるので、この論理式の返り値がブロックの返り値(真偽)になります。
実行結果は以下の通りです。

1 1 2 3 5 8 13 21 34
2 8 34

偶数だけが取り出せました。(^_^)

疑問

ところで、『grep に「式」を渡す時、以下のように書ける』と書きましたが

use strict;
 
my @fruits = qw(pine banana apple lemon grape mango);
print "@fruits\n";
 
my @e_fruits = grep $_ =~ /e$/, @fruits;
print "@e_fruits\n";

この書き方だと一見「$_ =~ /e$/」の評価結果が grep に引数として渡されてしまうように見えます。でも実際は正常に動きます。とても不思議です。
grep の場合は特別に遅延評価するようになっているのでしょうか? それとも自動的にブロックに変換されるのかな? と、疑問を投げかけてみる。(^_^)

*1:カラコンテキストの場合は新しいリストの要素の数が返ってきます

*2:実はコードを引数として渡す関数は前にも勉強しました。sort です。(^_^