言語処理100本ノック を(飽きるまで)やってみるにあたり、敢えてRubyで書いてみる。
基本的にはPythonを想定しているらしいし、そもそもNLPライブラリの充実度から言ってもPythonを使うのが極めて妥当な選択といえるだろう。そこを敢えてRubyで。
00. 文字列の逆順
puts "stressed".reverse
01. 「パタトクカシーー」
str = "パタトクカシーー" puts 1.step(7, 2).map{|i| str[i]}.join
やはりスライスにstepを指定できるPythonのほうが楽である。
02. 「パトカー」+「タクシー」=「パタトクカシーー」
strs = %w[パトカー タクシー] puts strs.map(&:chars).inject(&:zip).flatten.join
別解
strs = %w[パトカー タクシー] puts strs.map(&:chars).transpose.flatten.join
zip
と transpose
と flatten
を基本装備しているRubyに死角はなかった。
しかし、interleave操作ってのは割と使うのでArrayにあっても良い気はするけど、そう思う人は自分で定義できるのがRubyの良いところである。
ActiveSupportみたいな外部ライブラリを通じて「そう思う人」が一般的であることやライブラリデザインの善し悪しが実証されれば、Ruby本体に取り込まれることもある。ここでも登場している Symbol.to_proc
がその好例である。
ただ、 RubyのStringが「文字の列」でない のが地味に辛い。 StringがEnumerableでないのは、「何を単位としてiterateしたいのかは場合によるからcodepoints, chars, linesなどを明示的に使おう」「昔はlinesがデフォルトだったけど、少なくともこれは違うんでは?」「文字列を文字の列として扱うのが基本であるかのような風潮を助長するのは良くない」などが複合していると思われる。
03. 円周率
sentence = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." p sentence.split(/\W+/).map(&:size)
04. 元素記号
sentence = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can." words = sentence.split(/\W+/) result = {} len = ->(i){ [0, 4, 5, 6, 7, 8, 14, 15, 18].include?(i) ? 1 : 2 } words.each_with_index do |word, i| key = word[0, len[i]] result[key] = i+1 end p result
リスト内包表記がある分Pythonのほうが綺麗にいくだろうか。 あと、出題通りだと"Might"から"Mi"を切り出すことになるけど良いの?
05. n-gram
module Enumerable def ngram(n) each_cons(n).to_a end end if $0 == __FILE__ str = "I am an NLPer" p str.chars.ngram(2) p str.split(/\W+/).ngram(2) end
文字bi-gramのほうは空白は無視した方が良かったんだろうか。
06. 集合
require_relative "05.rb" strs = %w[paraparaparadise paragraph] x = strs[0].chars.ngram(2) y = strs[1].chars.ngram(2) puts "union:\t#{x | y}" puts "intersection:\t#{x & y}" puts "diff:\t#{x - y}" puts "X includes 'se'?: #{x.include?(%w[s e])}" puts "Y includes 'se'?: #{y.include?(%w[s e])}"
Arrayクラスが集合演算を持っているのでそのままである。実用的にはSetを使うべきかもしれない。
07. テンプレートによる文生成
def run_template(x, y, z) "#{x}時の#{y}は#{z}" end puts run_template(12, "気温", 22.4)
やっぱり引数が短い場合は文字列interpolationのほうが String::%
より便利である。
ちなみにRubyの String::%
はPythonが元ネタだったはず。
08. 暗号文
CIPHER_TABLE = (?a .. ?z).map{|c| (219 - c.ord).chr }.join.freeze def cipher(str) str.tr('a-z', CIPHER_TABLE) end if $0 == __FILE__ sentence= <<-EOS He then led me to the frame, about the sides, whereof all his pupils stood in ranks. It was twenty feet square, placed in the middle of the room. The superfices was composed of several bits of wood, about the bigness of a die, but some larger than others. They were all linked together by slender wires. These bits of wood were covered, on every square, with paper pasted on them; and on these papers were written all the words of their language, in their several moods, tenses, and declensions; but without any order. The professor then desired me “to observe; for he was going to set his engine at work.” The pupils, at his command, took each of them hold of an iron handle, whereof there were forty fixed round the edges of the frame; and giving them a sudden turn, the whole disposition of the words was entirely changed. He then commanded six-and-thirty of the lads, to read the several lines softly, as they appeared upon the frame; and where they found three or four words together that might make part of a sentence, they dictated to the four remaining boys, who were scribes. This work was repeated three or four times, and at every turn, the engine was so contrived, that the words shifted into new places, as the square bits of wood moved upside down. EOS puts cipher(sentence) raise unless cipher(cipher(sentence)) == sentence end
09. Typoglycemia
def randomize(str) str.split(/ /).map {|word| if word.size <= 4 word else [word[0], word[1..-2].chars.shuffle, word[-1]].flatten.join end }.join(' ') end if $0 == __FILE__ puts randomize("I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .") end