言語処理100本ノック2020 (20~29)

# はじめに
本記事は言語処理100本ノックの解説です。
100本のノックを全てこなした記録をQiitaに残します。
使用言語はPythonです。

https://nlp100.github.io/2020/ja/

今回は第3章: 正規表現(20~29)までの解答例をご紹介します。
>第3章: 正規表現
>Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある．
>
>・1行に1記事の情報がJSON形式で格納される
・各行には記事名が”title”キーに，記事本文が”text”キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
・ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ．

私の観測している限り、3章で100本ノックを辞める人が多い印象です。
本記事が3章を突破するヒントになれば幸いです。

# 20. JSONデータの読み込み
>Wikipedia記事のJSONファイルを読み込み，「イギリス」に関する記事本文を表示せよ．問題21-29では，ここで抽出した記事本文に対して実行せよ．
```python:コード
import json
import re
text_list = []
basepath = "[PATH]"
with open('{}/jawiki-country.json'.format(basepath), 'r') as f:
    lines = f.readlines()
    for line in lines:
        text_list.append(json.loads(line))

for i in range(len(text_list)):
    if text_list[i]['title']=="イギリス":
        UK_text = str(text_list[i])
        break
#問題21-25でUK_textを使います
UK_text
```

```text:出力結果
{\'title\': \'イギリス\', \'text\': \'{{redirect|UK}}\\n{{redirect|英国|春秋時代の諸侯国|英 (春秋)}}\\n{{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}\\n{{基礎情報 国\\n|略名  =イギリス\\n|日本語国名 = グレートブリテン及び北アイルランド連合王国\\n|公式国名 = {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />\\n*{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu Thuath}}（[[スコットランド・ゲール語]]）\\n*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}（[[ウェールズ語]]）\\n*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann}}（[[アイルランド語]]）\\n*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Glédh}}（[[コーンウォール語]]）\\n*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}（[[スコットランド語]]）\\n**{{lang|sco|Claught Kängrick o Docht Brätain an Norlin Airlann}}、{{lang|sco|Unitet Kängdom o Great Brittain an Norlin Airlann}}（アルスター・スコットランド語）</ref>\\n|国旗画像 = Flag of the United Kingdom.svg\\n|国章画像 = [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]\\n|国章リンク =（[[イギリスの国章|国章　~~~省略
```
**コメント**
問題21-25で本問の変数「UK_text」を使います。

# 21. カテゴリ名を含む行を抽出
>記事中でカテゴリ名を宣言している行を抽出せよ．


```python:コード
import re
pattern = "\[\[Category:.*?\]\]"
result = re.findall(pattern, UK_text)
result
```
```text:出力結果
['[[Category:イギリス|*]]',
 '[[Category:イギリス連邦加盟国]]',
 '[[Category:英連邦王国|*]]',
 '[[Category:G8加盟国]]',
 '[[Category:欧州連合加盟国|元]]',
 '[[Category:海洋国家]]',
 '[[Category:現存する君主国]]',
 '[[Category:島国]]',
 '[[Category:1801年に成立した国家・領域]]']
```

**コメント**
正規表現はたった1行を書くために長時間考えますよね。

# 22. カテゴリ名の抽出
>記事のカテゴリ名を（行単位ではなく名前で）抽出せよ．


```python:コード
import re
pattern = "\[\[Category:(.*?)(?:\|.*?|)\]\]"
result = re.findall(pattern, UK_text)
result
```
```text:出力結果
['イギリス',
 'イギリス連邦加盟国',
 '英連邦王国',
 'G8加盟国',
 '欧州連合加盟国',
 '海洋国家',
 '現存する君主国',
 '島国',
 '1801年に成立した国家・領域']
```

**コメント**
正規表現を秒で書ける人の頭の中が見たい。

# 23. セクション構造
>記事中に含まれるセクション名とそのレベル（例えば”== セクション名 ==”なら1）を表示せよ．


```python:コード
import collections
import re
pattern = "(={2,4}.*?={2,4})"
result = re.findall(pattern, UK_text)
section = {}
for text in result:
    c1 = collections.Counter(text)
    c2 = int(c1['=']/2) - 1
    text = text.replace('=', '')
    section[text] = c2
section
```
```text:出力結果
{'国名': 1,
 '歴史': 1,
 '地理': 1,
 '主要都市': 2,
 '気候': 2,
 '政治': 1,
 '元首': 2,
 '法': 2,
 '内政': 2,
 '地方行政区分': 2,
 '外交・軍事': 2,
~~~~~~~~~~~~省略~~~~~~~~~~~~
```
**コメント**
だいぶ前に書いたコードなんですけど、「int(c1['=']/2) - 1」の部分がかっこいい処理だなと自画自賛(すみません)。

# 24. ファイル参照の抽出
>記事から参照されているメディアファイルをすべて抜き出せ．


```python:コード
import re
pattern = '\[\[ファイル:(.*?)(?:\||\])'
result = re.findall(pattern, UK_text)
result
```
```text:出力結果
['Royal Coat of Arms of the United Kingdom.svg',
 'United States Navy Band - God Save the Queen.ogg',
 'Descriptio Prime Tabulae Europae.jpg',
 "Lenepveu, Jeanne d\\'Arc au siège d\\'Orléans.jpg",
 'London.bankofengland.arp.jpg',
~~~~~~~~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
```
**コメント**
wikipediaの画像は誰があげているんですかね。「a.jpeg」とかにしていないのは好感。
# 25. テンプレートの抽出
>記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し，辞書オブジェクトとして格納せよ．


```python:コード
import re
pattern = '基礎情報(.*?\<references/\>)'
result = re.findall(pattern, UK_text)
result[0] += "\\n"
pattern = '(?<=\\\\n\|)(.*?) *= *(.*?)(?=\\\\n)'
result2 = re.findall(pattern, result[0])
inf_dic = {}
for i, j in result2:
  inf_dic[i] = j
inf_dic
```
```text:出力結果
{'略名': 'イギリス',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国章画像': '[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]',
~~~~~~~~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
```

**コメント**
正規表現あるあるだと思うけど、答えがあっているか確認が大変。例外的なパターンがあって取りこぼしているかもしれないです。
「inf_dic」は次問で使います。

# 26. 強調マークアップの除去
>25の処理時に，テンプレートの値からMediaWikiの強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: マークアップ早見表）．


```python:コード
import re
inf_dic2 = {}
for key, text in inf_dic.items():
  inf_dic2[key] = re.sub(r'(\\\'){2,5}' , '', text)
inf_dic2
```
```text:出力結果(抜粋)
#処理前
'国歌': "[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}<br />\\'\\'神よ女王を護り賜え\\'\\'<br />{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}",

#処理後
'国歌': '[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}<br />神よ女王を護り賜え<br />{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}',
```

**コメント**
強調マークアップが少なかったので変わっているところだけ掲載。「inf_dic2」は次問で使います。

# 27. 内部リンクの除去
>26の処理に加えて，テンプレートの値からMediaWikiの内部リンクマークアップを除去し，テキストに変換せよ（参考: マークアップ早見表）．


```python:コード
import re
inf_dic3 = {}
for key, text in inf_dic2.items():
  pattern = "(?<=\}\}\<br \/\>（)\[{2}"#<br />（フランス語]]:[[Dieu et mon droit|神と我が権利]]）
  text = re.sub(pattern, '', text)

  pattern = "\[{2}.*?\|.*?px\|(?=.*?\]\])"#'[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]',
  text = re.sub(pattern, '', text)

  pattern = "(?<=(\|))\[{2}"
  text = re.sub(pattern, '', text)

  pattern = "(?<=\}{2}（)\[{2}"#スコットランド・ゲール語]]）\n*{{lang|cy
  text = re.sub(pattern, '', text)

  pattern = "(?<=\>（)\[{2}.*?\|"#[[グレートブリテン及びアイルランド連合王国]]成立<br />（1800年合同法]]）
  text = re.sub(pattern, '', text)

  pattern = "(?<=（.{4}).*?\[{2}.*?\)\|" #'[[イングランド王国]]／[[スコットランド王国]]<br />（両国とも[[合同法 (1707年)|1707年合同法]]まで）',
  text = re.sub(pattern, '', text)

  pattern = "\[{2}.*?\|"#[[(除去)|]]の処理
  text = re.sub(pattern, '', text)

  pattern = "(\[{2}|\]{2})"#最後に残ったやつを処理
  inf_dic3[key] = re.sub(pattern, '', text)
inf_dic3
```
```text:出力結果
{'略名': 'イギリス',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br />',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国章画像': 'イギリスの国章',
 '国章リンク': '（国章）',
 '標語': '{{lang|fr|Dieu et mon droit}}<br />（フランス語:神と我が権利）',
 '国歌': '{{lang|en|God Save the Queen}}{{en icon}}<br />神よ女王を護り賜え<br />{{center|ファイル:United States Navy Band - God Save the Queen.ogg}}',
~~~~~~~~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
```
**コメント**
本問は例外処理を1つ1つ処理できる正規表現を作成。取りこぼしや処理の間違いはあるかもしれないです。他の人はどう解くか気になる問題。
「inf_dic3」は次問で使います。


# 28. MediaWikiマークアップの除去
>27の処理に加えて，テンプレートの値からMediaWikiマークアップを可能な限り除去し，国の基本情報を整形せよ．

```python:コード
inf_dic4 = {}
for key, text in inf_dic3.items():
  pattern = '\{\{.*?\{\{center\|' #{{center|ファイル:United States Navy Band - God Save the Queen.ogg}}',
  text = re.sub(pattern, '', text)

  pattern = '\{\{.*?\|.*?\|.{2}\|'
  text = re.sub(pattern, '', text) #'{{仮リンク|リンゼイ・ホイル|en|Lindsay Hoyle}}'

  pattern = '\<ref.*?\>.*?\<\/ref\>'#<ref>で囲んでいるもの
  text = re.sub(pattern, '', text)

  pattern = '\<ref.*?\>|\<br \/\>'#<ref>単体+<br />単体
  text = re.sub(pattern, '', text)

  pattern = '\{\{lang\|.*?\|'#公式国名、標語の外国語タイトルを消すため
  text = re.sub(pattern, '', text)

  pattern = '\{\{.*?\}\}'#確立年月日2,3,4の{を除去する
  text = re.sub(pattern, '', text)

  pattern = '\}\}'# 公式国名、標語、国家、他元首等氏名2の}を除去する
  text = re.sub(pattern, '', text)

  inf_dic4[key] = text
inf_dic4
```
```text:出力結果
{'略名': 'イギリス',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '公式国名': 'United Kingdom of Great Britain and Northern Ireland英語以外での正式国名:',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国章画像': 'イギリスの国章',
 '国章リンク': '（国章）',
 '標語': 'Dieu et mon droit（フランス語:神と我が権利）',
 '国歌': 'ファイル:United States Navy Band - God Save the Queen.ogg',
~~~~~~~~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~~~~~~~
```

**コメント**
泥臭くやりました。正規表現を適用する順番も重要。解答が割れる問題だと思うので、いろいろな人の解答を見てください。
「inf_dic4」は次問で使います。

# 29. 国旗画像のURLを取得する
>テンプレートの内容を利用し，国旗画像のURLを取得せよ．（ヒント: MediaWiki APIのimageinfoを呼び出して，ファイル参照をURLに変換すればよい）


```python:コード
import urllib
url = 'https://www.mediawiki.org/w/api.php?action=query&titles=File:' + urllib.parse.quote(inf_dic4['国旗画像']) + '&format=json&prop=imageinfo&iiprop=url'
connection = urllib.request.urlopen(urllib.request.Request(url))
response = json.loads(connection.read().decode())
print(response['query']['pages']['-1']['imageinfo'][0]['url'])
```
```text:出力結果
https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_the_United_Kingdom_%283-5%29.svg
```
下のリンクです。
https://upload.wikimedia.org/wikipedia/commons/8/83/Flag_of_the_United_Kingdom_%283-5%29.svg

**コメント**
ロジックを考える系の問題では無いです。だから、先駆者の解答と似たり寄ったりになりますね。

# 他章の解答例

https://qiita.com/Dukapan100knock/items/d418f2aa37b2b3d16428