LoginSignup
0
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-04

こんにちは、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独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
  • 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の意味です。つまり必須オプションです。 ちなみに?だと任意のオプションになります。
  1. gather for take イディオムで遅延リストを生成します
    • $*IN.lines { take $_ } で各行を取り出します
  2. .tail($N) でコマンドライン引数Nで与えた値と同数の行を遅延リストの末尾から抜き出します
  3. .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;
}

解説

  1. ["xaa".."xzz"]で出力ファイル名のリストを生成し、変数に代入します
  2. repeat-until文を使って、標準入力が尽きるまで:$N行ごとにファイルに書き出していきます
    1. state $idx = 0で現在のファイル(e.g. 0なら@filename[0] eq "xaa", 1なら@filename[1] eq "xab")を示すための変数を宣言します。stateは初期化を一度しか行わないという宣言子です。
    2. do for ^$N 《ブロック》で《ブロック》内の操作を$N回呼び出し、結果をリストに代入します。《ブロック》内では$*IN.getを使って標準入力から一行ずつ読み取りを行います
    3. @filename[$idx]で書き込みモードでファイルハンドルをオープンします
    4. $fh.say(@group.join("\n"))でファイルに書き込みます
    5. $fh.closeでファイルハンドルを閉じます
    6. $idxをインクリメントし、次のループへ進みます

17. 1列目の文字列の異なり

コード

17.p6
$*IN.lines.map({ .split("\t")[0] }).Set.elems.say;

解説

  1. $*IN.linesで標準入力から遅延リストを生成します
  2. .mapを使って各行を変換します
    • .split("\t")[0]でタブで分割して一列目だけ取り出します
  3. .SetでSet型に変換します
  4. .elemsで要素数を算出します
  5. .sayで出力します

18. 各行を3コラム目の数値の降順にソート

コード

18.p6
.say for $*IN.lines.map({ .split("\t") })\
.sort({ $^b[2] <=> $^a[2] })>>.join("\t");

解説

  1. $*IN.linesで標準入力から遅延リストを生成します
  2. .mapを使って各行を変換します
    • .split("\t")でタブで分割してリストにします
  3. .sort({ $^b[2] <=> $^a[2] })で3列目の数字の降順にソートします
  4. >>.join("\t")で各行を再度タブ区切りに戻します
  5. .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")

解説

  1. $*IN.linesで標準入力から遅延リストを生成します
  2. .mapを使って各行を変換します
    • .split("\t")[0]で一列目だけ取り出します
  3. .categorizeで要素それ自身をキーとして、valueも要素それ自身を入れます(※)
  4. .map({ (.key, .value.elems) })でinvocantを(《ハッシュのキー》,《そのキーに紐づく値の要素数》)のリストに変換します
  5. .sort({ $^b[1] <=> $^a.[1] })でinvocantのリストの列の2番目(i.e. 《そのキーに紐づく値の要素数》)の要素の降順に並べ替えます
  6. >>.join("\t")で各要素をタブ区切りの文字列にします
  7. .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つ目的があります:

  1. Web検索しても現行のPerl6をNLPのシーンで真面目に使っている例があまり見つからないので、そういうのをちょっとは作っておきたい
  2. 拙作p6-MeCabでサクッとPerl6ishに0~39の問題を解いてみるため

というわけで、今年のアドベントカレンダーで100本全部をやりきるつもりは全くないので、期待していた方はすみません。
また、せっかくなので別のネタを書いてみたいというのもあります。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0