■ Ruby: 単語や文字の数え上げ

「語」とか「文字」の頻度を確認してみませんか? 「英文」や「プログラム言語」、それにプログラムで処理した結果などの確認には、とっても役立つ道具です。 思ったような結果になっているか? の確認にも使えますよ。

■ 使い方

引数として確認したい「入力ファイル」を指定します。 これだけです。

$ wordfreq.rb ChangeLog | head -15
2229 c
1243 jp
 879 Matsumoto
 879 Yukihiro
 879 matz
 867 co
 725 netlab
 536 2000
 488 eval
 468 to
 464 rb
 451 should
 423 1999
 406 for
 369 ruby
$ charfreq.rb ChangeLog | head -15
18142 e
15012 t
14810 o
14186 a
13300 r
12443 i
11302 n
10102 s
8841 c
7612 u
7602 l
6079 m
5975 d
5571 b
5421 p

入力ファイルは日本語が含まれていても大丈夫です。

wordfreq.rb にオプション「-c」を加えることで、処理をまとめたスクリプトも用意しました。 そちらも参照してください。 途中経過を確認できるということで、短いスクリプトなので、全部入れておきます。

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

wordfreq.rb 単語を数えます。

#! /usr/local/bin/ruby -Ke
# /home/tetsu/src/ruby/sdk/wordfreq.rb
# Created: March 10,2001 Saturday 02:11:26
# Author: tetsu(WATANABE Tetsuya)
# $Id: wordfreq.rb,v 1.2 2001/03/09 17:30:17 tetsu Exp $
# usage:

require 'nkf'

c = Hash.new(0)

while gets
  NKF.nkf('-eZm', $_).scan(/\w+/) do |w|
    c[w] += 1
  end
end

c.keys.sort do |a, b|
  [c[b], a] <=> [c[a], b]
end.each do |k|
  printf "%4d %s\n", c[k], k
end

charfreq.rb 文字を数えます。

#! /usr/local/bin/ruby -Ke
# /home/tetsu/src/ruby/sdk/charfreq.rb
# Created: March 10,2001 Saturday 02:12:16
# Author: tetsu(WATANABE Tetsuya)
# $Id: charfreq.rb,v 1.2 2001/03/09 17:32:14 tetsu Exp $
# usage:

require 'nkf'

c = Hash.new(0)

while gets
  NKF.nkf('-eZm', $_).scan(/\w/) do |w|
    c[w] += 1
  end
end

c.keys.sort do |a, b|
  [c[b], a] <=> [c[a], b]
end.each do |k|
  printf "%4d %s\n", c[k], k
end

スクリプトとしては、freq.rb としてオプションとして「語」単位か「文字」単位かを分けたほうがいいですね。 正規表現以外はまったく処理が同じですから。 いや、名前から分かりやすい wordfreq.rb にオプション -c でいいかな。 名前は大切ですし? ということで、次のようにしてみました。

#! /usr/local/bin/ruby -Ke
# /home/tetsu/src/ruby/sdk/wordfreq.rb
# Created: March 10,2001 Saturday 02:11:26
# Author: tetsu(WATANABE Tetsuya)
# $Id: wordfreq.rb,v 1.3 2001/03/09 18:26:56 tetsu Exp $
# usage:

def usage
  STDERR.puts "usage: #$0 [-c] ..."
  exit 1
end

opt_char = false
reg_pattern = Regexp.new('\w+')

while /^-/ =~ ARGV[0]
  $_ = ARGV.shift
  if /^-c/
    opt_char = true
  else
    usage
  end
end

require 'nkf'

reg_pattern = Regexp.new('\w') if opt_char

c = Hash.new(0)

while gets
  NKF.nkf('-eZm', $_).scan(reg_pattern) do |w|
    c[w] += 1
  end
end

c.keys.sort do |a, b|
  [c[b], a] <=> [c[a], b]
end.each do |k|
  printf "%4d %s\n", c[k], k
end

■ 解説

この話題は Unix ユーザならよくご存じの?

$ man tar | col -b | tr -sc 'A-Za-z' '\012' | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr |head
     29 archive
     25 the
     23 files
     23 file
     19 f
     17 to
     16 tar
     13 of
     12 extract
     11 t
という単語の数え上げ(語の頻度) wordfreq です。 Unix ではパイプを使えば一行でできてしまう? 内容ですが、Ruby を使っても簡単ですね。 出力形式については、注目しているポイントによって変更してください。 現在は「出現頻度」順になっています。

プログラミング的な応用も可能で、変数名の間違い探しなどに役立ちます。 コンパイラ系や Ruby のように初期化が必要な場合は、変数名の間違いによるバグは入りにくいのです。 でも Awk や Perl のように宣言や初期化が必要のない場合には変数名を一文字間違ったために、みつけにくいバグが入ったりします。 あ、長大なスクリプトを書いた場合の話ですけど。 ま、Perl はオプションの指定でこの辺はクリアできてしまいますけど。 スクリプト自身の確認には、こういうオプションがサポートされていたりするのでいいのですが、出力結果についてはどうでしょう? 出力も予想もしていないものが含まれている可能性があったりします。 こういう確認に使用可能ですね。

wordfreq.rb では日本語の場合は、「単語」がスペースで区切られていたりしないので、ちょっと扱いが悪いです。 ですが、最近は「分かち書き」をしてくれる便利なツールが利用できます。 これを利用すると、次のようなことも可能です。

$ kakasi -w < README.jp  | wordfreq.rb | head
  50 を
  40 の
  20 する
  19 に
  18 は
  15 本
  14 で
  14 プログラム
  12 です
  11 Ruby
目的によってはこれも活用できますよね?


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com