こんにちは、Perl 6アドベントカレンダーの三日目の投稿になります。
NLP 5本ノックということで、東北大学の言語処理100本ノックの『第1章: 準備運動』の 05~09までの5本をPerl 6で解きつつ解説をしていきたいと思います。
問題文は掲載しませんので、ブラウザの別窓で下記ページを参照しながらお楽しみください:
http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch1
読み進める前に
注意!
- 読者レベルとしては、Perl 6アドベントカレンダー1日目で紹介したPerl 6 introductionなどのチュートリアルを一通り終えたレベルを想定しています。
- わかりやすく紹介するために、正確ではない表現がところどころ出てくるかもしれません。
- より正確な情報を知りたい場合は、Perl 6アドベントカレンダー1日目で紹介した公式ドキュメント や公式テストケースのroastなどを参照ください。
- 日本語訳が定着していないようなPerl 6独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
- Perl 6のモットーはTMTOWTDI (やり方は一つじゃない) です。特に正解はありません。でも、「これのほうがもっとかっこよく簡潔に書けるよ!」といった指摘は大歓迎です!
05. n-gram
コード
05.p6
char-ngram("I am an NLPer", 2).perl.say;
word-ngram("I am an NLPer", 2).perl.say;
my sub char-ngram(Str $text, Int $n) {
my @chars = $text.comb;
gather for ^@chars X $n -> ($start, $len) {
next if $start + $len > @chars;
take @chars[$start..^($start + $len)]
}
}
my sub word-ngram(Str $text, Int $n) {
my @words = $text.words;
gather for ^@words X $n -> ($start, $len) {
next if $start + $len > @words;
take @words[$start..^($start + $len)]
}
}
解説
sub char-ngram(Str $text, Int $n)
-
$text.comb
で引数の文字列を文字単位に分割し、リストを@chars
に代入します -
gather for take
イディオムでn-gramの文字列を取り出して遅延リストを生成します-
^@words X $n
で開始位置,文字列長の組のすべての組み合わせのリストを生成します - 開始位置 + 文字列長が
@words
の長さを超える場合は、その開始位置,文字列長の組に対する処理を止めます -
@words
中の[開始位置,開始位置 + 文字列長)`の箇所の文字列を取得します
-
sub word-ngram(Str $text, Int $n)
sub char-ngram(Str $text, Int $n)
における$text.comb
が$text.words
に置き換わっただけです。$text.words
は引数の文字列を空白区切りで分割します
06. 集合
コード
06.p6
my Set \X = char-ngram("paraparaparadise", 2).Set;
my Set \Y = char-ngram("paragraph", 2).Set;
say X ∪ Y;
say X ∩ Y;
say X ∖ Y;
say Y ∖ X;
my sub char-ngram(Str $text, Int $n) {
my @chars = $text.comb;
gather for ^@chars X $n -> ($start, $len) {
next if $start + $len > @chars;
take @chars[$start..^($start + $len)]
}
}
解説
NOTE: sub char-ngram
は05. n-gram
で用いたものと同一です
- "paraparaparadise" のbi-gramのリストを生成し、
.Set
でSet型に変換し\X
に代入します - "paragraph" のbi-gramのリストを生成し、
.Set
でSet型に変換し\Y
に代入します -
X ∪ Y
でX
とY
の和集合を求め、say
で出力します -
X ∩ Y
でX
とY
の積集合を求め、say
で出力します -
X ∖ Y
で 集合X - Y
を求め、say
で出力します -
Y ∖ X
で 集合Y - X
を求め、say
で出力します
補足
-
\X
や\Y
はsigilless variable と呼ばれる変数です。今回は数式っぽく見せるためにちょっと遊び心で用いてみましたが、一般的にはSeq型の値を代入するときによく用いられます(※)。 - (※) https://github.com/perl6/roast/blob/d1baf2e7a3e56cd6619c46040d04ed6daebc1d02/S03-operators/context-forcers.t#L239-L261
- sigilless variableについてより詳しく知りたい方はこちらを参照してください: https://docs.perl6.org/language/variables#Sigilless_variables
- そのほかの集合演算子については次のページを参照ください: https://docs.perl6.org/language/setbagmix.html
07. テンプレートによる文生成
コード
07.p6
from-template(12,"気温",22.4).say;
my sub from-template(Int $x, Str $y, Rat $z --> Str) {
($x, "時の", $y, "は", $z).join
}
解説
sub from-template(Int $x, Str $y, Rat $z --> Str)
NOTE: func ($piyo --> Str)
は func ($piyo) returns Str
と書くこともできます
-
$x
,$y
,$z
の3つの変数を受け取ります -
($x, "時の", $y, "は", $z)
というリストを生成し、.join
で要素を結合し、文字列を返します
08. 暗号文
コード
08.p6
cipher("abc東北123").say;
my sub cipher(Str $text --> Str) {
gather for $text.comb {
if $_ ~~ /<:Ll>/ {
take 219 - $_.ord
} else {
take $_
}
}.join.Str
}
解説
sub cipher(Str $text --> Str)
-
gather for take
イディオムを用いて遅延リストを生成します -
$text.comb
で$text
を文字単位に分割してリストにし、ひとつひとつの文字を見ていきます - 与えられた文字が
<:Ll>
つまり英小文字だった場合は219 - その文字の文字コード
を取得します - そうでない場合はそのまま文字を取得します
09. Typoglycemia
コード
09.p6
"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."\
.words\
.map({ my @chars = .comb;
if @chars.elems <= 4 {
@chars.join
} else {
@chars.head ~ @chars[1..^@chars.end].pick(@chars - 2).join ~ @chars.tail
}
})\
.say;
解説
-
.words
でinvocantの文字列を空白区切りで分割し、リストにします -
.map
でinvocantのリスト中の各単語を操作します - 単語を文字単位で分割し、リストにします
- 文字数が4以下(i.e. 1.のリストの要素数が4以下)ならそのまま結合して文字列にします
- そうでないなら、
@chars
中の[先頭+1,末尾)
の箇所から.pick
を使ってランダムな順番で文字列を選び、join
により結合します。そして、この文字列に対して、@chars.head
を先頭に、@chars.tail
を末尾に結合します。 -
.say
で出力します
補足
- 興味のある方は
pick
とroll
の違いを公式ドキュメントで調べてみましょう
以上、Perl 6でNLP 5本ノック 『第1章: 準備運動』 05 ~ 09 でした。