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

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のクラスはオブジェクトの母、モジュールはベビーシッター

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

ここまで来るともう止まりません。はっきり言って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つのメソッド。さすがにもう気が済みました。わたしの暴走を許してくださりありがとうございます。

(次回に続く)



blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS