LoginSignup
1
0

More than 3 years have passed since last update.

言語処理100本ノック2020年版を解いてみた【第3章:正規表現 20〜24】

Last updated at Posted at 2020-05-17

東北大乾・岡崎研(現乾・鈴木研)で作成された、新人研修の一つであるプログラミング基礎勉強会の教材『言語処理100本ノック 2020年版』をPython(3.7)で解いた記事の第5弾です。

独学でPythonを勉強してきたので、間違いやもっと効率の良い方法があるかもしれません。
改善点を見つけた際はご指摘いただけると幸いです。

第3章からは合っているかどうかな部分が多くなってきているので、改善点だけでなく、合っているかどうかという点もぜひご指摘ください。

ソースコードはGitHubにも公開しています。

第3章: 正規表現

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.

  • 1行に1記事の情報がJSON形式で格納される

  • 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される

  • ファイル全体はgzipで圧縮される

以下の処理を行うプログラムを作成せよ.

20. JSONデータの読み込み

Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.問題21-29では,ここで抽出した記事本文に対して実行せよ.

20.py
import pandas as pd

file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query('title == "イギリス"')['text'].values[0]
print(uk_text)

これまでの経験でpandasは非常に便利だと実感したので、pandasを使っています。
pandas.read_json()関数の第一引数にJSON形式の文字列かファイルパスを渡すと、pandas.DataFrameとして読み込んでくれます。

今回読み込んでいるファイルは圧縮ファイルなので、オプションとして引数compressioninferを指定しています。
compression='infer'では.gz.bz2.zip.xzに対応です。

linesの引数では1行ごとにJSONオブジェクトとして読み込みを行っています。

イギリスの記事を探すのはDataFrameのquery()メソッドを使って行いました。
query()メソッドは指定した条件のデータ抽出を簡単に行えて便利です。
ここではtitleという列名がイギリスであるものを抽出してtext列の値を出力しています。

21. カテゴリ名を含む行を抽出

記事中でカテゴリ名を宣言している行を抽出せよ.

21.py
import pandas as pd
import re

file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query('title == "イギリス"')['text'].values[0]

pattern = r'\[\[Category:(.*)\]\]'
for line in uk_text.split("\n"):
    result = re.match(pattern, line)
    if result is not None:
        print(line)

ここから正規表現を使っていきます。

patternでは[[Category:〜]]に該当する行を抽出するように指定しています。
Pythonの正規表現を用いたマッチングではreモジュールをインポートします。
re.match()関数で第一引数にマッチさせたいパターンを、第二引数に調べたいテキストを指定してマッチングさせます。

マッチング結果がNoneでなければ条件に当てはまっているので、その行を出力という内容です。

22. カテゴリ名の抽出

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

22.py
import pandas as pd
import re

file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query('title == "イギリス"')['text'].values[0]

pattern = r'\[\[Category:(.*)\]\]'
remove_pattern = r'\|.*'
for line in uk_text.split("\n"):
    result = re.match(pattern, line)
    if result is None:
        continue
    ans = re.sub(remove_pattern, '', result.group(1))
    print(ans)

先程はカテゴリ名を行単位で出力していましたが、今回は名前のみの抽出という課題です。
各行に対して2つの作業を行っています。

まずは、「21.」同様[[Category:〜]]にマッチする部分があるかということ。
そして、マッチするものが合った場合は|〜の部分を取り除いたものを取得すること。

result.group(1)pattern = r'\[\[Category:(.*)\]\]'のマッチした部分のうち、(.*)の部分が取得できます。
これを回答としてしまっても良いかも知れませんが、余計なものも中に含まれていました。
[[Category:欧州連合加盟国|元]]のようにパイプも入ったものがたまにあるんですね。
このような場合、result.group(1)のみでは欧州連合加盟国|元が取得されるので、カテゴリ名として不要なパイプ以降は取り除きたいと思います。

ということで、re.sub()メソッドで第一引数に置き換えたいパターン、第二引数に置き換える文字列、第三引数に対象の文字列を指定して置換を行って出力しています。

23. セクション構造

記事中に含まれるセクション名とそのレベル(例えば”== セクション名 ==”なら1)を表示せよ.

23.py
import pandas as pd
import re

file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query('title == "イギリス"')['text'].values[0]

pattern = r'^(==*)(.+)=+$'
for line in uk_text.split("\n"):
    result = re.match(pattern, line)
    if result is None:
        continue
    print(result.group(2).strip(' ='), len(result.group(1)))

見出しは== 見出し ==のように行頭にイコールを2つ以上つけて表します。(参考:マークアップ早見表
イコールの数が見出しレベルを表しているので、見出しに対してマッチングを行い、行頭のイコールの数をresult.group(1)で、見出し名をresult.group(2)で抽出しています。

24. ファイル参照の抽出

記事から参照されているメディアファイルをすべて抜き出せ.

24.py
import pandas as pd
import re

file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query('title == "イギリス"')['text'].values[0]

pattern = r'ファイル:(.+?)\|(thumb\|.*)+?'
for line in uk_text.split("\n"):
    result = re.finditer(pattern, line)
    if result is None:
        continue
    for match in result:
        print(match.group(1))

ファイルは[[ファイル:Wikipedia-logo-v2-ja.png|thumb|説明文]]のような形で表されています。
行頭にあるとは限らないので、「ファイル:」という文字列を手がかりにマッチングを行っています。

今回は1行に複数のファイルが含まれている場合も考えられるので、re.finditer()関数を用いています。
finditer()関数では連続的にマッチオブジェクトを返すことができます。
行内でマッチしたものを対象にメディアファイル名を抽出しています。

まとめ

この記事では言語処理100本ノック2020年版 第3章: 正規表現の問題番号20〜24までを解いてみました。

学生の頃はRubyで正規表現を扱っていたのですが、Pythonの方がひとくせある印象を受けました。
最短マッチに毎度苦戦しましたが、やっているうちに徐々に慣れてくる感覚があったので、こういった丁寧な問題には感謝ですね。

まだまだ未熟なので、よりよい解答がありましたらぜひご指摘ください!!
よろしくお願いいたします。

つづき

前回まで

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