MeCabのラッパーを実装

MecabのインストールMecabのrubyバインディングのインストールを行ったので、Mecabrubyバインディングのラッパークラスを作ってみました。

目的

商品名や説明分を形態素に分解してキーワードのインデックスを作り、そのキーワードインデックスからの検索を行いたので。

仕様

以下のような要求を満たせるよう実装を試みました。

      1. 集合(Set)のかたちで結果を返せるようにする。
      2. 助詞や助動詞は結果から省きたい。
      3. 文中の前後をつなげたものも結果に含みたい(東京都は東京,京都,京,東,都などの要素に分解されるが、東京と都をつなげて東京都も結果に含みたい)
      4. 文字数1の動詞や感嘆詞などは省きたい。

実装

で、実装してきます。

# Mecabのラッパー。
# 形態素解析を行う。
#
require "set"
require 'MeCab'
class MorphsParser


最初に品詞コードの集合を定義していきます。

  # MeCabの品詞コードの範囲
  NOUN_CODES = 37..67
  VERB_CODES = 31..33
  ADJECTIVE_CODES = 10..12
  EXCLAMATION_CODES = 2..2
  SIGN_CODES = 4..9
  #SAHEN_CODE = 36 
  
  # 結果としてい返したい品詞コード(名詞,動詞,形容詞,記号)の集合
  @@include_codes = Set.new +
    NOUN_CODES + 
    VERB_CODES + 
    ADJECTIVE_CODES + 
#    EXCLAMATION_CODES + 
    SIGN_CODES
  
  # 動詞の品詞コードの集合
  @@verb_codes = Set.new + VERB_CODES
  # 感嘆詞の品詞コードの集合
  @@exclamation_codes = Set.new + VERB_CODES
  #@@include_codes.add(SAHEN_CODE)


コンストラクタでは、各オプションの定義を行います。

  # initialize
  # argsには以下のoptionの指定が可能。
  # :only_some_pos 助詞、接続詞、助動詞などを除外する場合true  
  # :con_morphs_num 続いて現れる形態素をつなげたものも結果に含む場合その数 >=1 (1なら結合なし)
  # :min_char_of_verb 結果に含む動詞の最小文字数(default 1)
  # :min_char_of_exlamation 結果に含む感嘆詞の最小文字数(default 1)
  def initialize(args)
    @only_some_pos = 
      if args.has_key? :only_some_pos then args[:only_some_pos] else false end
    @con_morphs_num = 
      if args.has_key? :con_morphs_num then args[:con_morphs_num] else 1 end
    @min_char_of_verb = 
      if args.has_key? :min_char_of_verb then args[:min_char_of_verb] else 1 end
    @min_char_of_exlamation = 
      if args.has_key? :min_char_of_exlamation then args[:min_char_of_exlamation] else 1 end
  end

解析の本体部分は以下のように。

  #
  # 解析された形態素の集合(Set)を返す。
  #
  def to_set(value)
    morphs = Set.new;
    c = MeCab::Tagger.new(ARGV.join(" "))
    n = c.parseToNode(value)
    len = n.sentence_length;
    for i in 0..len
    	b = n.begin_node_list(i)
      while b do
        # printf "B[%d] %s\t%s %s \n", i, b.surface, b.posid, b.feature;
        unless _must_add(b) then
          b = b.bnext 
          next
        end
        morphs.add(b.surface)
        if @con_morphs_num > 1 
          _connect_morphs(b, morphs)
        end   
        b = b.bnext 
      end
    	e = n.end_node_list(i)	 
      while e do
        #printf "E[%d] %s\t%d %s\n", i, e.surface, e.posid, e.feature;
        unless _must_add(e) then
          e = e.bnext 
          next
        end
        morphs.add(e.surface)
        if @con_morphs_num > 1 
          _connect_morphs(e, morphs)
        end   
         e = e.bnext 
       end  
    end
    morphs
  end
  
  private
  
  # 
  # 形態素ノードを結果に含むべきかの判定
  # 全ての品詞を返さないよう指定(@only_some_pos)している場合は、
  #     結果として返すべき品詞コード(@@include_codes)に含まれてなければfalseを返す。
  # また、品詞がOKでも文字数が指定(@min_char_of_verb, @min_char_of_exclamation)
  #     より少ない場合はfalseを返す。
  #
  def _must_add(node)
    if @only_some_pos  and !@@include_codes.include?  node.posid  then
        return false
    end
    if (@@verb_codes.include? node.posid and @min_char_of_verb > node.surface.split(//).size)  then
      return false
    end
    if @@exclamation_codes.include? node.posid  and @min_char_of_exclamation > node.surface.split(//).size then 
      return false
    end 
    return true
  end
  
  # 前後の形態素をつなげる
  # * +node+ - 形態素のノード
  # * +morphs+ - 形態素の集合(Set)
  def _connect_morphs(node, morphs)
    s = node.surface
    Range.new(2,@con_morphs_num).each do |i|
      node = node.next
      if node.nil?
        return morphs
      end
      s << node.surface
      morphs.add(s)
    end
    morphs
  end
  
end

テスト

簡単なテストコードをしてみます。

p = MorphsParser.new(:only_some_pos => true, :con_morphs_num => 3, :min_char_of_verb => 5, :min_char_of_exclamation => 5)
p p.to_set("大阪府から東京都へは新幹線で移動します。")


結果は以下、いまいちかな..

#<Set: {"しま", "府から東京", "府", "東京都へ", "大", "線", "ら", "新", "都へは", "京", "都へ", "新幹線で移動", "京都", "東京", "都", "東京都", "から", "動", "新幹線で", "へ", "東", "府から", "大阪", "。", "す", "ます", "移", "幹", "幹線", "新幹線", "阪", "大阪府", "大阪府から"}>