2018年06月04日

言語処理100本ノックでPython入門 #48 - 名詞から根へのパスの抽出

  

今日は、言語処理100本ノック 2015の問題48です。

■ 問題
48. 名詞から根へのパスの抽出
文中のすべての名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出せよ. ただし,構文木上のパスは以下の仕様を満たすものとする.
  • 各文節は(表層形の)形態素列で表現する 
  • パスの開始文節から終了文節に至るまで,各文節の表現を"->"で連結する 
「吾輩はここで始めて人間というものを見た」という文(neko.txt.cabochaの8文目)から,次のような出力が得られるはずである.
吾輩は -> 見た
ここで -> 始めて -> 人間という -> ものを -> 見た
人間という -> ものを -> 見た
ものを -> 見た

■ Pythonのコード
import re
import functools

class Morph:
    def __init__(self, surface, base, pos, pos1):
        self.surface = surface
        self.base = base
        self.pos = pos
        self.pos1 = pos1

    def toList(self):
        return [self.surface, self.base, self.pos, self.pos1]

class Chunk:
    def __init__(self, number, dst):
        self.number = number
        self.morphs = []
        self.dst = dst
        self.srcs = []

    def print(self):
        print(self.number)
        print([x.toList() for x in self.morphs])
        print(self.dst, self.srcs)
        print()

    def concatMorphs(self):
        seq = filter(lambda x: x.pos != '記号', self.morphs)
        return functools.reduce(lambda x, y: x + y.surface, seq, '')

def analyze():
    article = []
    sentence = []
    chunk = None
    with open('neko.txt.cabocha', 'r', encoding='utf8') as fin:
        for line in fin:
            words = re.split(r'\t|,|\n| ', line)
            if line[0] == '*':
                num = int(words[1])
                destNo = int(words[2].rstrip('D'))
                chunk = Chunk(num, destNo)
                sentence.append(chunk)
            elif words[0] == 'EOS':
                if sentence:
                    for index, c in enumerate(sentence, 0):
                        sentence[c.dst].srcs.append(index)
                    article.append(sentence)
                sentence = []
            else:
                chunk.morphs.append(Morph(
                    words[0],
                    words[7],
                    words[1],
                    words[2],
                ))
    return article


def findNouns(sentence):
    for chunk in sentence:
        for m in chunk.morphs:
            if m.pos == '名詞':
                yield chunk
                break

def makePath(sentence, chunk):
    curr = sentence[chunk.number]
    path = []
    while curr.dst >= 0:
        path.append(curr.concatMorphs())
        curr = sentence[curr.dst]
    path.append(curr.concatMorphs())
    return path

def enumPath(article):
    for sentence in article:
        for chunk in findNouns(sentence):
            path = makePath(sentence, chunk)
            if len(path) >= 2:
                yield path

def main():
    article = analyze()
    with open('result48.txt', 'w', encoding='utf8') as w:
        for path in enumPath(article):
            w.write('{}\n'.format(' -> '.join(path)))

if __name__ == '__main__':
    main()

ソースコードは、GitHubで公開しています。

■ コードの簡単な説明


Morphクラス、Chunkクラス、analyze関数は変更ありません。
問題47は、結構複雑なコードで書いているうちに自分が何をやりたかったのか判らなくなることがありましたが、 この問題48は、問題47に比べて複雑さもそれほどではなかったです。

enumPath関数
analyze関数で得たarticleから、名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出する関数

findNouns関数
enumPathの下請け関数
1つの文から、名詞を含む文節(Chunk)を列挙する。 makePath関数

enumPathの下請け関数
findNounsで得た文節から終了文節に至るまでのパスを作成するする関数

main関数
enumPathで得たパスを-> で連結しファイルに出力する


■ ひとり言

そういえば、初めて while 文を使ったかも。
makePathの中で使っています。while文は他の言語にもあるので、何も難しいことはないですね。

各文節を"->"で連結するのに、joinを使っていますが、いまだに、以下の書き方に慣れないです。
' -> '.join(path)

■ 結果
吾輩は -> 猫である
名前は -> 無い
どこで -> 生れたか -> つかぬ
見当が -> つかぬ
何でも -> 薄暗い -> 所で -> 泣いて -> 記憶している
所で -> 泣いて -> 記憶している
ニャーニャー -> 泣いて -> 記憶している
いた事だけは -> 記憶している
吾輩は -> 見た
ここで -> 始めて -> 人間という -> ものを -> 見た
人間という -> ものを -> 見た
ものを -> 見た
あとで -> 聞くと -> 種族であったそうだ
それは -> 種族であったそうだ
書生という -> 人間中で -> 種族であったそうだ
人間中で -> 種族であったそうだ
一番 -> 獰悪な -> 種族であったそうだ
獰悪な -> 種族であったそうだ
書生というのは -> 話である
我々を -> 捕えて -> 煮て -> 食うという -> 話である
当時は -> なかったから -> 思わなかった
何という -> 考も -> なかったから -> 思わなかった
考も -> なかったから -> 思わなかった
彼の -> 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
スーと -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
時 -> フワフワした -> 感じが -> あったばかりである
感じが -> あったばかりである
掌の -> 上で -> 落ちついて -> 見たのが -> 人間という -> ものの -> 見始であろう