2018年03月19日

言語処理100本ノックでPython入門 #27 - 正規表現の(?:……)


今日は、言語処理100本ノック 2015の問題27です。

■ 問題

問題20で作成したファイルを入力として、以下の問題を解きます。

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


内部リンクマークアップを除去するとは、
[[記事名]]  ==> 記事名
[[記事名|表示文字]  ==> 表示文字
[[記事名#節名|表示文字]]  ==> 表示文字
と変換せよということだと解釈。 なので、
[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
といった、ファイル: と先頭にあるものなどは、対象外としました。


■ Pythonのコード
import json
import re

def readArticle(filename):
    with open(filename, 'r', encoding='utf8') as fin:
        return fin.read()

def getBasicInfo(article):
    basicInfo = re.search(r'\{\{基礎情報(.+?)\}\}\n', article, re.DOTALL).group(1)
    pat = r'^\|(.+?)\s*=\s*(.+?)(?<!<br/>)\n'
    reg = re.compile(pat, re.MULTILINE | re.DOTALL)
    wdict = {}
    for k, v in reg.findall(basicInfo):
        v1 = re.sub(r"''('|''')([^']+?)''+", r"\2", v)
        v2 = re.sub(r"\[\[(?:[^:\]]+?\|)?([^:]+?)\]\]", r"\1", v1)
        wdict[k] = v2
    return wdict

def main():
    # england-article.txtは、問題20(nlp20.py)で作成したファイル
    article = readArticle('england-article.txt')    
    dic = getBasicInfo(article)
    dic = sorted(dic.items(), key=lambda x: x[0])
    for item, v in dic:
        print(item, ':', v)

if __name__ == '__main__':
    main()

■ コード内の正規表現の説明
v2 = re.sub(r"\[\[(?:[^:\]]+?\|)?([^:]+?)\]\]", r"\1", v1)
の簡単な説明を。

まずは、正規表現の全体像。
r"\[\[……\]\]"
これで、内部リンクと一致させます。

途中の
(?:[^:\[]+?\|)?
は、削除して良い文字列を示しています。
(?:……)
は、後からグループ参照をしないという意味。これで、/1, /2 などは対象外になります。

[^:\[]+?
は、: と [ 以外の文字が1以上連続(+?なので最短一致)する文字列です。

その後に、|が続くパターンを示しています。 最後に?付けてるので、これが、0回か1回でマッチします。

その後の
([^:]+?)
は、:以外の任意の文字が1以上連続するパターンで、これが残すべき文字列です。\1 で参照できます。

前半の (?:……)が有効に働いています。 ?: 指定をしておかないと、\1 か \2 かのどちらを残したらよいかわからないです。


■ 結果

GDP/人 : 36,727<ref name="imf-statistics-gdp" />
GDP値 : 2兆3162億<ref name="imf-statistics-gdp" />
GDP値MER : 2兆4337億<ref name="imf-statistics-gdp" />
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統計年 : 2012
GDP統計年MER : 2012
GDP統計年元 : 2012
GDP順位 : 6
GDP順位MER : 5
ISO 3166-1 : GB / GBR
ccTLD : .uk / .gb<ref>使用は.ukに比べ圧倒的少数。</ref>
人口値 : 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>
人口大きさ : 1 E7
人口密度値 : 246
人口統計年 : 2011
人口順位 : 22
位置画像 : Location_UK_EU_Europe_001.svg
元首等氏名 : エリザベス2世
元首等肩書 : 女王
公式国名 : {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br/>
*{{lang|gd|An Rioghachd Aonaichte na Breatainn Mhor agus Eirinn mu Thuath}}(スコットランド・ゲール語)<br/>
*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}(ウェールズ語)<br/>
*{{lang|ga|Riocht Aontaithe na Breataine Moire agus Tuaisceart na hEireann}}(アイルランド語)<br/>
*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Gledh}}(コーンウォール語)<br/>
*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}(スコットランド語)<br/>
**{{lang|sco|Claught Kangrick o Docht Bratain an Norlin Airlann}}、{{lang|sco|Unitet Kangdom o Great Brittain an Norlin Airlann}}(アルスター・スコットランド語)</ref>
公用語 : 英語(事実上)
国旗画像 : Flag of the United Kingdom.svg
国歌 : 神よ女王陛下を守り給え
国章リンク : (国章)
国章画像 : [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
国際電話番号 : 44
夏時間 : +1
建国形態 : 建国
日本語国名 : グレートブリテン及び北アイルランド連合王国
時間帯 : ±0
最大都市 : ロンドン
標語 : {{lang|fr|Dieu et mon droit}}<br/>(フランス語:神と私の権利)
水面積率 : 1.3%
注記 : <references />
略名 : イギリス
確立年月日1 : 927年/843年
確立年月日2 : 1707年
確立年月日3 : 1801年
確立年月日4 : 1927年
確立形態1 : イングランド王国/スコットランド王国<br />(両国とも1707年連合法まで)
確立形態2 : グレートブリテン王国建国<br />(1707年連合法)
確立形態3 : グレートブリテン及びアイルランド連合王国建国<br />(1800年連合法)
確立形態4 : 現在の国号「グレートブリテン及び北アイルランド連合王国」に変更
通貨 : UKポンド (&pound;)
通貨コード : GBP
面積値 : 244,820
面積大きさ : 1 E11
面積順位 : 76
首相等氏名 : デーヴィッド・キャメロン
首相等肩書 : 首相
首都 : ロンドン
  

Posted by gushwell at 21:00Comments(0)Python

2018年03月18日

言語処理100本ノックでPython入門 #26 - 正規表現の置換処理


今日は、言語処理100本ノック 2015の問題26です。

■ 問題

問題20で作成したファイルを入力として、以下の問題を解きます。
26. 強調マークアップの除去 25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)を除去してテキストに変換せよ(参考: マークアップ早見表).

■ どうやって解くか


強調マークアップは、以下のように2つ以上の''で括ります。
''弱い強調''    
'''強調'''    
'''''強い強調'''''

このマークアップ('記号)だけを削除するということですね。

re.subメソッドを使って置換処理を行えばいいですね。 ちょっと手抜きですが、以下のような正規表現にしました。
for k, v in reg.findall(basicInfo):
    wdict[k] =  re.sub(r"''('|''')([^']+?)''+", r"\2", v)

4番目の引数として、回数を指定すると、置換する回数を指定できます。省略するとすべてを置換します。 今回は、すべてを置換対象とするので、第4引数は省略しています。


■ Pythonのコード
import re

def readArticle(filename):
    with open(filename, 'r', encoding='utf8') as fin:
        return fin.read()

def getBasicInfo(article):
    basicInfo = re.search(r'\{\{基礎情報(.+?)\}\}\n', article, re.DOTALL).group(1)
    pat = r'^\|(.+?)\s*=\s*(.+?)(?<!<br/>)\n'
    reg = re.compile(pat, re.MULTILINE | re.DOTALL)
    wdict = {}
    for k, v in reg.findall(basicInfo):
        v1 = re.sub(r"''('|''')([^']+?)''+", r"\2", v)
        wdict[k] = v1

    return wdict

def main():
    article = readArticle('england-article.txt')    
    dic = getBasicInfo(article)
    dic = sorted(dic.items(), key=lambda x: x[0])
    for item, v in dic:
        print(item, ':', v)

if __name__ == '__main__':
    main()


■ 結果

結果は長くなるので、該当箇所だけ抜き出したものを以下に示します。
確立形態4 : 現在の国号「グレートブリテン及び北アイルランド連合王国」に変更
でも、基礎情報のうち該当する個所が上の一か所なんだけど...
これで合ってるのかな?

元の行は以下の通りです。

確立形態4 : 現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更
  
Posted by gushwell at 22:30Comments(0)Python

2018年03月15日

言語処理100本ノックでPython入門 #25 - re.DOTALL/re.MULTILINEオプション



言語処理100本ノック 2015の問題25に挑戦です。 

■ 問題

問題20で作成したファイルを入力として、以下の問題を解きます。
25. テンプレートの抽出
記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.

■ Pythonのコード
import json
import re
def readArticle(filename): with open(filename, 'r', encoding='utf8') as fin: return fin.read()
def getBasicInfo(article): basicInfo = re.search(r'\{\{基礎情報(.+?)\}\}\n', article, re.DOTALL).group(1) pat = r'^\|(.+?)\s*=\s*(.+?)(?<!<br/>)\n' reg = re.compile(pat, re.MULTILINE | re.DOTALL) wdict = {} for k, v in reg.findall(basicInfo): wdict[k] = v return wdict
def main(): article = readArticle('england-article.txt') dic = getBasicInfo(article) dic = sorted(dic.items(), key=lambda x: x[0]) for item, v in dic: print(item, ':', v)
if __name__ == '__main__': main()
'england-article.txt'は問題20で作成したファイルです。これを入力ファイルとしています。


■ 今回のトピック等

今回はいろいろと勉強になりました。

re.DOTALLオプション

まずは、基礎情報のところだけを抜き出し、それから、フィールドと値を取り出そうと考えました。 しかし、
re.search(r'\{\{基礎情報(.+?)\}\}\n', article)
って書いても一致しません。どうしてって悩みましたが、re.DOTALLというオプションが必要なようです。
re.DOTALLは、正規表現のドット.に改行も含めるという指定です。

途中で、対応していない`}}\n`があったら、正しく取り出せないんですが、そういた記述はないようなので これで良しとします。

re.MULTILINEオプション

次に、フィールドと値を取り出します。調べると、"公式国名"の値は改行が含まれていて複数行にわたっています。 次のように書いてみました。
pat = r'^\|(.+?)\s*=\s*(.+?)(?<!<br/>)\n'
reg = re.compile(pat, re.DOTALL)

正規表現の (?<!...) は、... とマッチしない場合にマッチします。つまり、(?<!--<br/-->)は、文字列`<br />`を含まないという指定です。

でも、うまくいきません。複数行の検索においては、行頭の ^ を各行の行頭とマッチさせるには、re.DOTALLとは別にre.MULTILINE を指定する必要がありました。
reg = re.compile(pat, re.MULTILINE | re.DOTALL)

finditer/findallメソッド 

finditerメソッドを使うと、matchオブジェクトを列挙することができます。

最初に書いたコード。
wdict = {}
for m in reg.finditer(basicInfo):
    wdict[m.group(1)] = m.group(2)
return wdict 

次に書いたコード。
wdict = {}
for k, v in reg.findall(basicInfo):
    wdict[k] = v
return wdict    
findallメソッド使うと、こんな書き方もできるんですね。

findallメソッドは、戻り値はmatchオブジェクトではないので、group()などは利用できません。
その代わり、グループ指定がある場合は、そのタプルのリストが返るので、for文で上のようにタプルの要素を分解して取得することができます。

今回のコードでは、これを採用しました。
今回の例だと、()によるグルーピングは2つですが、3つの場合は、3つの要素を持つタプルになるので、
 
for g1, g2, g3 in reg.findall(s):
のように書けます。


■ 結果
GDP/人 : 36,727<ref name="imf-statistics-gdp" />
GDP値 : 2兆3162億<ref name="imf-statistics-gdp" />
GDP値MER : 2兆4337億<ref name="imf-statistics-gdp" />
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統計年 : 2012
GDP統計年MER : 2012
GDP統計年元 : 2012
GDP順位 : 6
GDP順位MER : 5
ISO 3166-1 : GB / GBR
ccTLD : [[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>
人口値 : 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>
人口大きさ : 1 E7
人口密度値 : 246
人口統計年 : 2011
人口順位 : 22
位置画像 : Location_UK_EU_Europe_001.svg
元首等氏名 : [[エリザベス2世]]
元首等肩書 : [[イギリスの君主|女王]]
公式国名 : {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br/>
*{{lang|gd|An Rioghachd Aonaichte na Breatainn Mhor agus Eirinn mu Thuath}}([[スコットランド・ゲール語]])<br/>
*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}([[ウェールズ語]])<br/>
*{{lang|ga|Riocht Aontaithe na Breataine Moire agus Tuaisceart na hEireann}}([[アイルランド語]])<br/>
*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Gledh}}([[コーンウォール語]])<br/>
*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}([[スコットランド語]])<br/>
**{{lang|sco|Claught Kangrick o Docht Bratain an Norlin Airlann}}、{{lang|sco|Unitet Kangdom o Great Brittain an Norlin Airlann}}(アルスター・スコットランド語)</ref>
公用語 : [[英語]](事実上)
国旗画像 : Flag of the United Kingdom.svg
国歌 : [[女王陛下万歳|神よ女王陛下を守り給え]]
国章リンク : ([[イギリスの国章|国章]])
国章画像 : [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]
国際電話番号 : 44
夏時間 : +1
建国形態 : 建国
日本語国名 : グレートブリテン及び北アイルランド連合王国
時間帯 : ±0
最大都市 : ロンドン
標語 : {{lang|fr|Dieu et mon droit}}<br/>([[フランス語]]:神と私の権利)
水面積率 : 1.3%
注記 : <references />
略名 : イギリス
確立年月日1 : [[927年]]/[[843年]]
確立年月日2 : [[1707年]]
確立年月日3 : [[1801年]]
確立年月日4 : [[1927年]]
確立形態1 : [[イングランド王国]]/[[スコットランド王国]]<br />(両国とも[[連合法 (1707年)|1707年連合法]]まで)
確立形態2 : [[グレートブリテン王国]]建国<br />([[連合法 (1707年)|1707年連合法]])
確立形態3 : [[グレートブリテン及びアイルランド連合王国]]建国<br />([[連合法 (1800年)|1800年連合法]])
確立形態4 : 現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更
通貨 : [[スターリング・ポンド|UKポンド]] (&pound;)
通貨コード : GBP
面積値 : 244,820
面積大きさ : 1 E11
面積順位 : 76
首相等氏名 : [[デーヴィッド・キャメロン]]
首相等肩書 : [[イギリスの首相|首相]]
首都 : [[ロンドン]]

  
Posted by gushwell at 20:15Comments(0)Python

2018年03月13日

言語処理100本ノックでPython入門 #24 - 正規表現のcompile



本日は、言語処理100本ノック 2015の問題24です。

■ 問題


問題20で作成したファイルを入力として、以下の問題を解きます。
24. ファイル参照の抽出 
記事から参照されているメディアファイルをすべて抜き出せ.

■ Pythonのコード
import json
import re

def readArticle(filename): with open(filename, 'r', encoding='utf8') as fin: return fin.read()
def getMediaFfiles(article): reg = re.compile(r'(?:File|ファイル):([^\|]+)\|') for s in article.split('\n'): m = reg.search(s) if m: yield m.group(1)
def main(): article = readArticle('england-article.txt') for name in getMediaFfiles(article): print(name)
if __name__ == '__main__': main()

'england-article.txt'は問題20で作成したファイルです。これを入力ファイルとしています。


■ 正規表現


今回は、以下のような正規表現を書いたわけですが、

r'(?:File|ファイル):([^\|]+)\|'
?: は、後方参照の対象にはならないので、(?:File|ファイル) の部分は m.group(n)での参照対象外です。

なので、 m.group(1) で目的の文字列が取り出せます。

それと、Pyrtnonのドキュメント「6.2. re — 正規表現操作」によると、
簡単なアプリケーションを除くほとんどで、コンパイルされた形式が用いられるのが普通です。
とのことなので、今回は、以下のようにcompileしてみました。

reg = re.compile(r'(?:File|ファイル):([^\|]+)\|')

■ 実行結果
Royal Coat of Arms of the United Kingdom.svg
Battle of Waterloo 1815.PNG
The British Empire.png
Uk topo en.jpg
BenNevis2005.jpg
Elizabeth II greets NASA GSFC employees, May 8, 2007 edit.jpg
Palace of Westminster, London - Feb 2007.jpg
David Cameron and Barack Obama at the G20 Summit in Toronto.jpg
Soldiers Trooping the Colour, 16th June 2007.jpg
Scotland Parliament Holyrood.jpg
London.bankofengland.arp.jpg
City of London skyline from London City Hall - Oct 2008.jpg
Oil platform in the North SeaPros.jpg
Eurostar at St Pancras Jan 2008.jpg
Heathrow T5.jpg
Anglospeak.svg
CHANDOS3.jpg
The Fabs.JPG
PalaceOfWestminsterAtNight.jpg
Westminster Abbey - West Door.jpg
Edinburgh Cockburn St dsc06789.jpg
Canterbury Cathedral - Portal Nave Cross-spire.jpeg
Kew Gardens Palm House, London - July 2009.jpg
2005-06-27 - United Kingdom - England - London - Greenwich.jpg
Stonehenge2007 07 30.jpg
Yard2.jpg
Durham Kathedrale Nahaufnahme.jpg
Roman Baths in Bath Spa, England - July 2006.jpg
Fountains Abbey view02 2005-08-27.jpg
Blenheim Palace IMG 3673.JPG
Liverpool Pier Head by night.jpg
Hadrian's Wall view near Greenhead.jpg
London Tower (1).JPG
Wembley Stadium, illuminated.jpg
  
Posted by gushwell at 22:30Comments(0)Python

2018年03月11日

言語処理100本ノックでPython入門 #23 - タプル



言語処理100本ノック 2015の問題23に挑戦です。 

■ 問題


問題20で作成したファイルを入力として、以下の問題を解きます。
23. セクション構造
記事中に含まれるセクション名とそのレベル(例えば"== セクション名 =="なら1)を表示せよ.

■ Pythonのコード
import re

def readArticle(filename): with open(filename, 'r', encoding='utf8') as fin: return fin.read()
def getSections(article): for s in article.split('\n'): m = re.match(r'(=+)\s*([^=\s]+)\s*=+', s) if m: yield m.group(2), len(m.group(1)) - 1
def main(): article = readArticle('england-article.txt') for name, level in getSections(article): print(name, level)
if __name__ == '__main__': main()

■ どう解くか

'england-article.txt'は問題20で作成したファイルです。これを入力ファイルとしています。
No22 とほぼ同じような構造で書けますね。

変更したのは、getCategoriesメソッドの名前をgetSectionsに変え、中身と戻り値を若干変更しただけです。

def getSections(article):
    for s in article.split('\n'):
        m = re.match(r'(=+)\s*([^=\s]+)\s*=+', s)
        if m:
            yield m.group(2), len(m.group(1)) - 1

このメソッドで、レベルをどう取り出そうか少し悩みましたが、`=` 連続もグループ化して、その長さを求めればOKですね。 それと、= と セクション名の間に空白がある場合もあるので、`\s*`で空白有りにも対応しています。


■ タプル


Pythonで初めてタプル使いました。yieldで列挙するときに、タプルを使っています。

yield m.group(2), len(m.group(1)) - 1

呼び出し元での参照も簡単です。
for name, level in getSections(article):
        print(name, level)

やはり、言語そのものでタプルをサポートしているのでとても便利です。


■ 結果

国名 1
歴史 1
地理 1
気候 2
政治 1
外交と軍事 1
地方行政区分 1
主要都市 2
科学技術 1
経済 1
鉱業 2
農業 2
貿易 2
通貨 2
企業 2
交通 1
道路 2
鉄道 2
海運 2
航空 2
通信 1
国民 1
言語 2
宗教 2
婚姻 2
教育 2
文化 1
食文化 2
文学 2
哲学 2
音楽 2
イギリスのポピュラー音楽 3
映画 2
  
Posted by gushwell at 22:20Comments(0)Python

2018年03月08日

言語処理100本ノックでPython入門 #22 - 正規表現のグループ


今日は、言語処理100本ノック 2015の問題22です。

■ 問題

問題20で作成したファイルを入力として、以下の問題を解きます。
22. カテゴリ名の抽出
記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.

■ Pythonのコード
import json
import re
def readArticle(filename): with open(filename, 'r', encoding='utf8') as fin: return fin.read()
def getCategories(article): for s in article.split('\n'): m = re.match(r'\[\[Category:([^|]*)(|.*)?\]\]', s) if m: yield m.group(1)
def main(): article = readArticle('england-article.txt') for cat in getCategories(article): print(cat)
if __name__ == '__main__': main()
'england-article.txt'は問題20で作成したファイルです。これを入力ファイルとしています。


■ 正規表現のグループ

問題21のコードをすこし変えればできますね。 カテゴリの行は以下のようになっています。

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

ここから、実際のカテゴリ名を抜き出します。

まずは、
m = re.match(r'\[\[Category:([^|]*)(|.*)?\]\]', s)
のようにして、matchオブジェクトを得ます。

カテゴリが下のような文字列だった場合は、|の前後で、グルーピングを分けるようにしています。
英連邦王国|*

その後、以下のように書けば、最初の()グループの中身が取得できます。
m.group(1) 

つまり、"英連邦王国|*" だったら、"英連邦王国"だけを取得できます。
r'\[\[Category:([^|]*)(|.*)?\]\]'
って表現がもう少し簡単にならないかなー。


■ 結果
イギリス
英連邦王国
G8加盟国
欧州連合加盟国
海洋国家
君主国
島国
1801年に設立された州・地域
  
Posted by gushwell at 21:43Comments(0)Python