CaboChaで処理されたデータをもとに段落をオブジェクトとして扱うためのParagraphクラス
第4回北海道開発オフでは、Rubyで構文解析器CaboChaの簡単なテストを動かしてみました。まともにRubyに取り組んだのは久々でしたが(かつてはクラスも書かずに挫折……!)、あらためてゴリゴリ書いてみて、書きやすいなという印象を受けました。
そんなわけで、いい機会なので、一つの段落を配列の配列として扱えるクラスを書いてみました。設計やら構成やらが中途半端なのは気に留めない方向で!
paragraph.rb
#!/usr/bin/ruby require 'kconv' require 'CaboCha' $KCODE = 'u' class Paragraph < Array begin Cab = CaboCha::Parser.new([$0] + ARGV) rescue Exception retry end def initialize(line) return if line.size > 5000 begin tree = Cab.parse(line.kconv(Kconv::EUC, Kconv::UTF8)) rescue Exception tree = [] return end punct = true cid = 0 revise = 0 linking = {} tree.size.times do |i| begin token = Token.new(tree.token(i)) rescue Exception retry end if token.hasChunk then if punct then self.push([]) linking = {} punct = false end linked = cid - revise linkin = token.chunk.link - revise begin self[-1].push(Chunk.new(linkin)) rescue Exception retry end if linking.keys.include? linked then self[-1][-1].linked = linking[linked] end if linking.keys.include? linkin then linking[linkin].push(linked) else linking[linkin] = [linked] end cid += 1 end if token.pos[/^記号-(読点|空白|括弧開|括弧閉)/u] then next elsif token.pos[/^記号-句点/u] then punct = true self[-1][-1].link = -1 revise = cid elsif token.pos[/^(助(動)?詞|(名詞|動詞)-非自立|動詞-接尾)/u] && self[-1].size < 1 then next else self[-1][-1].push_token(token) end end self[-1][-1].link = -1 end end class Paragraph::Chunk def initialize(link) @head = Head.new @func = Func.new @link = link @linked = [] @to_func = false end def push_token(token) if token.pos[/^(助(動)?詞|(名詞|動詞)-非自立|動詞-接尾)/u] && @head.size > 0 then @to_func = true end if @to_func then @func.push_token(token) else @head.push(token) end end attr_accessor :head, :func, :link, :linked end class Paragraph::Chunk::Head < Array def surface if self.size == 0 '' else self.map{|token| token.surface}.join end end def base if self.size == 0 then '' elsif self.size == 1 then self[0].base else self[0..-2].map{|token| token.surface}.join + self[-1].base end end def pos return nil if self.size < 1 if self[-1].pos[/^名詞-非自立/u] then 'noun-affix' elsif self[-1].pos[/^名詞/u] then 'noun' elsif self[-1].pos[/^動詞/u] then 'verb' elsif self[-1].pos[/^形容詞/u] then 'adjective' elsif self[-1].pos[/^副詞/u] then 'adverb' elsif self[-1].pos[/^連体詞/u] then 'adnominal' elsif self[-1].pos[/^接続詞/u] then 'conjunction' elsif self[-1].pos[/^感動詞/u] then 'exclamation' else nil end end end class Paragraph::Chunk::Func < Array def initialize @flag = true end def push_token(token) if @flag then self.push(Part.new) end if token.pos[/^助詞-係助詞/u] then self.push(Part.new) if self[-1].size > 0 @flag = true elsif token.ctype.size > 0 then @flag = true else @flag = false end self[-1].push(token) end end class Paragraph::Chunk::Func::Part < Array def surface self.map{|e| e.surface}.join end def base last = self.last self[0..-2].map{|e| e.surface}.join + last.base end end class Paragraph::Token def initialize(token) @hasChunk = token.hasChunk @chunk = token.chunk if token.hasChunk @surface = token.surface.kconv(Kconv::UTF8, Kconv::EUC) # 表層型 @base = token.base.kconv(Kconv::UTF8, Kconv::EUC) # 原型 @read = token.read.kconv(Kconv::UTF8, Kconv::EUC) # 読み @pos = token.pos.kconv(Kconv::UTF8, Kconv::EUC) # 品詞 @ctype = token.ctype.kconv(Kconv::UTF8, Kconv::EUC) # 活用型 @cform = token.cform.kconv(Kconv::UTF8, Kconv::EUC) # 活用形 @ne = token.ne # 固有表現 end attr_reader :hasChunk, :chunk, :surface, :base, :read, :pos, :ctype, :cform, :ne end
test_paragraph.rb
#!/usr/bin/ruby require 'paragraph' $KCODE = 'u' loop do print 'INPUT> ' line = gets.chomp if line == '' then puts 'exit.' break end p = Paragraph.new(line) p.each_with_index do |s, index| puts "文<#{index + 1}>:" s.each_with_index do |c, index| puts "#{index}: [#{c.head.surface}(#{c.head.base})] => #{c.link}" if c.head.surface.size > 0 then puts '>>' + c.head.last.pos end c.func.each do |t| puts "#{t.surface}(#{t.base})" end if c.linked.length > 0 then puts c.linked.join(', ') end end end end
出力
$ ruby test_paragraph.rb INPUT> キリンさんが好きです。でも、ゾウさんのほうがもっと好きです。 文<1>: 0: [キリンさん(キリンさん)] => 1 >>名詞-接尾-人名 が(が) 1: [好き(好き)] => -1 >>名詞-形容動詞語幹 です(です) 0 文<2>: 0: [でも(でも)] => 4 >>接続詞 1: [ゾウさん(ゾウさん)] => 2 >>名詞-接尾-人名 の(の) 2: [ほう(ほう)] => 4 >>名詞-非自立-一般 が(が) 1 3: [もっと(もっと)] => 4 >>副詞-一般 4: [好き(好き)] => -1 >>名詞-形容動詞語幹 です(です) 0, 2, 3