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のクラスはオブジェクトの母、モジュールはベビーシッター
メソッドが見つからないなら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