今日も「かんたんPerl補遺」として、本書では紹介しきれなかった関数を紹介します。前回(Opus 9)ではリストをリストに変換するmap関数を研究しました。
 復習すると、

my @出力リスト = ();
for (@入力リスト) {
 push @出力リスト, 関数($_);
}

のような処理は、

my @出力リスト = map {関数($_)} @入力リスト;

のように書き換えられます。
 短く、スッキリしますし、数珠つなぎできるのでラクチンです。
 リストをリストに変換するのはmapと覚えましょう。

 さて、grepはmapと似ていますが、リストから値を抽出してサブセットのリストを作る関数です。

Aurore-gr5
my @出力リスト = ();
for (@入力リスト) {
 if (関数($_)) {
  push @出力リスト, $_;
 }
}

のような処理は、

my @出力リスト = grep {関数($_)} @入力リスト;

のように書き換えることが出来ます。つまり、関数が真を返すリスト要素のみを返します。

 『かんたんPerl』の第11章では、AKB総選挙の「恋するフォーチュンクッキー」選抜の中から、名前が3文字のメンバーを、正規表現を使って以下のように抽出していました。

#! /usr/bin/perl
#
# matchDot.pl -- 名前が3文字のメンバー(ドット(.)による検索)

use 5.010;
use strict;
use warnings;
use utf8;

if ($^O eq "MSWin32") {
 binmode STDOUT, ":encoding(Shift_JIS)";
} else {
 binmode STDOUT, ":encoding(UTF-8)";
}

while (<DATA>) {
 print if / ...\t/; # スペースとタブの間に3文字
}

__DATA__
1 指原 莉乃 Rino_Sashihara@example.com Team-H HKT48
2 大島 優子 Yuuko_Ohshima@example.com Team-K AKB48
3 渡辺 麻友 Mayu_Watanabe@example.com Team-B AKB48
4 柏木 由紀 Yuki_Kashiwagi@example.com Team-B AKB48
5 篠田 麻里子 Mariko_Shinoda@example.com Team-A AKB48
6 松井 珠理奈 Jurina_Matsui@example.com Team-E SKE48
7 松井 玲奈 Rena_Matsui@example.com Team-S SKE48
8 高橋 みなみ Minami_Takahashi@example.com Team-A AKB48
9 小嶋 陽菜 Haruna_Kojima@example.com Team-A AKB48
10 宮澤 佐江 Sae_Miyazawa@example.com Team-K AKB48
11 板野 友美 Tomomi_Itano@example.com Team-K AKB48
12 島崎 遥香 Haruka_Shimazaki@example.com Team-B AKB48
13 横山 由依 Yui_Yokoyama@example.com Team-A AKB48
14 山本 彩 Sayaka_Yamamoto@example.com Team-N NMB48
15 渡辺 美優紀 Miyuki_Watanabe@example.com Team-M NMB48
16 須田 亜香里 Akari_Suda@example.com Team-KII SKE48

 実行するとこうなります。

[sample]$ ./matchDot.pl
5 篠田 麻里子 Mariko_Shinoda@example.com Team-A AKB48
6 松井 珠理奈 Jurina_Matsui@example.com Team-E SKE48
8 高橋 みなみ Minami_Takahashi@example.com Team-A AKB48
15 渡辺 美優紀 Miyuki_Watanabe@example.com Team-M NMB48
16 須田 亜香里 Akari_Suda@example.com Team-KII SKE48

 キモはこの部分です。

while (<DATA>) {
 print if / ...\t/; # スペースとタブの間に3文字
}

 苗字と名前の間には(このデータで唯一)スペースがあり、名前が3文字ということはスペースとタブの間に任意の文字「.」が3個あるということなので、そのデータにマッチする正規表現は「 ...\t」となります。これが真のとき、データをprintしています。
 ここでは、whileを使ってDATAファイル ハンドルのデータを取得しています。
 この場合<DATA>は、スカラー コンテキストで評価されます。
 さて、このプログラムをgrepで書きなおしてみます。

#! /usr/bin/perl
#
# matchDotGrep.pl -- 名前が3文字のメンバー(ドット(.)による検索、grep版)

use 5.010;
use strict;
use warnings;
use utf8;

if ($^O eq "MSWin32") {
 binmode STDOUT, ":encoding(Shift_JIS)";
} else {
 binmode STDOUT, ":encoding(UTF-8)";
}

print grep { / ...\t/ } <DATA>;

__DATA__
…以下略…

 見た目がスッキリしましたね。
 この場合、<DATA>はリスト コンテキストで評価され、各々のリスト要素についてコード ブロックが評価され、真を返すリスト要素のみから出来たリスト要素が返されるので、それをprintしています。

 名前が3文字のメンバーの、名前とメール アドレスだけを抽出してみましょう。(念のため、このメール アドレスは、こういう解説文を書くときに使うようにRFC 2606で定められているドメイン名example.comを使っているので、誰にも迷惑は掛かりません。)

 while版はこうなります。


#! /usr/bin/perl
#
# matchDotNM.pl -- 名前が3文字のメンバーの名前とメール アドレスだけを返す

use 5.010;
use strict;
use warnings;
use utf8;

if ($^O eq "MSWin32") {
 binmode STDOUT, ":encoding(Shift_JIS)";
} else {
 binmode STDOUT, ":encoding(UTF-8)";
}

while (<DATA>) { # スペースとタブの間に3文字
 if (/ ...\t/) {
  my (undef, $name, $mail) = split /\t/;
  say "$name $mail";
 }
}

__DATA__
…後略…

 grep版は、mapを数珠つなぎしてみます。

#! /usr/bin/perl
#
# matchDotNMf.pl -- 名前が3文字のメンバーの名前とメール アドレスだけを返す(mapとgrep)

use 5.010;
use strict;
use warnings;
use utf8;

if ($^O eq "MSWin32") {
 binmode STDOUT, ":encoding(Shift_JIS)";
} else {
 binmode STDOUT, ":encoding(UTF-8)";
}

print map { join("\t", (split "\t")[1, 2])."\n" } grep { / ...\t/ } <DATA>;

__DATA__
…後略…

 若干可読性が落ちた気がしますが、実行してみます。

[sample]$ ./matchDotNMf.pl
篠田 麻里子 Mariko_Shinoda@example.com
松井 珠理奈 Jurina_Matsui@example.com
高橋 みなみ Minami_Takahashi@example.com
渡辺 美優紀 Miyuki_Watanabe@example.com
須田 亜香里 Akari_Suda@example.com
[sample]$

 バッチリですね。

 Perlはmap、grepの他に、sortも手続きを引数として取ります。
 これを高階関数といい、いま流行の関数型プログラミングのもとになっている考え方です。