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

さてGUIで出力も魅力的なものになりました
もうやり残すことはなさそうです
当初単なる制御構造であったスクリプト
WordDictionaryという立派なクラスになりました
これで将来このクラスを拡張したり
このクラスをベースにしたアプリケーションを作れます



将来っていつ?
...


そうです
DogYearのこの時代に
いつ来るか分からない将来を待っている
ゆとりなんて私たちにはないのです!
自分たちから未来に向かって進むのです!
何らかの形ある結果を残すのです!!
...


ということで...
なんか息巻いている人がいるので
もう少し進みましょう

Version24

まずはもう少し機能拡張しましょう
WordDictionaryクラスから生成した
複数の単語辞書オブジェクトを
相互作用させるような機能があったら楽しそうです


まずは2つのオブジェクトを結合する
+メソッドを定義しましょう

 class WordDictionary
   attr_reader :words

   def +(other)
     result = (@words + other.words).join(" ")
     WordDictionary.new(result)
   end
   protected :words
 end


+メソッドは他の単語辞書オブジェクトを引数に取って
自身と引数の単語を結合した単語辞書オブジェクトを返します
つまりAオブジェクトにrubyという単語が32個あり
Bオブジェクトに18個あった場合
これらを結合したCオブジェクトには
rubyは50個あることになります


attr_reader :wordsは
+メソッドの実装において他のオブジェクトの
@wordsインスタンス変数にアクセスできるようにします
protected :wordsにおいてその可視性を
同系のオブジェクトからのアクセスに限定します


+メソッドのresultには2つの単語オブジェクトの単語を
スペースで結合してなる文字列が入り
その文字列でWordDictionaryクラスの
オブジェクトを作って返り値とします
@wordsは配列を指していますから
実装においては配列の+メソッドが使えます


これによって通常の算術演算のような記法で
2つの単語辞書オブジェクトを結合できるようになりました

 alice = WordDictionary.new('public/alice.txt')
 romeo_juliet = WordDictionary.new('public/romeo_juliet.txt')
 p alice.top_by_length(10) { |val| val > 10 }
 p romeo_juliet.top_by_length(10) { |val| val > 10 }
 p (alice + romeo_juliet).top_by_length(10) { |val| val > 10 }

 #> [["caterpillar", 28, 11], ["everything", 14, 10], ["adventures", 12, 10], ["foundation", 25, 10], ["electronic", 27, 10], ["paragraph", 11, 9], ["anxiously", 14, 9], ["beautiful", 13, 9], ["agreement", 18, 9], ["trademark", 11, 9]]
 [["distributed", 18, 11], ["shakespeare", 17, 11], ["electronic", 19, 10], ["therefore", 23, 9], ["gentleman", 14, 9], ["gutenberg", 24, 9], ["gentlemen", 11, 9], ["copyright", 16, 9], ["benvolio", 17, 8], ["daughter", 17, 8]]
Argument has assumed as a text string
 [["distribution", 16, 12], ["shakespeare", 18, 11], ["caterpillar", 28, 11], ["opportunity", 11, 11], ["information", 13, 11], ["distributed", 22, 11], ["everything", 15, 10], ["adventures", 12, 10], ["foundation", 25, 10], ["permission", 16, 10]]

Version25

じゃあ次に-メソッドを定義しましょう
ついでに&メソッドと|メソッドも定義しましょう
-メソッドは2つのオブジェクトの差分を
&メソッドと|メソッドはそれぞれそれらの積と和を出力します
なお先のrubyの例を-メソッドに適用した場合
Cオブジェクトのrubyの個数は14でなく0になります

 class WordDictionary
   attr_reader :words

   def +(other)
     result = (@words + other.words).join(" ")
     WordDictionary.new(result)
   end
   protected :words

   def -(other)
     result = (@words - other.words).join(" ")
     WordDictionary.new(result)
   end
 
   def &(other)
     result = (@words & other.words).join(" ")
     WordDictionary.new(result)
   end
 
   def |(other)
     result = (@words | other.words).join(" ")
     WordDictionary.new(result)
   end
 end


さて次にやるべきことは分かってますね?
そう
Don't Repeat Yourself!です

Version26

上の4つの演算はその中の演算子が異なるだけです
先の例のようにこれをブロックやオブジェクト化して渡す
方法がありますがここではもっとスマートに
シンボルを使って渡してみましょう

 class WordDictionary
   attr_reader :words

   def +(other)
     arithmetics(:+, other)
   end
 
   def -(other)
     arithmetics(:-, other)
   end
 
   def &(other)
     arithmetics(:&, other)
   end
 
   def |(other)
     arithmetics(:|, other)
   end
 
   private
   def arithmetics(op, other)
     result = (@words.send op, other.words).join(" ")
     WordDictionary.new(result)
   end
 end


arithmeticsメソッド内で
sendメソッドを使っている点がポイントです
sendメソッドは
シンボルで表現されたメソッドを実行できるようにします
これでコードがすっきりしました


(次回に続く)