素人の言語処理100本ノック:25


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

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

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

####出来上がったコード：

```python:main.py
# coding: utf-8
import gzip
import json
import re
fname = 'jawiki-country.json.gz'


def extract_UK():
	'''イギリスに関する記事本文を取得

	戻り値：
	イギリスの記事本文
	'''

	with gzip.open(fname, 'rt') as data_file:
		for line in data_file:
			data_json = json.loads(line)
			if data_json['title'] == 'イギリス':
				return data_json['text']

	raise ValueError('イギリスの記事が見つからない')


# 基礎情報テンプレートの抽出条件のコンパイル
pattern = re.compile(r'''
	^\{\{基礎情報.*?$	# '{{基礎情報'で始まる行
	(.*?)		# キャプチャ対象、任意の0文字以上、非貪欲
	^\}\}$		# '}}'の行
	''', re.MULTILINE + re.VERBOSE + re.DOTALL)

# 基礎情報テンプレートの抽出
contents = pattern.findall(extract_UK())

# 抽出結果からのフィールド名と値の抽出条件コンパイル
pattern = re.compile(r'''
	^\|			# '|'で始まる行
	(.+?)		# キャプチャ対象（フィールド名）、任意の1文字以上、非貪欲
	\s*			# 空白文字0文字以上
	=
	\s*			# 空白文字0文字以上
	(.+?)		# キャプチャ対象（値）、任意の1文字以上、非貪欲
	(?:			# キャプチャ対象外のグループ開始
		(?=\n\|) 	# 改行+'|'の手前（肯定の先読み）
		| (?=\n$)	# または、改行+終端の手前（肯定の先読み）
	)			# グループ終了
	''', re.MULTILINE + re.VERBOSE + re.DOTALL)

# フィールド名と値の抽出
fields = pattern.findall(contents[0])

# 辞書にセット
result = {}
keys_test = []		# 確認用の出現順フィールド名リスト
for field in fields:
	result[field[0]] = field[1]
	keys_test.append(field[0])

# 確認のため表示（確認しやすいようにkeys_testを使ってフィールド名の出現順にソート）
for item in sorted(result.items(),
		key=lambda field: keys_test.index(field[0])):
	print(item)
```

####実行結果：

```console:端末
('略名', 'イギリス')
('日本語国名', 'グレートブリテン及び北アイルランド連合王国')
('公式国名', '{{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}}（[[スコットランド・ゲール語]]）<br/>\n*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}（[[ウェールズ語]]）<br/>\n*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann}}（[[アイルランド語]]）<br/>\n*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Glédh}}（[[コーンウォール語]]）<br/>\n*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}（[[スコットランド語]]）<br/>\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>')
('国旗画像', 'Flag of the United Kingdom.svg')
('国章画像', '[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]')
('国章リンク', '（[[イギリスの国章|国章]]）')
('標語', '{{lang|fr|Dieu et mon droit}}<br/>（[[フランス語]]:神と私の権利）')
('国歌', '[[女王陛下万歳|神よ女王陛下を守り給え]]')
('位置画像', 'Location_UK_EU_Europe_001.svg')
('公用語', '[[英語]]（事実上）')
('首都', '[[ロンドン]]')
('最大都市', 'ロンドン')
('元首等肩書', '[[イギリスの君主|女王]]')
('元首等氏名', '[[エリザベス2世]]')
('首相等肩書', '[[イギリスの首相|首相]]')
('首相等氏名', '[[デーヴィッド・キャメロン]]')
('面積順位', '76')
('面積大きさ', '1 E11')
('面積値', '244,820')
('水面積率', '1.3%')
('人口統計年', '2011')
('人口順位', '22')
('人口大きさ', '1 E7')
('人口値', '63,181,775<ref>[http://esa.un.org/unpd/wpp/Excel-Data/population.htm United Nations Department of Economic and Social Affairs>Population Division>Data>Population>Total Population]</ref>')
('人口密度値', '246')
('GDP統計年元', '2012')
('GDP値元', '1兆5478億<ref name="imf-statistics-gdp">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a= IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom]</ref>')
('GDP統計年MER', '2012')
('GDP順位MER', '5')
('GDP値MER', '2兆4337億<ref name="imf-statistics-gdp" />')
('GDP統計年', '2012')
('GDP順位', '6')
('GDP値', '2兆3162億<ref name="imf-statistics-gdp" />')
('GDP/人', '36,727<ref name="imf-statistics-gdp" />')
('建国形態', '建国')
('確立形態1', '[[イングランド王国]]／[[スコットランド王国]]<br />（両国とも[[連合法 (1707年)|1707年連合法]]まで）')
('確立年月日1', '[[927年]]／[[843年]]')
('確立形態2', '[[グレートブリテン王国]]建国<br />（[[連合法 (1707年)|1707年連合法]]）')
('確立年月日2', '[[1707年]]')
('確立形態3', '[[グレートブリテン及びアイルランド連合王国]]建国<br />（[[連合法 (1800年)|1800年連合法]]）')
('確立年月日3', '[[1801年]]')
('確立形態4', "現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更")
('確立年月日4', '[[1927年]]')
('通貨', '[[スターリング・ポンド|UKポンド]] (&pound;)')
('通貨コード', 'GBP')
('時間帯', '±0')
('夏時間', '+1')
('ISO 3166-1', 'GB / GBR')
('ccTLD', '[[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>')
('国際電話番号', '44')
('注記', '<references />')
```

###「基礎情報」テンプレートとは？
Wikipediaのテンプレートの説明が[Help:テンプレート](https://ja.wikipedia.org/wiki/Help:テンプレート)にあるのですが、いきなりここを見ても良くわかりませんでした。どうやらきちんと学ぶには[Wikipedia:ガイドブック](https://ja.wikipedia.org/wiki/Wikipedia:ガイドブック)から始めて、編集のルールを覚える必要がありそうです。

ただ、Wikipediaの執筆予定は今のところないので今回はあまり深入りせず、今回対象のテンプレートの解説[Template:基礎情報 国](https://ja.wikipedia.org/wiki/Template:基礎情報_国)を見て、なんとなくフォーマットを理解して作ってみました。

本当は1つの正規表現で全て抽出したかったのですが、どうしても上手くいきません。そのため、基礎情報テンプレートの使用部分だけ抽出する処理と、抽出した結果からフィールドを抽出する処理の2段階で実装しています。

###基礎情報テンプレートの抽出
この正規表現はシンプルで、テンプレートの頭の部分と終わりの部分を探して、その間をキャプチャしているだけです。`.`で改行も対象にしたいため、正規表現のコンパイル時に[`re.DOTALL`](http://docs.python.jp/3/library/re.html#re.DOTALL)を指定しました。

この段階の抽出結果は、次のようになります。

```console:基礎情報テンプレートの抽出結果

|略名 = イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
|公式国名 = {{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/>
*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann}}（[[アイルランド語]]）<br/>
*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Glédh}}（[[コーンウォール語]]）<br/>
*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}（[[スコットランド語]]）<br/>
**{{lang|sco|Claught Kängrick o Docht Brätain an Norlin Airlann}}、{{lang|sco|Unitet Kängdom o Great Brittain an Norlin Airlann}}（アルスター・スコットランド語）</ref>
|国旗画像 = Flag of the United Kingdom.svg
|国章画像 = [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
|国章リンク = （[[イギリスの国章|国章]]）
|標語 = {{lang|fr|Dieu et mon droit}}<br/>（[[フランス語]]:神と私の権利）
|国歌 = [[女王陛下万歳|神よ女王陛下を守り給え]]
|位置画像 = Location_UK_EU_Europe_001.svg
|公用語 = [[英語]]（事実上）
|首都 = [[ロンドン]]
|最大都市 = ロンドン
|元首等肩書 = [[イギリスの君主|女王]]
|元首等氏名 = [[エリザベス2世]]
|首相等肩書 = [[イギリスの首相|首相]]
|首相等氏名 = [[デーヴィッド・キャメロン]]
|面積順位 = 76
|面積大きさ = 1 E11
|面積値 = 244,820
|水面積率 = 1.3%
|人口統計年 = 2011
|人口順位 = 22
|人口大きさ = 1 E7
|人口値 = 63,181,775<ref>[http://esa.un.org/unpd/wpp/Excel-Data/population.htm United Nations Department of Economic and Social Affairs>Population Division>Data>Population>Total Population]</ref>
|人口密度値 = 246
|GDP統計年元 = 2012
|GDP値元 = 1兆5478億<ref name="imf-statistics-gdp">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a= IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom]</ref>
|GDP統計年MER = 2012
|GDP順位MER = 5
|GDP値MER = 2兆4337億<ref name="imf-statistics-gdp" />
|GDP統計年 = 2012
|GDP順位 = 6
|GDP値 = 2兆3162億<ref name="imf-statistics-gdp" />
|GDP/人 = 36,727<ref name="imf-statistics-gdp" />
|建国形態 = 建国
|確立形態1 = [[イングランド王国]]／[[スコットランド王国]]<br />（両国とも[[連合法 (1707年)|1707年連合法]]まで）
|確立年月日1 = [[927年]]／[[843年]]
|確立形態2 = [[グレートブリテン王国]]建国<br />（[[連合法 (1707年)|1707年連合法]]）
|確立年月日2 = [[1707年]]
|確立形態3 = [[グレートブリテン及びアイルランド連合王国]]建国<br />（[[連合法 (1800年)|1800年連合法]]）
|確立年月日3 = [[1801年]]
|確立形態4 = 現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更
|確立年月日4 = [[1927年]]
|通貨 = [[スターリング・ポンド|UKポンド]] (&pound;)
|通貨コード = GBP
|時間帯 = ±0
|夏時間 = +1
|ISO 3166-1 = GB / GBR
|ccTLD = [[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>
|国際電話番号 = 44
|注記 = <references />
```

###フィールド名と値の抽出
フィールド名は行頭が`|`で始まるようなので、それを頼りに抽出しています。あとはフィールド名=値を切り出しているだけです。
ただし、`公式国名`などのフィールドの値は途中に改行があるため、値の終わりの判定は、次のフィールド名の先頭の手前、またはデータの終端としました。この「手前」を指すために肯定の先読みという機能を使っています。

###肯定の先読み
`(?=\n\|)`や`(?=\n$)`の部分が肯定の先読みと呼ばれる指定です。普通に`\n\|`と書いてしまうとフィールド名の行頭の`|`を消費してしまうため、次の検索でそのフィールドがヒットせず、フィールドの抽出が1行置きになってしまいます。
`(?=...)`という形で書けば、その手前までしか消費されないため、続くフィールドの検索に影響を与えないで済むようになっています。

###表示時に出現順でソート
結果を確認しやすいように、辞書を表示する際に出現順でソートしています。そのソートのためだけに、辞書追加時に`keys_test`というリストを作っています。
ただ、`fields`内は出現順になっているので、わざわざ`key_test`を作らなくても、ソート条件として`key=lambda field: list(zip(*fields))[0].index(field[0])`と指定すれば同じ結果になります。ただちょっとわかりにくいので、ここでは`keys_test`を作る形にしました。もう少しスマートな書き方もありそうです。

　
26本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第3章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示-継承 3.0 非移植](https://creativecommons.org/licenses/by-sa/3.0/legalcode)（[日本語訳](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)）です。*