言語処理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で圧縮される
以下の処理を行うプログラムを作成せよ.
###22. カテゴリ名の抽出
記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.
####出来上がったコード:
# 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文字以上
\[\[Category:
( # キャプチャ対象のグループ開始
.*? # 任意の文字0文字以上、非貪欲マッチ(貪欲にすると後半の'|'で始まる装飾を巻き込んでしまう)
) # グループ終了
(?: # キャプチャ対象外のグループ開始
\|.* # '|'に続く0文字以上
)? # グループ終了、0か1回の出現
\]\]
.* # 任意の文字0文字以上
$ # 行末
''', re.MULTILINE + re.VERBOSE)
# 抽出
result = pattern.findall(extract_UK())
# 結果表示
for line in result:
print(line)
####実行結果:
イギリス
英連邦王国
G8加盟国
欧州連合加盟国
海洋国家
君主国
島国
1801年に設立された州・地域
###貪欲マッチ
今回苦労したのは、[[Category:イギリス|*]]
の|*
の部分や、[[Category:島国|くれいとふりてん]]
の|くれいとふりてん
をどうやって取り除くかでした。最初は前半のキャプチャしたい部分を(.*)
という形の貪欲(どんよく)マッチにしていましたが、うまくいきません。
取り除きたい部分は(\|.*)
で良さそうなのですが、この|
で始まる部分は存在しないこともあるので、このブロックには0回か1回を示す?
を付けて(\|.*)?
としました。しかしこれだと、前半の(.*)
の方に|
で始まる部分も該当するため、前半が全部持って行ってしまいます。そこでこの前半の部分を非貪欲マッチ(.*?)
にすることで、解決しました。
###キャプチャ対象外のグループ
取り除きたい部分はキャプチャする必要がないので、(...)
ではなく(?:...)
としています。
###もう1つ考えたコード
[[Category:...]]
を抽出してから|
で始まる部分を取り除くという考え方ではなく、初めから]]
か|
が出てくるまでをキャプチャする、という形も考えました。
# 正規表現のコンパイル その2
pattern = re.compile(r'''
^ # 行頭
.* # 任意の文字0文字以上
\[\[Category:
(.*?) # キャプチャ対象、任意の文字0文字以上、非貪欲マッチ
(?:\]\]|\|) # キャプチャ対象外、']]'または'|'
.* # 任意の文字0文字以上
$ # 行末
''', re.MULTILINE + re.VERBOSE)
こちらでも同じ結果になります。
ただ、どちらのコードも何かイマイチ。もう少しスマートに書く方法がありそうな気はします。
23本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第3章で用いているデータのライセンスはクリエイティブ・コモンズ 表示-継承 3.0 非移植(日本語訳)です。