Perl 6でNLP 5本ノック 『第2章: UNIXコマンドの基礎』 10 ~ 14

こんにちは、Perl 6アドベントカレンダーの四日目の投稿になります。

NLP 5本ノックということで、東北大学の言語処理100本ノックの『第2章: UNIXコマンドの基礎』の 10~14までの5本をPerl 6で解きつつ解説をしていきたいと思います。

問題文は掲載しませんので、ブラウザの別窓で下記ページを参照しながらお楽しみください：
http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch2

# 読み進める前に

## 注意!
+ 読者レベルとしては、[Perl 6アドベントカレンダー1日目](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1)で紹介した[Perl 6 introduction](https://github.com/hankache/perl6intro)などのチュートリアルを一通り終えたレベルを想定しています。
+ わかりやすく紹介するために、正確ではない表現がところどころ出てくるかもしれません。
 + より正確な情報を知りたい場合は、[Perl 6アドベントカレンダー1日目](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1)で紹介した[公式ドキュメント](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1#%E5%85%AC%E5%BC%8F%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88) や公式テストケースの[roast](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1#roast)などを参照ください。
+ 日本語訳が定着していないようなPerl 6独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
+ Perl 6のモットーはTMTOWTDI (やり方は一つじゃない) です。特に正解はありません。でも、「これのほうがもっとかっこよく簡潔に書けるよ！」といった指摘は大歓迎です！
+ `《一列目の要素》`といった表記が出てくるかと思いますが、 "《"や"》"は単に強調のために使っているだけでありPerl 6自身の文法ではないので注意してください。

# 10. 行数のカウント

## コード

```pl:10.p6
$*IN.lines.elems.say;
```

## 解説

1. `$*IN.lines` で標準入力を受け取り、遅延リストを生成します
2. `.elems`でinvocantのリストの要素数を算出します
3. `.say`で要素数を出力します

## 補足

+ 以前は要素数を効率的に数えるメソッドとして`Iterator.count-only`というメソッドが存在していました。しかし、バージョン2016.11のrakudoにおいてもサポートされなくなっているので、この例では用いていません。
   + https://github.com/rakudo/rakudo/commit/fd98fc342a34271227347d5ba8ca05e1e3f4db32 

# 11. タブをスペースに置換

## コード

```pl:11.p6
.trans("\t" => " ").say for $*IN.lines;
```

## 解説

1. `for $*IN.lines`で標準入力の各行を走査していきます
2. `.trans("\t" => " ")`でinvocant(i.e. 行の文字列)に含まれるタブを半角スペースに変換します
3. `.say`で出力します


# 12. 1列目をcol1.txtに，2列目をcol2.txtに保存

## コード

```pl: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;
```

## 解説

1. `open("col1.txt", :w)` で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します
2. `open("col2.txt", :w)` で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します
3. `$*IN.lines.map` で標準入力の各行を変換して、リストにします。`map`内のポインティブロックでは下記のような操作がおこなわれます
  1. 標準入力の各行を`split("\t")`でタブ文字で分割してリストにします
  2. 1列目と2列目だけを抜き出して、(《1列目の要素》,《2列目の要素》)のリストを生成します
4. `$col1-fh.say(《TEXT》)`で`col1.txt`に《TEXT》の書き込みをします
  1. `@col1-col2>>.[0]`で《1列目の要素》だけを抜き出してリストにします
  2. `.join("\n")`で改行区切りでinvocantを結合します
5. `$col2-fh.say(《TEXT》)`で`col2.txt`に《TEXT》の書き込みをします
  1. `@col1-col2>>.[1]`で《2列目の要素》だけを抜き出してリストにします
  2. `.join("\n")`で改行区切りでinvocantを結合します
6. `.close`でファイルハンドルを閉じます

# 13. col1.txtとcol2.txtをマージ

## コード

```pl: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;
```

## 解説

1. `open("col1.txt", :r)` で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します
2. `open("col2.txt", :r)` で書き込み専用モードのIO::Handleオブジェクトを生成し変数に代入します
3. `($col1-fh.lines Z $col2-fh.lines)`で1列目と2列目をZip演算子で結合します
4. `>>.join("\t")`でリストの各要素をタブで結合します
5. `.close`でファイルハンドルを閉じます

## 補足

+ Zip演算子について詳しく知りたい場合は次のページを参照してください：https://docs.perl6.org/routine/Z

# 14. 先頭からN行を出力

## コード

```pl:14.p6
sub MAIN(Int :$N!) {
    .say for gather for $*IN.lines { take $_ }.head($N);
}
```

## 解説

+ `sub MAIN`を使うとコマンドライン引数を受け取とれるようになります
  + `:$N!`の`!`はrequiredの意味です。つまり必須オプションです。 ちなみに`?`だと任意のオプションになります。

1. `gather for take` イディオムで遅延リストを生成します
   + `$*IN.lines { take $_ }` で各行を取り出します
2. `.head($N)` でコマンドライン引数Nで与えた値と同数の行を遅延リストの先頭から抜き出します
3. `.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　でした。