Posted at

メロスを形態素解析せよ

More than 5 years have passed since last update.

問題:https://codeiq.jp/ace/masui_yuichiro/q506

私の答案

#!/usr/bin/ruby

#coding:utf-8

require 'stringio'

# MultiSAX: Ruby Gem to handle multiple SAX libraries
# https://github.com/cielavenir/multisax
module MultiSAX # minimal version
class SAX
@parser=nil
def open(*list)
list=[:libxml,:rexmlstream] if list.size==0
list.each{|e_module|
case e_module
when :libxml
begin
require 'rubygems'
require 'libxml'
rescue LoadError;next end
@parser=e_module;break
when :rexmlstream
require 'rexml/document'
require 'rexml/parsers/streamparser'
require 'rexml/streamlistener'
@parser=e_module;break
end
}
@parser
end
def reset() @parser=nil;@saxmodule=nil end

def parse(source,listener)
open if !@parser
case @parser
when :libxml
listener.instance_eval{
extend LibXML::XML::SaxParser::Callbacks
alias :on_start_element :sax_tag_start
alias :on_end_element :sax_tag_end
alias :on_cdata_block :sax_cdata
alias :on_characters :sax_text
alias :on_comment :sax_comment
}
when :rexmlstream
listener.instance_eval{
extend REXML::StreamListener
alias :tag_start :sax_tag_start
alias :tag_end :sax_tag_end
alias :cdata :sax_cdata
alias :text :sax_text
alias :comment :sax_comment
}
end

if source.is_a?(String)
case @parser
when :libxml then parser=LibXML::XML::SaxParser.string(source);parser.callbacks=listener;parser.parse
when :rexmlstream then REXML::Parsers::StreamParser.new(source,listener).parse
end
else
case @parser
when :libxml then parser=LibXML::XML::SaxParser.io(source);parser.callbacks=listener;parser.parse
when :rexmlstream then REXML::Parsers::StreamParser.new(source,listener).parse
end
end
listener
end

end
Sax=SAX.new

module Callbacks
def sax_tag_start(tag,attrs) end
def sax_tag_end(tag) end
def sax_comment(text) end
def sax_cdata(text) end
def sax_text(text) end
end
end

listener=MultiSAX::Sax.parse($<,Class.new{
include MultiSAX::Callbacks
def initialize
@in_main_text=false
@main_text_div_depth=0
@content=''
@current_tag=[]
end
attr_reader :content

def sax_tag_start(tag,attrs)
@current_tag.push(tag)
h_attrs=Hash[*[attrs]]
@in_main_text=true if tag=='div'&&h_attrs['class']=='main_text' #class="main_text"ならば本文
@main_text_div_depth+=1 if @in_main_text&&tag=='div'
end
def sax_tag_end(tag)
if (t=@current_tag.pop)!=tag then raise "xml is malformed /#{t}" end #タグの入れ子が誤っていたらエラーとする
if @in_main_text&&tag=='div'
@in_main_text=false if (@main_text_div_depth-=1)==0
end
end
def sax_text(txt)
@content+=txt.gsub(/[\s\t ]/,'') if @in_main_text&&@main_text_div_depth==1&&!['rp','rt'].include?(@current_tag.last) #rp,rtの中なら捨てる
end
}.new)

mecab_input=listener.content.encode('EUC-JP')
mecab_output=''

IO.popen('mecab -b 100000','r+b',external_encoding:'EUC-JP'){|io|
io.puts mecab_input
io.close_write
mecab_output=io.read.encode('UTF-8')
}
#puts mecab_output
#exit
io=StringIO.new(mecab_output)
nouns=[]
verbs=[]
io.each{|e|
e.chomp!
break if e=='EOS'
idx=e.index("\t")
word=e[0...idx]
meta=e.chomp[idx+1..-1].split(',')
nouns<<word if meta[0]=='名詞' && meta[1]=='一般'
verbs<<word if meta[0]=='動詞' && meta[1]=='自立'
}
nouns=nouns.sample(2)
puts "
#{nouns[0]}は最近、#{nouns[1]}#{verbs.sample}している。"

前半のMultiSAX部分は、require 'rubygems';require 'multisax'で代用可。

後は普通にテキスト化し、EUC-JPに変換してmecabに投げるだけ。

私はこの問題について正解扱いにならなかったので、不明瞭な点について整理のみしておく。


  • テキスト化の方法

rp、rtタグはフリガナに関わるHTMLタグであるので、今回は取り除いて考えた。

しかし、取り除くべきではなかったらしい。取り除くと例えば動詞の語幹が変わったりする。


  • mecabの実行方法

今回初めてmecabを扱ってわかったことだが、バッファサイズを予め指定しないと正しい解析ができないらしい。

そこで-b 100000を指定して実行している。

ところが、解説記事の出力を見ると、EOSが何度も出現しているし、途中いくつかの文が抜けてもいる。

正しさについては議論の余地がありますが、まあ、問題作るって難しいですよね。