LoginSignup
5
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-13

言語処理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. カテゴリ名の抽出

記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.

出来上がったコード:

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文字以上
    \[\[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(main2.py)
# 正規表現のコンパイル その2
pattern = re.compile(r'''
    ^       # 行頭
    .*      # 任意の文字0文字以上
    \[\[Category:
    (.*?)   # キャプチャ対象、任意の文字0文字以上、非貪欲マッチ
    (?:\]\]|\|) # キャプチャ対象外、']]'または'|'
    .*      # 任意の文字0文字以上
    $       # 行末
    ''', re.MULTILINE + re.VERBOSE)

こちらでも同じ結果になります。
ただ、どちらのコードも何かイマイチ。もう少しスマートに書く方法がありそうな気はします。

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


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

5
0
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
5
0