LoginSignup
14
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-20

言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。

第3章: 正規表現

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

25. テンプレートの抽出

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

出来上がったコード:

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)

実行結果:

端末
('略名', 'イギリス')
('日本語国名', 'グレートブリテン及び北アイルランド連合王国')
('公式国名', '{{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:テンプレートにあるのですが、いきなりここを見ても良くわかりませんでした。どうやらきちんと学ぶにはWikipedia:ガイドブックから始めて、編集のルールを覚える必要がありそうです。

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

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

基礎情報テンプレートの抽出

この正規表現はシンプルで、テンプレートの頭の部分と終わりの部分を探して、その間をキャプチャしているだけです。.で改行も対象にしたいため、正規表現のコンパイル時にre.DOTALLを指定しました。

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

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

|略名 = イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
|公式国名 = {{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本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。


実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第3章で用いているデータのライセンスはクリエイティブ・コモンズ 表示-継承 3.0 非移植日本語訳)です。

14
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
2