言語処理100本ノックの解答と感想-中編

advent calendar 17日目です
おくれてしまった。。。

## これは
[言語処理100本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100/)を解いたので解答と感想を1問ずつ書いていくもの(中編)
前回よりもめちゃくちゃ時間かかった〜〜〜


## 前提条件
環境などは[こちら](https://qiita.com/sh05_sh05/items/234e734cfceaa928bdba)(前回のリンク)


## 本編
### 第5章: 係り受け解析
>夏目漱石の小説『吾輩は猫である』の文章（neko.txt）をCaboChaを使って係り受け解析し，その結果をneko.txt.cabochaというファイルに保存せよ．このファイルを用いて，以下の問に対応するプログラムを実装せよ．

```python
import CaboCha
c = CaboCha.Parser()
with open('./neko.txt') as f:
    text = f.read()
with open('./neko.txt.cabocha', mode='w') as f:
    for se in  [s + '。' for s in text.split('。')]:
        f.write(c.parse(se ).toString(CaboCha.FORMAT_LATTICE))
```
実は初・係り受け解析なのでこれだけでもたくさん調べた

#### 40 係り受け解析結果の読み込み（形態素）
>形態素を表すクラスMorphを実装せよ．このクラスは表層形（surface），基本形（base），品詞（pos），品詞細分類1（pos1）をメンバ変数に持つこととする．さらに，CaboChaの解析結果（neko.txt.cabocha）を読み込み，各文をMorphオブジェクトのリストとして表現し，3文目の形態素列を表示せよ．

```python
# 40
class Morph:
    def __init__(self, surface, base, pos, pos1):
        self.surface = surface
        self.base = base
        self.pos = pos
        self.pos1 = pos1

doc = []
with open('./neko.txt.cabocha') as f:
    sentence = []
    line = f.readline()
    while(line):
        while('EOS' not in line):
            if not line.startswith('*'):
                cols = line.split('\t')
                m = Morph(
                    surface=cols[0],
                    base=cols[1].split(',')[-3],
                    pos=cols[1].split(',')[0],
                    pos1=cols[1].split(',')[1],
                )
                sentence.append(m)
            line = f.readline()
        doc.append(sentence)
        sentence = []
        line = f.readline()
print([t.surface for t in doc[2]])
```
CaboChaの出力フォーマットを調べながらやる

#### 41 係り受け解析結果の読み込み（文節・係り受け）
>40に加えて，文節を表すクラスChunkを実装せよ．このクラスは形態素（Morphオブジェクト）のリスト（morphs），係り先文節インデックス番号（dst），係り元文節インデックス番号のリスト（srcs）をメンバ変数に持つこととする．さらに，入力テキストのCaboChaの解析結果を読み込み，１文をChunkオブジェクトのリストとして表現し，8文目の文節の文字列と係り先を表示せよ．第5章の残りの問題では，ここで作ったプログラムを活用せよ．

```python
# 41
class Chunk:
    def __init__(self, morphs, dst, srcs):
        self.morphs = morphs
        self.dst = dst
        self.srcs = srcs
        
doc = []
with open('./neko.txt.cabocha') as f:
    sentence = []
    line = f.readline()
    while(line):
        if line.startswith('*'):
            cols = line.split(' ')
            # 前回のEOSの1行上のものが入らないように
            if cols[1] != '0':
                sentence.append(c)
            c = Chunk(
                morphs=[],
                dst=int(cols[2].split('D')[0]),
                srcs=[]
            )
        elif 'EOS' in line:
            sentence.append(c)
            # 自分にかかるものを探す処理
            for i, c in enumerate(sentence):
                c.srcs = [idx for idx, chk, in enumerate(sentence) if chk.dst == i ]
                
            doc.append(sentence)
            sentence = []
        else:
            cols = line.split('\t')
            if cols[1].split(',')[0] != "記号":
                m = Morph(
                    surface=cols[0],
                    base=cols[1].split(',')[-3],
                    pos=cols[1].split(',')[0],
                    pos1=cols[1].split(',')[1],
                )
                c.morphs.append(m)
        line = f.readline()
for c in doc[7]:
    print(c.dst, end=', ')
    for m in c.morphs:
        print(m.surface, end="")
    print()
for c in doc[0]:
    print(c.dst, end=', ')
    for m in c.morphs:
        print(m.surface)
        print(m.pos)
    print()
```

自分に係っている文節を探す方法を考えていたがいいのが思いつかなかったので、単純にループして走査した

#### 42 係り元と係り先の文節の表示
>係り元の文節と係り先の文節のテキストをタブ区切り形式ですべて抽出せよ．ただし，句読点などの記号は出力しないようにせよ．

```python
# 42
# 全部はjupyter(chrome)が固まるので50
for i, d in enumerate(doc[:50]):
    for c in d:
        if int(c.dst) == -1:
            continue
        for m in c.morphs:
            if m.pos == '記号':
                continue
            print(m.surface, end="")
        print('\t', end="")
        for m in d[c.dst].morphs:
            if m.pos == '記号':
                continue
            print(m.surface, end="")
        print()
```

文と文節と形態素を意識しながらやる

#### 43 名詞を含む文節が動詞を含む文節に係るものを抽出
>名詞を含む文節が，動詞を含む文節に係るとき，これらをタブ区切り形式で抽出せよ．ただし，句読点などの記号は出力しないようにせよ．

```python
# 43
# 全部はjupyter(chrome)が固まるので50
for i, d in enumerate(doc[:50]):
    for c in d:
        if int(c.dst) == -1:
            continue
        contain_noun = '名詞' in [m.pos for m in c.morphs]
        contain_verb = '動詞' in [m.pos for m in d[c.dst].morphs]
        if contain_noun and contain_verb:
            for m in c.morphs:
                if m.pos == '記号':
                    continue
                print(m.surface, end="")
            print('\t', end="")
            for m in d[int(c.dst)].morphs:
                if m.pos == '記号':
                    continue
                print(m.surface, end="")
            print()
```

単純に`if`で調べまくった

#### 44 係り受け木の可視化
>与えられた文の係り受け木を有向グラフとして可視化せよ．可視化には，係り受け木をDOT言語に変換し，Graphvizを用いるとよい．また，Pythonから有向グラフを直接的に可視化するには，pydotを使うとよい．

```python
# 44
import random, pathlib
from graphviz import Digraph

f = pathlib.Path('nekocabocha.png')
fmt = f.suffix.lstrip('.')
fname = f.stem
target_doc = random.choice(doc)
target_doc = doc[8]
idx = doc.index(target_doc)

dot = Digraph(format=fmt)
dot.attr("node", shape="circle")

N = len(target_doc)
# ノードの追加
for i in range(N):
    dot.node(str(i), ''.join([m.surface for m in target_doc[i].morphs]))

# 辺の追加
for i in range(N):
    if target_doc[i].dst >= 0:
        dot.edge(str(i), str(target_doc[i].dst))

# dot.engine = "circo"
dot.filename = filename
dot.render()

print(''.join([m.surface for c in target_doc for m in c.morphs]))
print(dot)
from IPython.display import Image, display_png
display_png(Image(str(f)))

```
可視化も楽しそうだと思いつつあまりやっていなかったので初めてやった、DOT言語を大まかに知るにはWikipediaでも十分だった


#### 45 動詞の格パターンの抽出
>今回用いている文章をコーパスと見なし，日本語の述語が取りうる格を調査したい． 動詞を述語，動詞に係っている文節の助詞を格と考え，述語と格をタブ区切り形式で出力せよ． ただし，出力は以下の仕様を満たすようにせよ．
動詞を含む文節において，最左の動詞の基本形を述語とする
述語に係る助詞を格とする
述語に係る助詞（文節）が複数あるときは，すべての助詞をスペース区切りで辞書順に並べる
「吾輩はここで始めて人間というものを見た」という例文（neko.txt.cabochaの8文目）を考える． この文は「始める」と「見る」の２つの動詞を含み，「始める」に係る文節は「ここで」，「見る」に係る文節は「吾輩は」と「ものを」と解析された場合は，次のような出力になるはずである．
`始める  で`
`見る    は を`
このプログラムの出力をファイルに保存し，以下の事項をUNIXコマンドを用いて確認せよ．
コーパス中で頻出する述語と格パターンの組み合わせ
「する」「見る」「与える」という動詞の格パターン（コーパス中で出現頻度の高い順に並べよ）

```python
# 45
with open("neko_verb.txt", mode="w") as f:
    for s in doc:
        for c in s:
            if '動詞' in [m.pos for m in c.morphs]:
                row = c.morphs[0].base
                j_list = []
                for i in c.srcs:
                    if len(s[i].morphs) < 2:
                        continue
                    srclast = s[i].morphs[-1]
                    if srclast.pos == '助詞':
                        j_list.append(srclast.surface)
                if len(j_list) > 0:
                    j_list.sort()
                    row += "\t" +  " ".join(j_list)
                    f.write(row + "\n")
```

```sh
$ cat neko_verb.txt | sort  | uniq -c  | sort -rn -k 3
$ cat neko_verb.txt | grep "^する" | sort  | uniq -c  | sort -rn -k 3
$ cat neko_verb.txt | grep "見る" | sort  | uniq -c  | sort -rn -k 3
$ cat neko_verb.txt | grep "与える" | sort  | uniq -c  | sort -rn -k 3
```
ここまでの「品詞」「係受け」を組み合わせる

#### 46 動詞の格フレーム情報の抽出
>45のプログラムを改変し，述語と格パターンに続けて項（述語に係っている文節そのもの）をタブ区切り形式で出力せよ．45の仕様に加えて，以下の仕様を満たすようにせよ．
項は述語に係っている文節の単語列とする（末尾の助詞を取り除く必要はない）
述語に係る文節が複数あるときは，助詞と同一の基準・順序でスペース区切りで並べる
「吾輩はここで始めて人間というものを見た」という例文（neko.txt.cabochaの8文目）を考える． この文は「始める」と「見る」の２つの動詞を含み，「始める」に係る文節は「ここで」，「見る」に係る文節は「吾輩は」と「ものを」と解析された場合は，次のような出力になるはずである．
`始める  で      ここで`
`見る    は を   吾輩は ものを`

```python
# 46
# 出力例とは異なるが問題の仕様はこちらの方が満たしている
for s in doc:
    for c in s:
        if '動詞' in [m.pos for m in c.morphs]:
            row = c.morphs[0].base
            j_list = []
            c_list = []
            for i in c.srcs:
                if len(s[i].morphs) < 2:
                    continue
                srclast = s[i].morphs[-1]
                if srclast.pos == '助詞':
                    j_list.append(srclast.surface)
                    c_list.append(''.join([m.surface for m in s[i].morphs]))
            if len(j_list) > 0:
                j_list.sort()
                c_list.sort()
                row += "\t" +  " ".join(j_list) + "\t"+  " ".join(c_list)
                print(row)
```
>述語に係る文節が複数あるときは，助詞と同一の基準・順序でスペース区切りで並べる

とのことなので出力例とは異なるが文節も独立してソートした

#### 47 機能動詞構文のマイニング
>動詞のヲ格にサ変接続名詞が入っている場合のみに着目したい．46のプログラムを以下の仕様を満たすように改変せよ．
「サ変接続名詞+を（助詞）」で構成される文節が動詞に係る場合のみを対象とする
述語は「サ変接続名詞+を+動詞の基本形」とし，文節中に複数の動詞があるときは，最左の動詞を用いる
述語に係る助詞（文節）が複数あるときは，すべての助詞をスペース区切りで辞書順に並べる
述語に係る文節が複数ある場合は，すべての項をスペース区切りで並べる（助詞の並び順と揃えよ）
例えば「別段くるにも及ばんさと、主人は手紙に返事をする。」という文から，以下の出力が得られるはずである．
`返事をする      と に は        及ばんさと 手紙に 主人は`
このプログラムの出力をファイルに保存し，以下の事項をUNIXコマンドを用いて確認せよ．
コーパス中で頻出する述語（サ変接続名詞+を+動詞）
コーパス中で頻出する述語と助詞パターン

```python
# 47
with open("neko_func_verb.txt", mode="w") as f:
    for s in doc:
        for c in s:
            if '動詞' in [m.pos for m in c.morphs]:
                verb = c.morphs[0].base
                for i in c.srcs:
                    v_head = s[i].morphs[-2:]
                    if len(v_head) < 2:
                        continue
                    if v_head[0].pos1 == "サ変接続" and v_head[1].surface == "を":
                        verb = ''.join([m.surface for m in v_head]) + verb
                        joshi_dic = {}

                        for j in c.srcs:
                            if len(s[j].morphs) < 2:
                                continue
                            srclast = s[j].morphs[-1]
                            if srclast.pos == '助詞' and srclast.surface != "を":
                                joshi_dic[srclast.surface] =  ''.join([m.surface for m in s[j].morphs])

                        if len(joshi_dic.keys()) > 0:
                            joshi_list = list(joshi_dic.keys())
                            joshi_list.sort()
                            row = verb + "\t" +  " ".join(joshi_list) + "\t" + " ".join([joshi_dic[joshi] for joshi in joshi_list])
                            f.write(row + "\n")
```

```sh
$ cat neko_func_verb.txt | sed "s/\t/ /g"| cut -f 1 -d " " | sort | uniq -c  | sort -rn -k 3
$ cat neko_func_verb.txt | sed "s/\t/+/g"| cut -f 1,2 -d "+" | sed "s/+/\t/g" | sort | uniq -c  | sort -rn -k 3
```
>述語に係る文節が複数ある場合は，すべての項をスペース区切りで並べる（助詞の並び順と揃えよ）

こちらは揃えろだったので辞書型にして対応づけた
何度問題を読んでも理解できなくてこの辺から困り始める

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

```python
# 48
for s in doc:
    for c in s:
        if "名詞" in [m.pos for m in c.morphs]:
            row = "".join([m.surface for m in c.morphs])
            chunk_to = c.dst
            if chunk_to == -1:
                continue
            while(chunk_to != -1):
                row += " -> " + "".join([m.surface for m in s[chunk_to].morphs])
                chunk_to = s[chunk_to].dst
            print(row)
```

こちらは直前の問題と比べてスムーズに解けた

#### 49 名詞間の係り受けパスの抽出
>文中のすべての名詞句のペアを結ぶ最短係り受けパスを抽出せよ．ただし，名詞句ペアの文節番号がiとj（i<j）のとき，係り受けパスは以下の仕様を満たすものとする．
問題48と同様に，パスは開始文節から終了文節に至るまでの各文節の表現（表層形の形態素列）を"->"で連結して表現する
文節iとjに含まれる名詞句はそれぞれ，XとYに置換する
また，係り受けパスの形状は，以下の2通りが考えられる．
文節iから構文木の根に至る経路上に文節jが存在する場合: 文節iから文節jのパスを表示
上記以外で，文節iと文節jから構文木の根に至る経路上で共通の文節kで交わる場合: 文節iから文節kに至る直前のパスと文節jから文節kに至る直前までのパス，文節kの内容を"|"で連結して表示
例えば，「吾輩はここで始めて人間というものを見た。」という文（neko.txt.cabochaの8文目）から，次のような出力が得られるはずである．
`Xは | Yで -> 始めて -> 人間という -> ものを | 見た`
`Xは | Yという -> ものを | 見た`
`Xは | Yを | 見た`
`Xで -> 始めて -> Y`
`Xで -> 始めて -> 人間という -> Y`
`Xという -> Y`

```python
# 49
for s in doc:
    # i < j のため最後尾は不要
    for i, c in enumerate(s[:-1]):
        if "名詞" in [m.pos for m in c.morphs] and c.morphs[-1].pos == "助詞":
            # j を探す
            for c_rest in s[i+1:]:
                if "名詞" in [m.pos for m in c_rest.morphs] and c_rest.morphs[-1].pos == "助詞":
                    i_clause =  "".join([m.surface if m.pos != "名詞" else "X" for m in c.morphs])
                    j_clause =  "".join([m.surface if m.pos != "名詞" else "Y" for m in c_rest.morphs])
                    
                    row = i_clause
                    chunk_to = c.dst
                    # パス上にjが存在するか確認のためにパスを求める
                    kkr_path = [chunk_to]
                    while(kkr_path[-1] != -1):
                        kkr_path.append(s[chunk_to].dst)
                        chunk_to = s[chunk_to].dst
                    
                    if s.index(c_rest) in kkr_path:
                        chunk_to = c.dst
                        while(chunk_to != s.index(c_rest)):
                            row += " -> " + "".join([m.surface for m in s[chunk_to].morphs])
                            chunk_to = s[chunk_to].dst
                        row += " -> " + j_clause
                    else:
                        row += " | " + j_clause
                        chunk_to = c_rest.dst
                        while(s[chunk_to].dst != -1):
                            row += " -> " + "".join([m.surface for m in s[chunk_to].morphs])
                            chunk_to = s[chunk_to].dst
                        row += " | " + "".join([m.surface for m in s[chunk_to].morphs])
                        
                    print(row)

```
>文節iとjに含まれる名詞句はそれぞれ，XとYに置換する

こちらも仕様と出力例が少し異なっていたが、自分は「Xが」のような名詞の置換にした


### 第6章: 英語テキストの処理
>英語のテキスト（nlp.txt）に対して，以下の処理を実行せよ．

```sh
$ wget http://www.cl.ecei.tohoku.ac.jp/nlp100/data/nlp.txt
```


#### 50 文区切り
>(. or ; or : or ? or !) → 空白文字 → 英大文字というパターンを文の区切りと見なし，入力された文書を1行1文の形式で出力せよ．

```python
# 50
import re
sentence_sep = re.compile(r'(\.|;|:|\?|!) ([A-Z])')

with open("./nlp.txt") as f:
    txt = f.read()
txt = re.sub(sentence_sep, r'\1\n\2', txt)
print(txt)
```
なるほどと思いながら解いた

#### 51 単語の切り出し
>空白を単語の区切りとみなし，50の出力を入力として受け取り，1行1単語の形式で出力せよ．ただし，文の終端では空行を出力せよ．

```python
# 51
def space2return(txt):
    sentence_sep = re.compile(r'(\.|;|:|\?|!)\n([A-Z])')
    txt = re.sub(sentence_sep, r'\1\n\n\2', txt)
    return re.sub(r' ', r'\n', txt)

txt = space2return(txt)
print(txt)
```
50の出力を受けるが、文の改行で少し予期せぬ動作をするので場当たり的に対応

#### 52 ステミング
>51の出力を入力として受け取り，Porterのステミングアルゴリズムを適用し，単語と語幹をタブ区切り形式で出力せよ． Pythonでは，Porterのステミングアルゴリズムの実装としてstemmingモジュールを利用するとよい．

```python
# 52
from nltk.stem import PorterStemmer
ps = PorterStemmer()

def stem_text(txt):
    for l in txt.split('\n'):
        yield l + '\t' + ps .stem(l)
    
for line in stem_text(txt):
    print(line)
```
[提示されたリンク先](https://pypi.org/project/stemming/)になかったので代用した
普段は避けているyieldをつかってみたが、返り値がイテレータになることがわかった

#### 53 Tokenization
>Stanford Core NLPを用い，入力テキストの解析結果をXML形式で得よ．また，このXMLファイルを読み込み，入力テキストを1行1単語の形式で出力せよ．

```sh
$ wget http://nlp.stanford.edu/software/stanford-corenlp-full-2018-10-05.zip
$ unzip stanford-corenlp-full-2018-10-05.zip
$ java -cp "./stanford-corenlp-full-2018-10-05/*" edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,parse,lemma,ner,coref -file ./nlp.txt
```
こいつ、というよりXMLのパースに苦しんだ
java実行時の`-annotators`はリファレンスを見て必要に応じて修正する

```python
# 53
import xml.etree.ElementTree as ET

tree = ET.parse("./nlp.txt.xml")
root = tree.getroot()
for token in root.iter("token"):
    print(token.find("word").text)
```
こういうのって全体を出力してタグを調べるやり方であってるんだろうか

#### 54 品詞タグ付け
>Stanford Core NLPの解析結果XMLを読み込み，単語，レンマ，品詞をタブ区切り形式で出力せよ．

```python
# 54
for token in root.iter("token"):
    print(token.find("word").text + "\t" + token.find("lemma").text + "\t" + token.find("POS").text)
```
とても見にくい

#### 55 固有表現抽出
>入力文中の人名をすべて抜き出せ．

```python
# 55
for token in root.iter("token"):
    NERtag = token.find("NER").text
    if NERtag == "PERSON":
        print(token.find("word").text)
```
実装かと思ったらタグづけされていた

#### 56 共参照解析
>Stanford Core NLPの共参照解析の結果に基づき，文中の参照表現（mention）を代表参照表現（representative mention）に置換せよ．ただし，置換するときは，「代表参照表現（参照表現）」のように，元の参照表現が分かるように配慮せよ．

```python
# 56
rep_dic_list = []

# 辞書作り
for coreferences in root.findall("document/coreference"):
    for mentions in coreferences:
        for m in mentions:
            if "representative" in m.attrib:
                rep_txt = m.find("text").text
            else:
                tmp_dic = {}
                tmp_dic["sentence"] = m.find("sentence").text
                tmp_dic["start"] = m.find("start").text
                tmp_dic["end"] = m.find("end").text
                tmp_dic["rep_txt"] = rep_txt
                rep_dic_list.append(tmp_dic)
                
# 出力
for s in root.iter("sentence"):
    rep_sent_list = [rd for rd in rep_dic_list if rd["sentence"] == s.attrib["id"]]
    # 置換が必要な文かどうか
    if len(rep_sent_list) == 0:
            print(" ".join([token.find("word").text for token in s.iter("token")]), end=" ")
    else:
        for token in s.iter("token"):
            tid = token.attrib["id"]
            rep_token_list = [rd for rd in rep_sent_list if rd["start"] == tid or rd["end"] == tid]
            
            if len(rep_token_list) > 0:
                # 該当は１つなので取り出す
                rep_dic = rep_token_list[0]
                
                #　装飾
                if tid == rep_dic["start"]:
                    print("「" + rep_dic["rep_txt"] + " (", end=" ")
                if tid == rep_dic["end"]:
                    print(")」", end=" ")
                    
            print(token.find("word").text, end=" ")
```

問題文を理解できずにここでだいぶ放置してしまった。
辞書を作って、置換というよりは修飾をする

#### 57 係り受け解析
>Stanford Core NLPの係り受け解析の結果（collapsed-dependencies）を有向グラフとして可視化せよ．可視化には，係り受け木をDOT言語に変換し，Graphvizを用いるとよい．また，Pythonから有向グラフを直接的に可視化するには，pydotを使うとよい．

```python
# 57
import random, pathlib
from graphviz import Digraph

f = pathlib.Path('nlp.png')
fmt = f.suffix.lstrip('.')
fname = f.stem

dot = Digraph(format=fmt)
dot.attr("node", shape="circle")

sent_id = 3

for sents in root.findall(f"document/sentences/sentence[@id='{sent_id}']"):
    for deps in sents:
        for dep in deps.findall("[@type='collapsed-dependencies']"):
            for token in dep:
                gvnr = token.find("governor")
                dpnt = token.find("dependent")
                dot.node(gvnr.attrib["idx"], gvnr.text)
                dot.node(dpnt.attrib["idx"], dpnt.text)
                dot.edge(gvnr.attrib["idx"], dpnt.attrib["idx"])


dot.filename = fname
dot.render()

# print(dot)
from IPython.display import Image, display_png
display_png(Image(str(f)))
```
初めは`governor`と`dependent`を素通りしていたので全くわからなかった

#### 58 タプルの抽出
>Stanford Core NLPの係り受け解析の結果（collapsed-dependencies）に基づき，「主語 述語 目的語」の組をタブ区切り形式で出力せよ．ただし，主語，述語，目的語の定義は以下を参考にせよ．
`述語: nsubj関係とdobj関係の子（dependant）を持つ単語`
`主語: 述語からnsubj関係にある子（dependent）`
`目的語: 述語からdobj関係にある子（dependent）`

```python
# 58
for sents in root.findall(f"document/sentences/sentence"):
    for deps in sents:
        for dep in deps.findall("[@type='collapsed-dependencies']"):
            nsubj_list = []
            for token in dep.findall("./dep[@type='nsubj']"):
                gvnr = token.find("governor")
                dpnt = token.find("dependent")
                nsubj_list.append( {
                    (gvnr.attrib["idx"], gvnr.text): (dpnt.attrib["idx"], dpnt.text)
                })
            for token in dep.findall("./dep[@type='dobj']"):
                gvnr = token.find("governor")
                dpnt = token.find("dependent")
                dobj_tuple = (gvnr.attrib["idx"], gvnr.text)
                
                if dobj_tuple in [list(nsubj.keys())[0] for nsubj in nsubj_list]:
                    idx =  [list(nsubj.keys())[0] for nsubj in nsubj_list].index( dobj_tuple )
                    jutugo = gvnr.text
                    shugo = nsubj_list[idx][dobj_tuple][1]
                    mokutekigo = dpnt.text
                    print(shugo + "\t" + jutugo + "\t" + mokutekigo)

```
一度辞書を作ってから条件に合うものを探す方針

#### 59 S式の解析
>Stanford Core NLPの句構造解析の結果（S式）を読み込み，文中のすべての名詞句（NP）を表示せよ．入れ子になっている名詞句もすべて表示すること．

```python
# 59 
import xml.etree.ElementTree as ET
import re

def search_nest(t):
    if isinstance(t[0], str):
        if isinstance(t[1], str):
            if t[0] == "NP":
                print(t[1])
            return t[1]
        else:
            if t[0] == "NP":
                np_list = []
                for i in t[1:]:
                    res = search_nest(i)
                    if isinstance(res, str):
                        np_list.append(search_nest(i))
                if len(np_list) > 0:
                    print(' '.join(np_list))
            else:
                for i in t[1:]:
                    search_nest(i)
    else:
        for i in t:
            search_nest(i)


tree = ET.parse("./nlp.txt.xml")
root = tree.getroot()
sent_id = 30

for parse in root.findall(f"document/sentences/sentence[@id='{sent_id}']/parse"):
    S_str = parse.text
    S_str = S_str.replace("(", "('")
    S_str = S_str.replace(")", "')")
    S_str = S_str.replace(" ", "', '")
    S_str = S_str.replace("'(", "(")
    S_str = S_str.replace(")'", ")")
    exec(f"S_tuple = {S_str[:-2]}")
    search_nest(S_tuple)
    

```
`()`の入れ子の認識ができずに人生最大の汚い実装をした、ゴリゴリにハードコーディングしてタプル型にして再帰的に取り出す。
`regex`を試しても自分で解決できなかったので解答を優先した。

## おわり
どちらの章も初めてのライブラリであったことと言語処理の用語が急に増えたので1~4章の倍くらい時間がかかった
しかし残りはDB, ML, ベクトルなのでいけるっしょという油断をしている