Rubyチュートリアル 〜英文小説の最頻出ワードを見つけよう!(その11)

さてもう改良点はないでしょうか
スクリプト全体をもう一度みてみましょう

 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, opt(false), &blk)
   end
 
   def bottom_by_value(nth,&blk)
     take_by_value(nth, opt, &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
 
 WORDS = ARGF.read.downcase.scan(/[a-z]+/)
 DICTIONARY = WORDS.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic }
 p DICTIONARY.top_by_value(30)


3行目が思いの外すっきりしたので
1行目のメソッドチェーンが気になりだしました
ちょっと病的な感覚かもしれません
でも楽しいRubyの学習のために先に進みます

Version14

「添付ファイルから単語を取って配列に入れる」
という操作は汎用性がありそうです
今度はこれをいじりましょう
ARGFに対するtake_wordsメソッドを定義します


ARGFは通常のオブジェクトと違い
属するクラスを持っていません
ですから上で示したハッシュや配列のように
その属するHashクラスやArrayクラスにメソッドを定義する
といったことができません


ではどうするか


こういう場合はそのオブジェクト専用の
名無しクラスにメソッドを定義します

 class << ARGF
   def take_words(regexp)
     read.downcase.scan(regexp)
   end
 end
 
 WORDS = ARGF.take_words(/[a-z]+/)
 DICTIONARY = WORDS.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic }
 p DICTIONARY.top_by_value(30)


この場合クラスに名前を与えずに
オブジェクトを<<で接ぎ木します
この無名クラスはSingletonクラス
または特異クラスなどと呼ばれます


クラスを定義しない
別の書き方もあります

 def ARGF.take_words(regexp)
   read.downcase.scan(regexp)
 end

こう書いたときSingletonメソッド
または特異メソッドなどと呼ばれます


take_wordsには正規表現を渡せるようにしてます
先頭がx,y,zで始まる単語のみを対象に
最頻出ワード30をリストしてみましょう

 WORDS = ARGF.take_words(/[xyz][a-z]+/)
 DICTIONARY = WORDS.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic }
 p DICTIONARY.top_by_value(30)
 
 #> [["you", 2071], ["zabeth", 636], ["your", 597], ["ys", 556], ["ying", 322], ["years", 226], ["yes", 214], ["ything", 176], ["ydia", 172], ["yet", 163], ["young", 144], ["xt", 143], ["ye", 137], ["year", 124], ["yself", 108], ["zzy", 97], ["yed", 82], ["ybody", 77], ["ylon", 75], ["zed", 67], ["ze", 64], ["yourself", 60], ["xpected", 58], ["yton", 58], ["yphon", 55], ["xactly", 54], ["yond", 54], ["xed", 52], ["yright", 48], ["yone", 45]]


Singletonメソッドについては以下が参考になるかもしれません


Rubyのクラスはオブジェクトの母、モジュールはベビーシッター - hp12c


メソッドが見つからないならRubyに作ってもらえばいいよ!    - If method_missing, define_method by Ruby - - hp12c


ここまで来るともう止まりません
はっきり言って2行目も気になります

 DICTIONARY = WORDS.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic }

Version15

しかも頻出ワード辞書というのは汎用性がありそうです
make_freq_dicメソッドとしてArrayに定義しましょう
ええこれは明らかに行き過ぎです
Arrayに定義されるべきメソッドは
あらゆる種類の配列で使われうるメソッドのみを定義すべきです
でももうわたしにも止められないのです!

 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)


すっきりです
ARGFから単語を取り出しWORDSで参照する
WORDSから頻出ワードを作ってDICTIONARYで参照する
DICTIONARYから頻出トップ30を取って出力する
1つのオブジェクトに1つのメソッド
さすがにもう気が済みました
わたしの暴走を許してくださりありがとうございます


(次回に続く)