きょうこログ

初心忘れず、最後まで全力で頑張ります。よろしくお願いします。

言語処理100本ノック【第3章 前編】

課題研究で言語処理100本ノック 2015に取り組んでいます。前回の続きです。使用言語はPython3です。今回からは第3章 正規表現 です。まずは第3章前半の5つを説明します。よければプログラムも説明文も、間違ったところや変なところがあればアドバイス頂けるとすごく嬉しいです!

第3章の概要

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

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

20. JSONデータの読み込み

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

ソースコード

import json

with open("jawiki-country.json", "r") as f:
  for i in f:
    a = json.loads(i) 
    if a.get("title") == "イギリス":
      print(a)

このプログラムではJSONファイルを読み込みの前段階として、withを使用しています。これまでのプログラムでは、open()でファイルオブジェクトを取得しclose()でメモリを解放する ( = ファイル操作を終わる ) までが一連の流れでした。しかしwith open() asを使ってファイルオブジェクトを取得すると、withブロックを抜けるタイミングで自動的にcloseするので、close()を明示する必要がありません。めっちゃ便利。

JSONデータを読み込むには、jsonモジュールのloads関数を用います。そもそもJSONとはデータを表現するための記法の1つで、難しい構造のデータを人間にも分かり易く表現することができます。例えば、

{
    "名前":[
        "きょうこ",
        "KYOKO",
    ],
    "出身地" : "日本",
    "生年月日":[
        "" : "1998",
        "" : "12",
        "" : "26"
    ]
}

と書くことで、人物のデータをまとめることが出来ます。loads関数を用いることで、与えられたJSONデータを辞書型に変換しました。

出力結果

{{redirect|UK}}
{{基礎情報 国
|略名 = イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
(中略)
[[Category:島国|くれいとふりてん]]
[[Category:1801年に設立された州・地域]]

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

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

ソースコード

import json

with open("jawiki-country.json", "r") as f:
  for i in f:
    a = json.loads(i) 
    if a.get("title") == "イギリス":
      b = a.get("text").split("\n")

for i in b:
  if "Category" in i:
    print(i)

split()は文字列を任意の区切り文字で分割する関数で、このプログラムではイギリスに関する記事を \n ごとに区切りました。区切った各行に対してinステートメントを使って、"Category" という文字列を含む行のみを抽出しています。

実行結果

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

22. カテゴリ名の抽出

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

ソースコード

import json
import re

with open("jawiki-country.json", "r") as f:
  for i in f:
    a = json.loads(i) 
    if a.get("title") == "イギリス":
      b = a.get("text").split("\n")

regex = re.compile("^\[\[Category:(.*?)(\|.*)*\]\]$")

for j in b:
  if "Category" in j:
    print(regex.search(j).group(1))

このプログラムでは、jsonモジュールとreモジュールをインポートしました。1つ前の 21. カテゴリ名を含む行を抽出 で抽出した行に対して正規表現を使用し、カテゴリ名を抜き出します。re.compile()は、正規表現オブジェクトをコンパイルする関数です。この関数で生成したオブジェクトに対して、reモジュールの関数を使用することができます。

この問題のポイントは、行によって抽出のパターンが2通り考えられることです。例えば、

f:id:f_respectful12:20180405150051p:plain

のように、パターン①では |* を除去して "イギリス" のみ抽出する必要がありますが、パターン②では "欧州連合加盟国" だけを抽出します。ここでは、"|"からはじまる0文字以上の文字を考えることで、2つのパターンから必要な情報だけを抜き出しました。

^[[Category:(.?)(|.)*]]$

正規表現 意味
^ 行のはじまり
[[Category: 全て一致
(.*?) 任意の0文字以上の文字: グループ
(|.*) "|"からはじまる0文字以上の文字: グループ
*]] 全て一致
$ 行のおわり

search()正規表現がマッチする最初の場所を探す関数です。正規表現オブジェクト.search(検索対象の文字列)で検索結果 ( = マッチオブジェクト ) を返します。group()はマッチオブジェクトを解読して、抽出したグループを取り出す関数です。正規表現では抽出対象をグループ化することが出来ます。この例では、(.*?)(\|.*)などです。group()の引数にはグループ番号を指定します。グループ番号は正規表現の左から順番に 1, 2, 3... です。抽出対象全体は 0 で指定できます。

実行結果

イギリス
英連邦王国
G8加盟国
欧州連合加盟国
海洋国家
君主国
島国
1801年に設立された州・地域

23. セクション構造

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

ソースコード

import json
import re

with open("jawiki-country.json", "r") as f:
  for i in f:
    a = json.loads(i) 
    if a.get("title") == "イギリス":
      b = a.get("text").split("\n")

regex = re.compile(u"^(==+)\s*(.*?)\s*(==+)$")

for j in b:
  c = regex.search(j)
  if c != None: print("{0} [{1}]".format(c.group(2), len(c.group(1)) - 1))

このプログラムでは、==国名====歴史==といった風に与えられているセクション名から構造を読み解き、構造の深さを数値化して出力します。

f:id:f_respectful12:20180405161602p:plain

ここで使用した正規表現は以下の通りです。

^(==+)\s(.?)\s*(==+)$

正規表現 意味
^ 行のはじまり
(==+) "=="と1回以上一致する"="だけで構成される文字
\s* 任意の0文字以上の空白文字
(.*?) 任意の0文字以上の文字
\s* 任意の0文字以上の空白文字
(==+) "=="と1回以上一致する"="だけで構成される文字
$ 行のおわり

2番目のfor文では、上記の正規表現と一致するときセクション名と構造の深さを出力します。group(1)の長さから1引いたものを、構造の深さと考えました。

実行結果

国名 [1]
歴史 [1]
地理 [1]
気候 [2]
政治 [1]
外交と軍事 [1]
地方行政区分 [1]
(中略)
関連項目 [1]
外部リンク [1]

24. ファイル参照の抽出

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

ソースコード

import json
import re

with open("jawiki-country.json", "r") as f:
  for i in f:
    a = json.loads(i) 
    if a.get("title") == "イギリス":
      b = a.get("text").split("\n")

regex = re.compile("(File|ファイル):(.*?)\|")

for j in b:
  c = regex.search(j)
  if c != None:
    print(c.group(2))

記事から参照されているメディアファイルは、

|国章画像 = [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]

[[File:Battle of Waterloo 1815.PNG|thumb|left|[[ワーテルローの戦い]]での勝利により[[ナポレオン戦争]]は終止符が打たれ、[[パクス・ブリタニカ]]の時代が到来した。]]

のように、ファイル:もしくはFile:| で囲まれた範囲に記されていることが分かります。これから考えられる正規表現は、

(File|ファイル):(.*?)|

正規表現 意味
(File ファイル) |"ファイル"もしくは"File"と一致
: 全て一致
(.*?) 任意の0文字以上の文字
| 全て一致

です。

実行結果

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
(中略)
London Tower (1).JPG
Wembley Stadium, illuminated.jpg

エントリーシートに時間を取られてたら、いつの間にか高専5年生になっていました。添削して下さった方々、本当にありがとうございます。 学生最後の1年間、インプットもアウトプットも楽しみまくるぞー!