こんにちは、Perl 6アドベントカレンダーの四日目の投稿になります。
NLP 5本ノックということで、東北大学の言語処理100本ノックの『第2章: UNIXコマンドの基礎』の 10~14までの5本をPerl 6で解きつつ解説をしていきたいと思います。
問題文は掲載しませんので、ブラウザの別窓で下記ページを参照しながらお楽しみください:
http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch2
読み進める前に
注意!
- 読者レベルとしては、Perl 6アドベントカレンダー1日目で紹介したPerl 6 introductionなどのチュートリアルを一通り終えたレベルを想定しています。
- わかりやすく紹介するために、正確ではない表現がところどころ出てくるかもしれません。
- より正確な情報を知りたい場合は、Perl 6アドベントカレンダー1日目で紹介した公式ドキュメント や公式テストケースのroastなどを参照ください。
- 日本語訳が定着していないようなPerl 6独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
- Perl 6のモットーはTMTOWTDI (やり方は一つじゃない) です。特に正解はありません。でも、「これのほうがもっとかっこよく簡潔に書けるよ!」といった指摘は大歓迎です!
-
《一列目の要素》
といった表記が出てくるかと思いますが、 "《"や"》"は単に強調のために使っているだけでありPerl 6自身の文法ではないので注意してください。
10. 行数のカウント
コード
10.p6
$*IN.lines.elems.say;
解説
-
$*IN.lines
で標準入力を受け取り、遅延リストを生成します -
.elems
でinvocantのリストの要素数を算出します -
.say
で要素数を出力します
補足
- 以前は要素数を効率的に数えるメソッドとして
Iterator.count-only
というメソッドが存在していました。しかし、バージョン2016.11のrakudoにおいてもサポートされなくなっているので、この例では用いていません。
11. タブをスペースに置換
コード
11.p6
.trans("\t" => " ").say for $*IN.lines;
解説
-
for $*IN.lines
で標準入力の各行を走査していきます -
.trans("\t" => " ")
でinvocant(i.e. 行の文字列)に含まれるタブを半角スペースに変換します -
.say
で出力します
12. 1列目をcol1.txtに,2列目をcol2.txtに保存
コード
12.p6
my $col1-fh = open("col1.txt", :w);
my $col2-fh = open("col2.txt", :w);
my @col1-col2 = $*IN.lines.map: { .split("\t")[0,1] };
$col1-fh.say(@col1-col2>>.[0].join("\n"));
$col2-fh.say(@col1-col2>>.[1].join("\n"));
$col1-fh.close;
$col2-fh.close;
解説
-
open("col1.txt", :w)
で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します -
open("col2.txt", :w)
で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します -
$*IN.lines.map
で標準入力の各行を変換して、リストにします。map
内のポインティブロックでは下記のような操作がおこなわれます - 標準入力の各行を
split("\t")
でタブ文字で分割してリストにします - 1列目と2列目だけを抜き出して、(《1列目の要素》,《2列目の要素》)のリストを生成します
-
$col1-fh.say(《TEXT》)
でcol1.txt
に《TEXT》の書き込みをします -
@col1-col2>>.[0]
で《1列目の要素》だけを抜き出してリストにします -
.join("\n")
で改行区切りでinvocantを結合します -
$col2-fh.say(《TEXT》)
でcol2.txt
に《TEXT》の書き込みをします -
@col1-col2>>.[1]
で《2列目の要素》だけを抜き出してリストにします -
.join("\n")
で改行区切りでinvocantを結合します -
.close
でファイルハンドルを閉じます
13. col1.txtとcol2.txtをマージ
コード
13.p6
my $col1-fh = open("col1.txt", :r);
my $col2-fh = open("col2.txt", :r);
.say for ($col1-fh.lines Z $col2-fh.lines)>>.join("\t");
$col1-fh.close;
$col2-fh.close;
解説
-
open("col1.txt", :r)
で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します -
open("col2.txt", :r)
で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します -
($col1-fh.lines Z $col2-fh.lines)
で1列目と2列目をZip演算子で結合します -
>>.join("\t")
でリストの各要素をタブで結合します -
.close
でファイルハンドルを閉じます
補足
- Zip演算子について詳しく知りたい場合は次のページを参照してください:https://docs.perl6.org/routine/Z
14. 先頭からN行を出力
コード
14.p6
sub MAIN(Int :$N!) {
.say for gather for $*IN.lines { take $_ }.head($N);
}
解説
-
sub MAIN
を使うとコマンドライン引数を受け取とれるようになります-
:$N!
の!
はrequiredの意味です。つまり必須オプションです。 ちなみに?
だと任意のオプションになります。
-
-
gather for take
イディオムで遅延リストを生成します-
$*IN.lines { take $_ }
で各行を取り出します
-
-
.head($N)
でコマンドライン引数Nで与えた値と同数の行を遅延リストの先頭から抜き出します -
.say for 《.head($N)の返したリスト》
で各行を出力していきます
補足
- MAINについて詳しく知りたい場合は次のページを参照してください:https://docs.perl6.org/language/functions#sub_MAIN
- もっと親切なヘルプメッセージを出したいと思った方もいるでしょう。そういう場合は
sub USAGE
を使うとよいです。つぎのページを参照するとよいでしょう。:https://docs.perl6.org/language/functions#sub_USAGE- 実際のアプリケーションだとzefやpandaなどのUSAGEの使い方が参考になるでしょう
以上、Perl 6でNLP 5本ノック 『第2章: UNIXコマンドの基礎』 10 ~ 14 でした。