こんにちは、Perl 6アドベントカレンダーの五日目の投稿になります。
NLP 5本ノックということで、東北大学の言語処理100本ノックの『第2章: UNIXコマンドの基礎』の 15~19までの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自身の文法ではないので注意してください。
15. 末尾のN行を出力
コード
15.p6
sub MAIN(Int :$N!) {
.say for gather for $*IN.lines { take $_ }.tail($N);
}
解説
-
sub MAIN
を使うとコマンドライン引数を受け取とれるようになります-
:$N!
の!
はrequiredの意味です。つまり必須オプションです。 ちなみに?
だと任意のオプションになります。
-
-
gather for take
イディオムで遅延リストを生成します-
$*IN.lines { take $_ }
で各行を取り出します
-
-
.tail($N)
でコマンドライン引数N
で与えた値と同数の行を遅延リストの末尾から抜き出します -
.say for 《.tail($N)の返したリスト》
で各行を出力していきます
16. ファイルをN分割する
コード
16.p6
sub MAIN(Int :$N!) {
my @filenames = ["xaa".."xzz"];
repeat {
state $idx = 0;
my @group = do for ^$N {
last if $*IN.eof;
~$*IN.get;
}
my $fh = open(@filenames[$idx], :w);
$fh.say(@group.join("\n"));
$fh.close;
$idx++;
} until $*IN.eof;
}
解説
-
["xaa".."xzz"]
で出力ファイル名のリストを生成し、変数に代入します -
repeat-until
文を使って、標準入力が尽きるまで:$N
行ごとにファイルに書き出していきます -
state $idx = 0
で現在のファイル(e.g. 0なら@filename[0]
eq "xaa", 1なら@filename[1]
eq "xab")を示すための変数を宣言します。state
は初期化を一度しか行わないという宣言子です。 -
do for ^$N 《ブロック》
で《ブロック》内の操作を$N
回呼び出し、結果をリストに代入します。《ブロック》内では$*IN.get
を使って標準入力から一行ずつ読み取りを行います -
@filename[$idx]
で書き込みモードでファイルハンドルをオープンします -
$fh.say(@group.join("\n"))
でファイルに書き込みます -
$fh.close
でファイルハンドルを閉じます -
$idx
をインクリメントし、次のループへ進みます
17. 1列目の文字列の異なり
コード
17.p6
$*IN.lines.map({ .split("\t")[0] }).Set.elems.say;
解説
-
$*IN.lines
で標準入力から遅延リストを生成します -
.map
を使って各行を変換します
-
.split("\t")[0]
でタブで分割して一列目だけ取り出します
-
.Set
でSet型に変換します -
.elems
で要素数を算出します -
.say
で出力します
18. 各行を3コラム目の数値の降順にソート
コード
18.p6
.say for $*IN.lines.map({ .split("\t") })\
.sort({ $^b[2] <=> $^a[2] })>>.join("\t");
解説
-
$*IN.lines
で標準入力から遅延リストを生成します -
.map
を使って各行を変換します
-
.split("\t")
でタブで分割してリストにします
-
.sort({ $^b[2] <=> $^a[2] })
で3列目の数字の降順にソートします -
>>.join("\t")
で各行を再度タブ区切りに戻します -
.say for 《4.で生成されたリスト》
で各行を出力します
19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
コード
19.p6
.say for $*IN.lines.map({ .split("\t")[0] })\
.categorize({ $_ })\
.map({ (.key, .value.elems) })\
.sort({ $^b[1] <=> $^a.[1] })>>.join("\t")
解説
-
$*IN.lines
で標準入力から遅延リストを生成します -
.map
を使って各行を変換します
-
.split("\t")[0]
で一列目だけ取り出します
-
.categorize
で要素それ自身をキーとして、valueも要素それ自身を入れます(※) -
.map({ (.key, .value.elems) })
でinvocantを(《ハッシュのキー》,《そのキーに紐づく値の要素数》)
のリストに変換します -
.sort({ $^b[1] <=> $^a.[1] })
でinvocantのリストの列の2番目(i.e. 《そのキーに紐づく値の要素数》)の要素の降順に並べ替えます -
>>.join("\t")
で各要素をタブ区切りの文字列にします -
.say for 《6. で出力されたリスト》
で各行を出力します
補足
- (※)適当なサンプルコードで回すと
.categorize
はこんな感じです
profession
$ perl6 -e '.say for ("エンジニア","バーサーカー","バーサーカー","バーサーカー","アルケミスト").categorize({ $_ })'
アルケミスト => [アルケミスト]
バーサーカー => [バーサーカー バーサーカー バーサーカー]
エンジニア => [エンジニア]
- categorizeについて詳しく知りたい方は次のページを参照してください:https://docs.perl6.org/routine/categorize
以上、Perl 6でNLP 5本ノック 『第2章: UNIXコマンドの基礎』 15 ~ 19 でした。
P.S.
あらかじめ書いておくと、私がNLP5本ノックに取り組んでいるのは2つ目的があります:
- Web検索しても現行のPerl6をNLPのシーンで真面目に使っている例があまり見つからないので、そういうのをちょっとは作っておきたい
- 拙作p6-MeCabでサクッとPerl6ishに0~39の問題を解いてみるため
というわけで、今年のアドベントカレンダーで100本全部をやりきるつもりは全くないので、期待していた方はすみません。
また、せっかくなので別のネタを書いてみたいというのもあります。