2009年05月30日

Rubyでn-gram的なことをする

昨日は酔っ払いながらn-gramと遊ぶ。

お陰で、とある自殺回数が多い作家様が書いた斜陽という作品は「お母さま」が325回出きたり(お母さま言い過ぎ)、ギロチンが24回出てくる(そんなに使ってたっけ?)ことが分かった。

分かったからどうだと言われると困る。語尾とかの傾向を見て「女性っぽい小説」、「男性っぽい小説」とかを見分けるくらいはできるかもしれないが。

にしても、文章解析の為にはやるべき下準備が多い。他人様の書いたソース読んだり、知らない概念を勉強したり。自分は頭悪いと1日5回くらい思う。その後、俺って天才と2回くらい思う。そんな日々。


$KCODE = 'UTF-8'

require 'kconv'

if ARGV.length < 2
  p "@param inFile"
  p "@param gram = 3 (min 2)"
  p "@param minAppear = 5"
  p "@param outFile = result.txt"
  exit
end

# 入力ファイル名
inFile = ARGV[0]

# 区切り指定
gram = 3
gram = ARGV[1].to_i if ARGV[1]

# 出力最低出現回数(デフォルト5。5回以上登場する言葉を表示)
minAppear = 5
minAppear = ARGV[2].to_i if ARGV[2]

# 出力ファイル
outFile = "result.txt"
outFile = ARGV[3] if ARGV[3]

# 辞書
dic = {}

# ファイル読み込み
file = File::open(inFile, "r") do | file |
  content = file.read.toutf8

  # 「。」、改行、括弧などで分割
  sentences = content.split(/。|「|」|《|》|<|>|<|>|『|』|(|)|\(|\)|\r\n/)
  # さらに「、」で分割
  content = []
  sentences.each do | sentence |
    content.push(sentence.split("、"))
  end

  # 辞書作成
  content.each do | sentence |
    sentence.each do | words |
      # 空白除去
      words.strip!
      words.gsub!(" ", "")
      # 1文字ずつ分割
      words = words.split(//u)
      (0..100000).each do | num |
        # 指定文字ずつ区切る
        word = words[num, gram]
        break if word == nil or word.length != gram
        if dic[word]
          dic[word] += 1
        else
          dic[word] = 1
        end
      end
    end
  end
end

File::open(outFile, "w") do | file |
  dic.sort { | a, b |
    b[1] <=> a[1]
  }.each { | key, value |
    file.puts "#{key} : #{value}" if value >= minAppear
  }
end