言語処理100本ノックに挑戦 / 第3章: 正規表現

# :muscle: 前書き
詳細は[第1章](https://qiita.com/saitotak/items/73852ecb7cc4b10b42a5)参照、引き続き言語処理100本ノックの第3章を回答していきます。本章では 正規表現を中心に json 操作、辞書型、API 呼出を学びました。正規表現を大体理解していたので、サクサク進めることができました。

これはどうやら、JupyterNotebookを使ったほうが効率よく回答できるやつだ、、、とやりながら気づきましたが、引き続きローカルの生 python でやっています。

# 第3章: 正規表現
> Wikipediaの記事を以下のフォーマットで書き出したファイル[jawiki-country.json.gz](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/jawiki-country.json.gz)がある．
> 1行に1記事の情報がJSON形式で格納される
> 各行には記事名が"title"キーに，記事本文が"text"キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
> ファイル全体はgzipで圧縮される
> 以下の処理を行うプログラムを作成せよ．

gzip解凍した後のデータのサンプル、UTF-8/LFでした。日本語のWikiPediaの「国」カテゴリが、改行区切りのJSON形式で連なっているようです(LDJSONと呼ぶそう)。1行が長いですが、"text"に本文、"title"がタイトルのようで、値はWikiPediaの書式でした。

```json:jawiki-country.json
{"text": "{{otheruses|主に現代のエジプト・アラブ共和国|古代|古代エジプト}}\n{{基礎情報 国\n|略名 =エジプト\n|日本語国名 =エジプト・アラブ共和国\n|公式国名 ='''{{lang|ar|جمهورية مصر العربية}}'''\n|国旗画像 =Flag of Egypt.svg\n|国章画像 =[[ファイル:Coat_of_arms_of_Egypt.svg|100px|エジプトの国章]]\n|国章リンク =（[[エジプトの国章|国章]]）\n|標語 =なし\n|位置画像 =Egypt (orthographic projection).svg\n|公用語 =[[アラビア語]]\n|首都 =[[カイロ]]\n|最大都市 =カイロ\n|元首等肩書 =[[近代エジプトの国家元首の一覧|大統領]]\n|元首等氏名 =[[アブドルファッターフ・アッ＝シーシー]]\n|首相等肩書 =[[エジプトの首相|首相]]\n|首相等氏名 =[[イブラヒーム・メフレブ]]\n|面積順位 =29\n|面積大きさ =1 E12\n|面積値 =1,001,450\n|水面積率 =0.6%\n|人口統計年 =2011\n|人口順位 =\n|人口大きさ =1 E7\n|人口値 =81,120,000\n|人口密度値 =76\n|GDP統計年元 =2008\n|GDP値元 =8,965億<ref name=\"economy\">IMF Data and Statistics 2009年4月27日閲覧（[http://www.imf.org/external/pubs/ft/weo/2009/01/weodata/weorept.aspx?pr.x=77&pr.y=19&sy=2008&ey=2008&scsm=1&ssd=1&sort=country&ds=.&br=1&c=469&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a=]）</ref>\n|GDP統計年MER =2008\n|GDP順 
(以下略)
```

## ⚾ 20. JSONデータの読み込み
> Wikipedia記事のJSONファイルを読み込み，「イギリス」に関する記事本文を表示せよ．問題21-29では，ここで抽出した記事本文に対して実行せよ．

### 解1 gzip/json/dict の操作
- gzip 解凍後、197 行目、 `"title": "イギリス"` のデータを引っ張ってくれば良さそうです
- `gzip.open(filename, mode='rb', compresslevel=9, encoding=None, errors=None, newline=None)` で gzip 読込み
    - [13.2. gzip — gzip ファイルのサポート — open](https://docs.python.jp/3/library/gzip.html#gzip.open)
- `json.loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)` で JSON を dict 型に変換
    - [19.2. json — JSON エンコーダおよびデコーダ - loads](https://docs.python.jp/3/library/json.html#json.loads)
- `dict.get(key[, default])` で要素取得
    - [4. 組み込み型 — dict](https://docs.python.jp/3/library/stdtypes.html#dict.get)

```python:answer
import gzip,json

with gzip.open('./data/jawiki-country.json.gz',mode='rt',encoding='utf=8') as gz_temp:
     jsonlines = gz_temp.read()

lst_country = []
for line in jsonlines.split('\n'):
    if len(line) > 1: # 空行がエラーになるため
      lst_country.append(json.loads(line))

for dct_country in lst_country:
    if dct_country.get('title') == 'イギリス':
        print(dct_country.get('text'))
        break

# {{redirect|UK}}
# {{基礎情報 国
# |略名 = イギリス
# |日本語国名 = グレートブリテン及び北アイルランド連合王国
# |公式国名 = {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br/>
# *{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu Thuath}}（[[スコットランド・ゲール語]]）<br/>
# *{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}（[[ウェールズ語]]）<br/>
# （以下略）
```

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

### 解1 正規表現によるマッチング
- データ後半の `[[Category:イギリス|*]]\n[[Category:英連邦王国|*]]...` のあたりを抽出すればよさそうです
- `re.search(pattern, string, flags=0)` で 正規表現によるマッチング
    - [6.2. re — 正規表現操作 — re.search](https://docs.python.jp/3/library/re.html#re.search)
    - [6.2. re — 正規表現操作 — search() vs match()](https://docs.python.jp/3/library/re.html#search-vs-match)

```python:answer
import re
# dct_country は Q20 にて取得済みとする
for text in dct_country.get('text').split('\n'):
    match = re.search('\[\[Category.*',text )
    if match != None:
        print(match.group(0))

# [[Category:イギリス|*]]
# [[Category:英連邦王国|*]]
# [[Category:G8加盟国]]
# [[Category:欧州連合加盟国]]
# [[Category:海洋国家]]
# [[Category:君主国]]
# [[Category:島国|くれいとふりてん]]
# [[Category:1801年に設立された州・地域]]
```

### 解2 re.findall による複数行マッチング
- `re.findall(pattern, string, flags=0)` による複数行マッチング
    - [6.2. re — 正規表現操作 — findall](https://docs.python.jp/3/library/re.html#re.findall)
- リストの内包表記 `[x for x in list]` を使って一行で print してみる
    - [5. データ構造 — リストの内包表記](https://docs.python.jp/3/tutorial/datastructures.html#list-comprehensions)
- `(...)` は マッチ後に回収され、参照可能とする
    - [6.2. re — 正規表現操作 — 正規表現のシンタックス](https://docs.python.jp/3/library/re.html#regular-expression-syntax)

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('(\[\[Category:.*\]\])',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match] # リスト内包表記,devnullはprintの戻り値[Null]格納用
# [[Category:イギリス|*]]
# [[Category:英連邦王国|*]]
# [[Category:G8加盟国]]
# [[Category:欧州連合加盟国]]
# [[Category:海洋国家]]
# [[Category:君主国]]
# [[Category:島国|くれいとふりてん]]
# [[Category:1801年に設立された州・地域]]
```

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

### 解1 非貪欲マッチ(最短マッチ)
- `(?:...)` は マッチ後に取り込まれない
- `*?`, `+?`, `??` は非貪欲マッチ(最短マッチ)
    - [6.2. re — 正規表現操作 — 正規表現のシンタックス](https://docs.python.jp/3/library/re.html#regular-expression-syntax)

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('\[\[Category:(.*?)(?:\|.*)?\]\]',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match] # リスト内包表記,devnullはprintの戻り値[Null]格納用
# イギリス
# 英連邦王国
# G8加盟国
# 欧州連合加盟国
# 海洋国家
# 君主国
# 島国
# 1801年に設立された州・地域
```


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

### 解1 失われる可読性
- リスト内包表記、書けるけど読みにくい…

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('(==+)(.*?)==+',dct_country.get('text'),re.MULTILINE)
devnull = [print(v[1] + ' is Level ' +  str(v[0].count('='))) for v in match]
# 国名 is Level 2
# 歴史 is Level 2
# 地理 is Level 2
# 気候 is Level 3
# 政治 is Level 2
# 外交と軍事 is Level 2
# (以下略)
```

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

### 解1 特に新しい要素はなし
- `[[File:Uk topo en.jpg|thumb|200px|イギリスの地形図]]` 等の要素を抽出すればよさそうです

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('File:(.*?)\|',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match]
# Battle of Waterloo 1815.PNG
# The British Empire.png
# Uk topo en.jpg
# BenNevis2005.jpg
# (以下略)
```

## ⚾ 25. テンプレートの抽出
> 記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し，辞書オブジェクトとして格納せよ．

### 解1 改行を含むマッチング
- データ前半の `{{基礎情報 国\n|略名 = イギリス\n|日本語国名 = グレートブリテン及び...` を見ると、 ` = `でフィールド名と値が設定されているようです
- `re.DOTALL` で改行を含むマッチング
    - [6.2. re — 正規表現操作 — DOTALL](https://docs.python.jp/3/library/re.html#re.DOTALL)
- `dict()` で list型 を dict型 に変換
    - [4. 組み込み型 — dict](https://docs.python.jp/3/library/stdtypes.html#mapping-types-dict)

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
print (match_dct)
# {'略名': 'イギリス', '日本語国名': 'グレートブリテン及び北アイルランド連合王国', '公式国名': '{{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|イギリスの国章]]', '国章リンク': '（
# (以下略)
```


## ⚾ 26. 強調マークアップの除去
> 25の処理時に，テンプレートの値からMediaWikiの強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: [マークアップ早見表](http://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）．

### 解1 特に新しい要素はなし
- マークアップ早見表の記載が変わっていましたが、[過去のバージョン](https://ja.wikipedia.org/w/index.php?title=Help:%E6%97%A9%E8%A6%8B%E8%A1%A8&oldid=56484799) では `''弱い強調''` `'''強調'''` `'''''強い強調'''''` でした
    - 例： `'確立形態4': "現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更"` → `'確立形態4': "現在の国号「グレートブリテン及び北アイルランド連合王国」に変更"`

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_dct2 = {}
for key,value in match_dct.items():
     match_dct2[key] = re.sub('\'\'+','',value)

print (match_dct2)
# (中略)
# '確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更'
# (後略)
```


## ⚾ 27. 内部リンクの除去
> 26の処理に加えて，テンプレートの値からMediaWikiの内部リンクマークアップを除去し，テキストに変換せよ（参考: [マークアップ早見表](http://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）．

### 解1 マッチした文字列を置換に利用する
- `\1` でマッチした文字列へアクセス
    - [6.2. re — 正規表現操作 — 正規表現のシンタックス](https://docs.python.jp/3/library/re.html#regular-expression-syntax)
- `[[記事名]]` `[[記事名|表示文字]]` `[[記事名#節名|表示文字]]` を除去すればよさそうです
    - 例: `'確立形態2': '[[グレートブリテン王国]]建国<br />（[[連合法 (1707年)|1707年連合法]]）'` → `'確立形態2': 'グレートブリテン王国建国<br />（連合法 (1707年)）'`

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_tmp = ''
match_tmp2 = ''
match_dct2 = {}
for key,value in match_dct.items():
    match_tmp = re.sub('\'\'+','',value) # 強調を除去
    match_tmp = re.sub('\[\[(.*?)([|\|].*)?\]\]','\\1',match_tmp) # リンクを除去
    match_dct2[key] = match_tmp

print (match_dct2)
# (中略)
# '確立形態2': 'グレートブリテン王国建国<br />（連合法 (1707年)）',
# (後略)
```
## ⚾ 28. MediaWikiマークアップの除去
> 27の処理に加えて，テンプレートの値からMediaWikiマークアップを可能な限り除去し，国の基本情報を整形せよ．

### 解1 綺麗になるまでがんばる
- 特に基準なく、見栄えが良くなるまで置換する

```python:answer
import re
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_tmp = ''
match_dct2 = {}
for key,value in match_dct.items():
    match_tmp = re.sub('\'\'+','',value) # 強調を除去
    match_tmp = re.sub('\[\[(.*?)([|\|].*)?\]\]','\\1',match_tmp) # リンクを除去
    match_tmp = re.sub('\{\{lang\|.*?\|(.*?)\}\}','\\1',match_tmp) # {{Lang 要素を除去
    match_tmp = re.sub('<ref.*?( />|</ref>)','',match_tmp) # ref 要素を除去
    match_tmp = re.sub('<br(\s|)/>','',match_tmp) # br 要素を除去
    match_dct2[key] = match_tmp

print (match_dct2)
# {'略名': 'イギリス', '日本語国名': 'グレートブリテン及び北アイルランド連合王国', '公式国名': 'United Kingdom of Great Britain and Northern Ireland<ref>英語以外での正式国名:', '国旗画像': 'Flag of the United Kingdom.svg', '国章画像': 'ファイル:Royal Coat of Arms of the United Kingdom.svg', '国章リンク': '（イギリスの国章）', '標語': 'Dieu et mon droit（フランス語:神と私の権利）', '国歌': '女王陛下万歳', '位置画像': 'Location_UK_EU_Europe_001.svg',
# （以下略）
```


## ⚾ 29. 国旗画像のURLを取得する
> テンプレートの内容を利用し，国旗画像のURLを取得せよ．（ヒント: [MediaWiki](http://www.mediawiki.org/wiki/API:Main_page/ja) APIの[imageinfo](http://www.mediawiki.org/wiki/API:Properties/ja#imageinfo_.2F_ii)を呼び出して，ファイル参照をURLに変換すればよい）

### 解1 urllib.request で API Call
- `urllib.request` で WikiMedia の API を実行
    - [21.6. urllib.request — URL を開くための拡張可能なライブラリ](https://docs.python.jp/3/library/urllib.request.html#module-urllib.request)
- WikiMedia の最新の API 仕様を確認、以下API(svfファイル名を指定)で取得できそう
    - `https://commons.wikimedia.org/w/api.php?action=query&titles=File:Royal%20Coat%20of%20Arms%20of%20the%20United%20Kingdom.svg&prop=imageinfo&iiprop=url`
    - [API:Imageinfo - MediaWiki](https://www.mediawiki.org/wiki/API:Imageinfo)
        - Parameters:
        - iiprop:Which file information to get:
        - url:Gives URL to the file and the description page.

```json:imageinfo_api_result
                "imageinfo": [
                    {
                        "url": "https://upload.wikimedia.org/wikipedia/commons/9/98/Royal_Coat_of_Arms_of_the_United_Kingdom.svg",
                        "descriptionurl": "https://commons.wikimedia.org/wiki/File:Royal_Coat_of_Arms_of_the_United_Kingdom.svg",
                        "descriptionshorturl": "https://commons.wikimedia.org/w/index.php?curid=21101265"
                    }
```

```python:answer
import re
import urllib.request
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)

image_name = re.sub('\[\[ファイル:(.*?)\|.*\]\]','\\1',match_dct['国章画像']) # ファイル名を抽出
param = {
    'format': 'json',
    'action': 'query',
    'titles': 'File:Royal%20Coat%20of%20Arms%20of%20the%20United%20Kingdom.svg',
    'prop': 'imageinfo',
    'iiprop': 'url'
}
query = '&'.join("%s=%s" % (k, v) for k, v in param.items())
url = 'http://en.wikipedia.org/w/api.php?%s'  % (query)
request = urllib.request.urlopen(url)
jsonData = request.read().decode('utf-8')
data = json.loads(jsonData)
print(data.get('query').get('pages').get('-1').get('imageinfo')[0].get('url'))

# 'https://upload.wikimedia.org/wikipedia/commons/9/98/Royal_Coat_of_Arms_of_the_United_Kingdom.svg'
```

あれ、間違えて国旗じゃなく国章のURLを取得してた、まあいいやおしまい！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/130874/bab34d2e-6b0d-59d9-b798-d41c0f52783b.png)