日本語テキストから、SVO格を指定した検索単語を含む「係受け元単語 -> 係受け先単語」ペアを、全件出力するメソッド

##作ったもの

__以下の3つを「位置引数」で受け取る関数__
（ キーワード引数の方が使いやすいかもしれない ）

1. __解析対象の和文テキスト：*str* 型__
2. __着目するターゲット単語：*str* 型__
3. __ターゲット単語が、どの格（主語（主格）・述語・目的語（目的格））で出現している文脈箇所を探索するのかを番号で指定：*int* 型__

```Python:ターゲット単語の出現位置の格（主語（主格）、述語、目的語（目的格）
case_num_dict = {1 : "主語", 2 : "述語", 3: "目的語"}
```

####（ メソッド仕様：入出力関係 ）

```Python:Python3.9.0
# 受け取ったstr型のテキストデータに複数の文が含まれる場合を、「。」の出現回数で判定して検出。
# 複数の文を、１つの文を要素に持つlistに格納する。その後、リスト内包表記のなかで、文を一つずつ、一つの文を受け取るget_svo_info_に渡す。
# 受け取ったstr型のテキストデータに、１つの文しか含まれない場合は、上記の処理を行わない。
def get_svo_info(text:str, target_word:str, case_num_of_target_word:int) -> List[Tuple[Dict, List]]:
```

__（ 参考にさせて頂いたWebページ ）__

テキストデータから、SVO（SPO)のTriple set（[主語, 動詞（述語）, 目的語]）を返す処理を行う実装コードを探してみたところ、なかなか見当たりませんでした。方々を探してみたところ、見つけた以下のコードがうまく動きました。

・ __[東邦大学 「2014-01-29 CaboChaによる係り受け解析の利用 ～ 文の主節の骨組を取り出す」](https://pepper.is.sci.toho-u.ac.jp/pepper/index.php?%A5%CE%A1%BC%A5%C8%2F%A5%C6%A5%AD%A5%B9%A5%C8%A5%DE%A5%A4%A5%CB%A5%F3%A5%B0%2FCaboCha%A4%C7%BC%E7%C0%E1%A4%CE%B9%FC%C1%C8%A4%F2%BC%E8%A4%EA%BD%D0%A4%B9)__

この記事では、東邦大学の研究室が公開中の上記のコードをもとに、上記の入出力関係を持つ関数（メソッド）に書き換えてみました。

___

###挙動確認

サンプルの和文テキストは、NHK NewsWebの国際面に、2020/12/1に掲載されていた__[記事](https://www3.nhk.or.jp/news/html/20201201/k10012741251000.html?utm_int=news_contents_news-main_001)__から拝借しました。

####（ 複数の文を含む単一のstrオブジェクトを引数に渡した場合 ）

```Python:ipython対話型インタプリタ（Python3.9.0）
In [1]: from get_svo_list_filter_by_target_word_and_case2 import *

In [2]: document = """ロシア軍は、北方領土で地対空ミサイルシステム「S300」の訓練を初めて行ったと発表しました。ロシアとしては北方領土で軍備を強化している姿勢を強調するねらいがあるものとみられます。
   ...: 
   ...: ロシア軍の東部軍管区は1日、地対空ミサイルシステム「S300」の訓練を北方領土を含む島々で初めて行ったと発表しました。
   ...: 
   ...: このミサイルシステムは中国国境に近いロシア極東のユダヤ自治州に配備されていたものを移送したということで、軍のテレビ局は、北方領土の択捉島でミサイルシステムを稼働させる映像を流しました。
   ...: 
   ...: このミサイルシステムは、射程がおよそ400キロ、戦闘機やミサイルなどを撃ち落とす対空防衛を目的としていて、島にある演習場で訓練することが目的だとしています。
   ...: 
   ...: ロシアは、択捉島と国後島には地対艦ミサイルシステムを配備していますが、「S300」の訓練を北方領土で行ったのは初めてで、ロシアとしては、北方領土で軍備を強化している姿勢を強調するねらいがあるも
   ...: のとみられます。
   ...: 
   ...: 日本政府は、ロシア側による北方領土での軍備の強化について「北方領土に関する日本の立場と相いれず受け入れられない」として繰り返し、抗議しています。"""

In [3]: get_svo_info(document, "ロシア", 1)
Out[3]: 
[[({'主語': 'ロシア軍は、'}, ['ロシア軍は、->発表しました'])],
 [],
 [],
 [],
 [],
 [({'主語': 'ロシアは、'}, ['ロシアは、->みられます'])],
 []]

In [4]: get_svo_info(document, "ロシア軍", 1)
Out[4]: [[({'主語': 'ロシア軍は、'}, ['ロシア軍は、->発表しました'])], [], [], [], [], [], []]

In [5]: get_svo_info(document, "映像", 3)
Out[5]: [[], [], [], [({'目的語': '映像を'}, ['映像を->流しました'])], [], [], []]

In [6]: get_svo_info(document, "発表", 2)
Out[6]: 
[[({'述語': '発表しました'}, ['ロシア軍は、->発表しました', '行ったと->発表しました'])],
 [],
 [({'述語': '発表しました'}, ['東部軍管区は->発表しました', '1日、->発表しました', '行ったと->発表しました'])],
 [],
 [],
 [],
 []]

In [7]: get_svo_info(document, "発表", 3)
Out[7]: [[], [], [], [], [], [], []]

In [8]: get_svo_info(document, "発表", 1)
Out[8]: [[], [], [], [], [], [], []]

In [9]: get_svo_info(document, "ロシア", 2)
Out[9]: [[], [], [], [], [], [], []]

In [10]: get_svo_info(document, "ロシア", 3)
Out[10]: [[], [], [], [], [], [], []]

In [12]: result = get_svo_info(document, "ロシア軍", 1)

In [13]: type(result)
Out[13]: list

In [14]: result[0]
Out[14]: [({'主語': 'ロシア軍は、'}, ['ロシア軍は、->発表しました'])]

In [15]: result[1]
Out[15]: []

In [16]: type(result[0])
Out[16]: list

In [17]: type(result[0][0])
Out[17]: tuple

In [18]: type(result[0][0][0])
Out[18]: dict

In [19]: type(result[0][0][1])
Out[19]: list

In [20]: print(result[0][0][0])
{'主語': 'ロシア軍は、'}

In [21]: print(result[0][0][0].items())
dict_items([('主語', 'ロシア軍は、')])

In [22]: print(result[0][0][0].keys())
dict_keys(['主語'])

In [23]: print(result[0][0][0].values())
dict_values(['ロシア軍は、'])

```

####（ １件の文を引数に渡した場合 ）

```Python:ipython対話型インタプリタ（Python3.9.0）
In [24]: one_sentence = "ロシア軍は、北方領土で地対空ミサイルシステム「S300」の訓練を初めて行ったと発表しました。"

In [25]: tmp = get_svo_info(one_sentence, "ロシア", 1)

In [26]: print(tmp)
[[({'主語': 'ロシア軍は、'}, ['ロシア軍は、->発表しました。'])]]

In [27]: tmp = get_svo_info(one_sentence, "ロシア", 2)

In [28]: print(tmp)
[[]]

In [29]: tmp = get_svo_info(one_sentence, "発表", 2)

In [30]: print(tmp)
[[({'述語': '発表しました。'}, ['ロシア軍は、->発表しました。', '行ったと->発表しました。'])]]

In [31]: tmp = get_svo_info(one_sentence, "訓練", 3)

In [32]: print(tmp)
[[]]

In [33]: tmp = get_svo_info(one_sentence, "訓練", 1)

In [34]: print(tmp)
[[]]

In [35]: tmp = get_svo_info(one_sentence, "訓練", 2)

In [36]: print(tmp)
[[]]

In [37]: tmp = get_svo_info(one_sentence, "地対空ミサイルシステム", 1)

In [38]: print(tmp)
[[]]

In [39]: tmp = get_svo_info(one_sentence, "地対空ミサイルシステム", 2)

In [40]: print(tmp)
[[]]

In [41]: tmp = get_svo_info(one_sentence, "地対空ミサイルシステム", 3)

In [42]: print(tmp)
[[]]

In [43]: tmp = get_svo_info(one_sentence, "S300", 1)

In [44]: print(tmp)
[[]]

In [45]: tmp = get_svo_info(one_sentence, "S300", 2)

In [46]: print(tmp)
[[]]

In [47]: tmp = get_svo_info(one_sentence, "S300", 3)

In [48]: print(tmp)
[[]]

In [49]: 
```

##実装コード

```Python:get_svo_list_filter_by_target_word_and_case2.py
import CaboCha
import sys
import codecs
# https://docs.python.org/ja/3.6/library/typing.html
from typing import Dict, Tuple, List

case_num_dict = {1 : "主語", 2 : "述語", 3: "目的語"}

def get_svo_info_(sentence:str, target_word:str, case_num_of_target_word:int) -> Tuple[Dict, List]:
    c = CaboCha.Parser()
    tree = c.parse(sentence)
    size = tree.size()
    myid = 0
    ku_list = []
    ku = ''
    ku_id = 0
    ku_link = 0
    kakari_joshi = 0
    kaku_joshi = 0
    
    for i in range(0, size):
        token = tree.token(i)
        if token.chunk:
            if (ku!=''):
                ku_list.append((ku, ku_id, ku_link, kakari_joshi, kaku_joshi))  #前 の句をリストに追加

            kakari_joshi = 0
            kaku_joshi = 0
            ku = token.normalized_surface
            ku_id = myid
            ku_link = token.chunk.link
            myid=myid+1
        else:
            ku = ku + token.normalized_surface
        
        m = (token.feature).split(',')
        if (m[1] == u'係助詞'):
            kakari_joshi = 1
        if (m[1] == u'格助詞'):
            kaku_joshi = 1
    
    ku_list.append((ku, ku_id, ku_link, kakari_joshi, kaku_joshi))  # 最後にも前の句をリストに追加
    for k in ku_list:
        if (k[2]==-1):  # link==-1?      # 述語である
            jutsugo_id = ku_id  # この時のidを覚えておく
    #述語句
    predicate_word = [k[0] for k in ku_list if (k[1]==jutsugo_id)]
    #for k in ku_list:
    #	if (k[1]==jutsugo_id):  # jutsugo_idと同じidを持つ句を探す
    #		print(k[1], k[0], k[2], k[3], k[4])
    #述語句に係る句
    # jutsugo_idと同じidをリンク先に持つ句を探す
    word_to_predicate_list = [k[0] for k in ku_list if k[2]==jutsugo_id]
    #　述語句に係る句 -> 述語句
    svo_arrow_text_list = [str(word_to_predicate) + "->" + str(predicate_word[0]) for word_to_predicate in word_to_predicate_list]
    #print(svo_arrow_text_list)
    
    desired_svo_arrow_text = [arrow_pair_str for arrow_pair_str in svo_arrow_text_list if target_word in arrow_pair_str]
    
    svo_dict = {}
    for num, k in enumerate(ku_list):
        if (k[2]==jutsugo_id):  # jutsugo_idと同じidをリンク先に持つ句を探す
            if (k[3] == 1):
                subject_word = k[0]
                if target_word in subject_word:
                    svo_dict["主語"] = subject_word
                    #print(subject_word)
            if (k[4] == 1):
                object_word = k[0]
                if target_word in object_word:
                    svo_dict["目的語"] = object_word
                    #print(object_word)
        if (k[1] == jutsugo_id):
                predicate_word = k[0]
                if target_word in predicate_word:
                    svo_dict["述語"] = predicate_word
                #print(predicate_word)
    
    case_type_of_search_word = case_num_dict[case_num_of_target_word]
    tmp_list_of_dict_list = [(svo_dict, desired_svo_arrow_text)]
    output_list_of_dict_list = [(dict_obj, list_obj) for (dict_obj, list_obj) in tmp_list_of_dict_list if list(dict_obj.keys())==[case_type_of_search_word]]
    # 空の要素を外す
    output = [elem for elem in output_list_of_dict_list if len(elem)>0]
    return output
        

# 受け取ったstr型のテキストデータに複数の文が含まれる場合を、「。」の出現回数で判定して検出。
# 複数の文を、１つの文を要素に持つlistに格納する。その後、リスト内包表記のなかで、文を一つずつ、一つの文を受け取るget_svo_info_に渡す。
# 受け取ったstr型のテキストデータに、１つの文しか含まれない場合は、上記の処理を行わない。
def get_svo_info(text:str, target_word:str, case_num_of_target_word:int) -> List[Tuple[Dict, List]]:
    sentence_num = text.count("。")
    if sentence_num > 1:
        sentence_list = text.split("。")
        sentence_list = [sentence for sentence in sentence_list if not(sentence == "")]
        output_list = result_list = [get_svo_info_(sentence, target_word, case_num_of_target_word) for sentence in sentence_list]
    else:
        output = get_svo_info_(text, target_word, case_num_of_target_word)
        output_list = [output]
    
    return output_list
```