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っぽくなりました。気分がいいです。
(次回に続く)
- ええ、男には無くてはならないものです ↩
blog comments powered by Disqus