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


blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS