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

10行程度のスクリプトで目的を達成できました
前置きばかりが長かったこの連載も
これで終えられます


でも...
わたしはどうも気に入りません
先のコードは分かりにくいというか
Rubyっぽくないというか...


もう一度スクリプトを見てみます

 dic = Hash.new(0)
 while line = ARGF.gets
   line.downcase!
   while line.sub!(/[a-z]+/, "")
     word = $&
     dic[word] += 1
   end
 end
 
 p dic.sort { |a, b| b[1] <=> a[1] }[0...30]


不満点を言えば...


!を忘れただけで無限ループに陥るのがヤです
「こら」と「こら!」で確かに雰囲気は変わりますが
怒っていることに変わりはありません


「$&」記号が意味不明です
しかも中途半端です
「$&♀」なら納得しますが...*1


subの第2引数も何かを忘れちゃったようでヤです
できれば省略したい


なによりもオブジェクト指向してません

Version02

そうです
気に入らないなら改良しましょう
リファクタリングです


単語を切り出す処理を
dicを作る処理と切り分けましょう

 WORDS = ARGF.read.downcase.scan(/[a-z]+/)
 dic = Hash.new(0)
 for word in WORDS
   dic[word] += 1
 end
 p dic.sort { |a, b| b[1] <=> a[1] }[0...30]


一行目を見てください
Rubyオブジェクト指向です」のところで説明した
メソッドチェーンです
ここではARGFに対しreadメソッドで一気にファイルを読み出し
まとめて小文字化した文字列オブジェクトを得ています
そしてscanメソッドを使ってそこから単語を切り出しています
scanメソッドはマッチした単語の配列を返します
これをWORDSで参照できるようにします


次にfor文でWORDSから単語を一つずつ取り出し辞書を作ります


1行目がオブジェクト指向的なコードになり
機能的にも(1)単語の切り出し(2)辞書dicの作成(3)ソート
の各処理が分離して全体がすっきりしました
大分好きなかたちになりました

Version03

でもこうなると
(2)がオブジェクト指向的でなく
制御構造中心になっているところが
気になる人は気になります


リファクタリングしましょう

 WORDS = ARGF.read.downcase.scan(/[a-z]+/)
 DICTIONARY = WORDS.inject(Hash.new(0)) { |dic, word| dic[word] += 1 ; dic }
 p DICTIONARY.sort { |a, b| b[1] <=> a[1] }[0...30]


配列のinjectメソッドは
畳み込みという処理をする便利なメソッドです
injectは引数とブロックを取って引数で渡されたオブジェクトに
配列の各要素をブロック内の条件で投入していきます


次のコードは配列要素を順次引数10に加算した結果を返します

 p [1, 2, 3].inject(10) { |mem, var| mem + var } 
 # >> 16


上のスクリプトでは引数に初期値0のハッシュを与えて
ブロック内で辞書を作ります
なおinjectメソッドからの返り値を
ハッシュオブジェクトとするために
ブロックの返り値をdicとする必要があります


スクリプトが3行になりました
極めてワードエコなコードです
Rubyのパワーを垣間見ます
これなら上司も喜びます


オブジェクト指向の良いところは
文章を読むように左から右にコードを読めるところです
ファイルを読んで小文字にして単語を取り出す
単語からその出現数の辞書を作る
辞書をソートして先頭の30件を取り出す


さあ目的は達成できました
スクリプトRubyっぽくなりました
気分がいいです


(次回に続く)

*1:ええ、男には無くてはならないものです