読者です 読者をやめる 読者になる 読者になる

謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

形態素解析した結果からまだ何か考察

日記 勉強 scala

前回 の処理に似たようなことを行い、

  • 接頭詞 + 名詞 = 名詞
  • 名詞 + 名詞 = 名詞
  • 名詞 + 動詞 = 動名詞 + 動詞
  • 動詞 + 動詞 = 動詞
  • 動詞 + 助動詞 = 動詞
  • 動詞 + 助詞 = 動詞

で区切ったあと、句読点が「記号」と認識されていることに着目。
記号で区切られた範囲を最少の「文」として区分けする。

すると、

List(Token(名詞,田村憲久厚生労働相,タムラケンヒサコウセイロウドウショウ), Token(助詞,は,ハ), Token(名詞,22日,ニニニチ))
List(Token(名詞,2階,ニカイ), Token(助詞,の,ノ), Token(名詞,飲食店,インショクテン), Token(助詞,を,ヲ), Token(動名詞,予約,ヨヤク), Token(動詞,した,シタ), Token(名詞,作家,サッカ), Token(助詞,の,ノ), Token(名詞,乙武洋匡さん,オツタケヒロタダシサン), Token(助詞,が,ガ))
List(Token(名詞,車いす,クルマイス), Token(助詞,を,ヲ), Token(名詞,理由,リユウ), Token(助詞,に,ニ), Token(動名詞,入店拒否,ニュウミセキョヒ), Token(動詞,された,サレタ), Token(助詞,と,ト), Token(名詞,ネット,ネット), Token(助詞,で,デ), Token(名詞,明らか,アキラカ), Token(助詞,に,
ニ), Token(動詞,し,シ), Token(助詞,て,テ), Token(動詞,いる,イル), Token(名詞,こと,コト), Token(助詞,に対し,ニタイシ))
List(Token(名詞,店員,テンイン), Token(助詞,が,ガ), Token(動名詞,協力,キョウリョク), Token(動詞,し,シ), Token(助詞,て,テ), Token(動詞,連れ,ツレ), Token(助詞,て,テ), Token(動詞,行く,イク), Token(名詞,努力,ドリョク), Token(助詞,を,ヲ), Token(動詞,すべきだ,スベキダ), Token(助詞,
と,ト), Token(動詞,思う,オモウ), Token(助詞,が,ガ))
List(Token(名詞,店,ミセ), Token(助詞,の,ノ), Token(名詞,状況,ジョウキョウ), Token(助詞,や,ヤ), Token(名詞,対応,タイオウ), Token(助詞,が,ガ), Token(動詞,分からず,ワカラズ))
List(Token(名詞,これ以上,コレイジョウ), Token(助詞,の,ノ), Token(名詞,コメント,コメント), Token(助詞,は,ハ), Token(動詞,差し控えたい,サシ
ヒカエタイ))
List(Token(助詞,と,ト), Token(動詞,述べた,ノベタ))

名詞/動名詞を抽出して

List(Token(名詞,田村憲久厚生労働相,タムラケンヒサコウセイロウドウショウ), Token(名詞,22日,ニニニチ))
List(Token(名詞,2階,ニカイ), Token(名詞,飲食店,インショクテン), Token(動名詞,予約,ヨヤク), Token(名詞,作家,サッカ), Token(名詞,乙武洋匡さん,オツタケヒロタダシサン))
List(Token(名詞,車いす,クルマイス), Token(名詞,理由,リユウ), Token(動名詞,入店拒否,ニュウミセキョヒ), Token(名詞,ネット,ネット), Token(名
詞,明らか,アキラカ), Token(名詞,こと,コト))
List(Token(名詞,店員,テンイン), Token(動名詞,協力,キョウリョク), Token(名詞,努力,ドリョク))
List(Token(名詞,店,ミセ), Token(名詞,状況,ジョウキョウ), Token(名詞,対応,タイオウ))
List(Token(名詞,これ以上,コレイジョウ), Token(名詞,コメント,コメント))

うーんここまで来ると、1つの文で一緒に使用された名詞と動名詞の関係も残したくなるな。

で、ここまで来たら

  • 単語をカウント
  • 1文の中で一緒に発言された名詞・動名詞を1セットとして、カウント

してみる。
お題はこれ

話の前に、もう一つ知っておいてもらいたいことがあります。私はかつてシャア・アズナブルという名で呼ばれたこともある男だ。私はこの場を借りて、ジオンの遺志を継ぐものとして語りたい。もちろん、ジオン公国のシャアとしてではなく、ジオン・ダイクンの子としてである。ジオン・ダイクンの遺志は、ザビ家のような欲望に根差したものではない。ジオン・ダイクンがジオン公国を作ったのでは無い。現在ティターンズ地球連邦軍を我が物にしている事実は、ザビ家のやり方より悪質であると気付く。

シャア様ですね

で、コードがこんなんになって

import net.reduls.igo.Tagger
import scala.collection.mutable

object Token {
  def toToken(morpheme:String, original:String) = {
    val elements = morpheme.split(",")
    Token(elements(0), original, elements(6))
  }

  private def compareType(l:Token, r:Token, types:String):Boolean = l.tokenType == r.tokenType && l.tokenType == types

  private def compareType(l:Token, r:Token, lTypes:String, rType:String):Boolean = l.tokenType == lTypes && r.tokenType == rType

  def splitBySymbol(tokens:List[Token]):List[List[Token]] = {
    val result = mutable.MutableList.newBuilder[List[Token]].result()
    var currentTokens = mutable.MutableList.newBuilder[Token].result()
    tokens.foreach { token =>
      if (token.tokenType == "記号") {
        result += currentTokens.toList
        currentTokens = mutable.MutableList.newBuilder[Token].result()
      } else {
        currentTokens += token
      }
    }

    result.filterNot(_.isEmpty).toList
  }

  def combineTokens(tokens:Iterable[Token]):mutable.MutableList[Token]
      = tokens.foldLeft(mutable.MutableList.newBuilder[Token].result()) { case (lists, token) =>
    if (lists.size == 0) { lists += token }
    else {
      (lists.last, token) match {
        case (l, r) if compareType(l, r, "接頭詞", "名詞") => lists.dropRight(1) += Token("名詞", l.currentToken + r.currentToken, l.sound + r.sound)
        case (l, r) if compareType(l, r, "名詞")            => lists.dropRight(1) += Token("名詞", l.currentToken + r.currentToken, l.sound + r.sound)
        case (l, r) if compareType(l, r, "名詞", "動詞")   => lists.dropRight(1) += Token("動名詞", l.currentToken , l.sound) += r
        case (l, r) if compareType(l, r, "動詞", "動詞")   => lists.dropRight(1) += Token("動詞", l.currentToken + r.currentToken, l.sound + r.sound)
        case (l, r) if compareType(l, r, "動詞", "助動詞") => lists.dropRight(1) += Token("動詞", l.currentToken + r.currentToken, l.sound + r.sound)
        case o => lists += token
      }
    }
  }
}

case class Token(tokenType:String, currentToken:String, sound:String)

class SentenceCalc {

  val wordMap   = collection.mutable.Map.empty[Int, String]
  val wordCount = collection.mutable.Map.empty[Int, Int]
  val pairCount = collection.mutable.Map.empty[(Int, Int), Int]

  def calcSentence(sentence:List[Token]) {
    sentence.foreach(t => {
      countWord(t.currentToken.hashCode, t.currentToken)
    })

    makeCombination(sentence.map(_.currentToken)).foreach { case (left, right) =>
      val (leftHash, rightHash) = (left.hashCode -> right.hashCode)
      val key = if (leftHash < rightHash) (leftHash -> rightHash) else (rightHash -> leftHash)
      pairCount.get(key) match {
        case Some(v) => pairCount.+=(key -> (v + 1))
        case None => pairCount.+=(key -> 1)
      }
    }
  }

  def makeCombination(tgt:List[String]):List[(String, String)] = {
    def pairComparator(head:String, tail:List[String]):List[(String, String)] = {
      tail.size match {
        case 1 => List(head -> tail.head)
        case e => tail.map(v => head -> v) ++ pairComparator(tail.head, tail.tail)
      }
    }

    if (tgt.isEmpty || tgt.size == 1) {
      List.empty[(String, String)]
    } else {
      pairComparator(tgt.head, tgt.tail)
    }
  }

  def countWord(hash:Int, s:String) {
    wordMap += (hash -> s)

    wordCount.get(hash) match {
      case Some(i) => wordCount += (hash -> (i + 1))
      case None => wordCount += (hash -> 1)
    }
  }
}

object IgoSample extends App {
  val tagger = new Tagger("ipadic")
  val parsedList = tagger.parse("話の前に、もう一つ知っておいてもらいたいことがあります。" +
    "私はかつてシャア・アズナブルという名で呼ばれたこともある男だ。" +
    "私はこの場を借りて、ジオンの遺志を継ぐものとして語りたい。" +
    "もちろん、ジオン公国のシャアとしてではなく、ジオン・ダイクンの子としてである。" +
    "ジオン・ダイクンの遺志は、ザビ家のような欲望に根差したものではない。" +
    "ジオン・ダイクンがジオン公国を作ったのでは無い。現在ティターンズが地球連邦軍を我が物にしている事実は、ザビ家のやり方より悪質であると気付く。")

  import scala.collection.JavaConversions._
  val filter = List("名詞", "動名詞")
  def tokens = Token.combineTokens(parsedList.map(v => Token.toToken(v.feature, v.surface)).toIterable)
  val sentences = Token.splitBySymbol(tokens.toList)
  val names = sentences map { sentence => sentence.filter(t => filter.contains(t.tokenType)) } filterNot(_.isEmpty)

  val calc = new SentenceCalc
  names.foreach(calc.calcSentence)
  println("Result ========================================")
  println("MessageDetect : ")
  calc.wordCount.foreach(v => println("  %s\t%d".format(calc.wordMap(v._1), v._2)))
  println("Result : ")
  calc.pairCount.foreach(v => {
    val (left,right) = v._1
    println("  %s = %s\t%d".format(calc.wordMap(left), calc.wordMap(right), v._2))
  })
}

でして結果が

Result ========================================
MessageDetect :
  もの  2
  名    1
  話    1
  シャア        1
  男    1
  地球連邦軍    1
  悪質  1
  の    1
  こと  2
  物    1
  よう  1
  遺志  2
  やり方        1
  シャア・アズナブル     1
  事実  1
  ジオン・ダイクン       3
  欲望  1
  ジオン        1
  子    1
  場    1
  私    2
  現在ティターンズ       1
  ザビ家        2
  ジオン公国    2
  前    1
  一つ  1
Result :
  やり方 = ザビ家       1
  事実 = 地球連邦軍     1
  男 = シャア・アズナブル        1
  もの = よう   1
  現在ティターンズ = 物  1
  の = ジオン・ダイクン  1
  現在ティターンズ = 事実        1
  私 = こと     1
  名 = 私       1
  もの = ザビ家 1
  遺志 = ジオン 1
  男 = こと     1
  私 = シャア・アズナブル        1
  物 = 事実     1
  もの = 欲望   1
  前 = 話       1
  場 = 私       1
  ジオン公国 = ジオン・ダイクン  1
  悪質 = やり方 1
  現在ティターンズ = 地球連邦軍  1
  名 = 男       1
  名 = シャア・アズナブル        1
  よう = 欲望   1
  物 = 地球連邦軍       1
  男 = 私       1
  よう = ザビ家 1
  遺志 = ジオン・ダイクン        1
  名 = こと     1
  子 = ジオン・ダイクン  1
  悪質 = ザビ家 1
  こと = 一つ   1
  欲望 = ザビ家 1
  もの = ジオン 1
  もの = 遺志   1
  ジオン公国 = の       1
  こと = シャア・アズナブル      1
  ジオン公国 = シャア   1

パッと見そんなでもないように見えて、組み合わせパターンが意味不明な一部を除いては概ね面白い結果に。