Rubyチュートリアル 〜英文小説の最頻出ワードを見つけよう!(その12)
でも待ってください
そこまで汎用性がある汎用性があるって言うのなら...
クラスにでもしたらどうですか?
それならArrayクラスにも迷惑は掛かりませんし
なるほどいい考えかもしれません
ではテキストファイルを受け取ると
英単語頻度辞書を生成するWordDictionaryクラスを作りましょう
まず現在のスクリプト全体を掲載します
module Enumerable def take_by(nth) sort_by { |elem| yield elem }.take(nth) end end class Hash def top_by_value(nth, &blk) take_by_value(nth, lambda { |v| -v }, &blk) end def bottom_by_value(nth,&blk) take_by_value(nth, lambda { |v| v }, &blk) end private def take_by_value(nth, sort_opt) select { |key, val| block_given? ? yield(val) : val }.take_by(nth) { |key, val| sort_opt[val] } end end class << ARGF def take_words(regexp) read.downcase.scan(regexp) end end class Array def make_freq_dic inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic } end end WORDS = ARGF.take_words(/[a-z]+/) DICTIONARY = WORDS.make_freq_dic p DICTIONARY.top_by_value(30)
Version16
これらのコードにおける各メソッドを
WordDictionaryクラスに実装します
このクラスはARGFオブジェクトを引数に取って
そこからワード辞書オブジェクトを生成します
Enumerableのtake_byはWordDictionary以外でも
使えそうなのでこのまま残します
上のスクリプトは次のように生まれ変わりました
module Enumerable def take_by(nth) sort_by { |elem| yield elem }.take(nth) end end class WordDictionary include Enumerable def initialize(argf) @words = argf.read.downcase.scan(/[a-z]+/) @freq_dic = @words.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic } end def each @freq_dic.each { |elem| yield elem } end def top_by_frequency(nth, &blk) take_by_value(nth, lambda { |v| -v }, &blk) end def bottom_by_frequency(nth, &blk) take_by_value(nth, lambda { |v| v }, &blk) end private def take_by_value(nth, sort_opt) @freq_dic.select { |key, val| block_given? ? yield(val) : val }.take_by(nth) { |key, val| sort_opt[val] } end end wdic = WordDictionary.new(ARGF) p wdic.top_by_frequency(30) p wdic.bottom_by_frequency(30) { |val| val > 100 }
ざっと眺めてみると先のコードが
大体そのまま移管されているのが分かると思います
top_by_valueとbottom_by_valueは
目的を分かりやすくするために名前をそれぞれ
top_by_frequencyとbottom_by_frequencyに変えました
変わったところを列挙してみます
- WordDictionaryクラス
- include Enumerable
- initializeメソッド
- eachメソッド
クラス定義はキーワードclassに続き
大文字で始まるクラス名を指定して行います
スクリプトが実行されたとき
このクラス定義からそのクラス名で参照可能な
クラスオブジェクトが生成されます
include EnumerableによってEnumerableモジュールに追加した
take_byメソッドが使えるようになります
initializeメソッドはWordDictionaryクラスを生成する
newメソッドが呼ばれたときに自動で実行されるメソッドです
通常ここにオブジェクトの初期化処理を書きます
WordDictionaryでは単語を切り出してその結果の配列を
@wordsというインスタンス変数で参照できるようにします
次いで頻出ワード辞書を作り出しその結果のハッシュを
@freq_dicインスタンス変数で参照できるようにしています
eachメソッドにはちょっとしたマジックがあります
Enumerableモジュールには繰り返し処理のための
便利なメソッドが多数存在しますが
eachメソッドをうまく定義すれば
WordDictionaryで生成されるオブジェクトでも
これらの便利なメソッドが使えるようになるのです
例をあとで示します
スクリプトの最後の3行で
このWordDictionaryクラスの使い方が分かると思います
ARGFを引数に取ったnewメソッドを
WordDictionaryクラスに送り
これによって単語辞書オブジェクトを生成します
「newメソッドなんて定義してないのに何故呼べるの?」
と考えた人は鋭いです
理由はこうです
すべてのクラスは何らかのクラスの継承クラスです
明示的に継承元クラスを指定する場合は
class WordDictionary < Hash
のようにします
明示的な指定がない場合Rubyは
自動でObjectクラスをその継承元クラスとして指定します
ですからWordDictionaryクラスは
Objectクラスの被継承者です
そして被継承者は継承元のメソッドすべてを
自由に使えるのです
さて
eachメソッドのマジックを1つ見せます
wdic = WordDictionary.new(ARGF) wdic.group_by { |word, freq| word.length }.select { |len, word| len > 14 }.each { |len, word| print "#{len} => #{word.transpose.first}\n"} #> 15 => ["representations", "merchantibility", "accomplishments", "acknowledgments", "inconsistencies", "conscientiously", "superintendence", "congratulations", "thoughtlessness", "recommendations", "uncompanionable", "disappointments", "condescendingly", "transformations", "transfiguration", "ecclesiasticism", "notwithstanding", "representatives", "appropriateness", "characteristics", "contemporaneous", "unrighteousness", "remorselessness", "comprehensively"] 16 => ["unenforceability", "superciliousness", "incomprehensible", "discontentedness", "inextinguishable", "internationalism"] 17 => ["disinterestedness", "misrepresentation", "communicativeness", "congregationalist", "indestructibility"]
Enumerableモジュールに定義されているgroup_byメソッドを
WordDictionaryクラスのオブジェクトで使った例です
ワード長が15以上のものをグループ別に表示させています
自作のクラスがこれでずっと高級になりました
Enumerableモジュールが持っているメソッドは以下で調べられます
(次回に続く)