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:ええ、男には無くてはならないものです