2018年03月06日

言語処理100本ノックでPython入門 #21 - 正規表現のmatch / ジェネレータ



言語処理100本ノック 2015の問題21を解きます。

■ 問題


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

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


カテゴリの行を抜粋してみると、以下のようになっています。これを正規表現を使って抜き出せば良いんですね。

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


■ 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'): if re.match(r'\[\[Category:.*\]\]', s): yield s
def main(): # england-article.txtは、問題20(nlp20.py)で作成したファイル article = readArticle('england-article.txt') for cat in getCategories(article): print(cat)
if __name__ == '__main__': main()

■ 今回のトピックなど

split関数 

問題20で作成したテキストファイルを入力にしています。 このテキストファイルをread()で一気に読み込んでいるので、行単位に分けるのに、 以下のようにsplit関数を使っています。
for s in text.split('\n'):

これで行単位に分割して、ループさせます。 r

e.search/re.match

カテゴリ名を宣言している行を抽出するには、searchメソッドを使えばOKですね。

なお、この2つの関数はmatchオブジェクトを返しますが、このmatchオブジェクトは常にブール値 True を持つらしいです。
オブジェクトがブール値を持つってC#プログラマとしてはちょっと違和感がありますが、まあ、便利なので良しとします。

で、見つからなかったときは、Noneを返すので、if文使ってマッチしたかどうかを調べられるようになっています。
if re.search(r'\[\[Category:.*\]\]', s):

これで、Categoryの行を取り出しています。

if re.match(r'\[\[Category:.*\]\]', s):
でも良いみたいです。

matchは行頭から一致するかを調べる。一方、searchは、指定パターンが文字列に含まれていればよいです。 ここでは、matchを使いました。

C#と違うから、なんか間違えそうです。

ちなみに、
r'\[\[Category:.*\]\]'
は、C#で書くと、
@"\[\[Category:.*\]\]"
と同じです。


ジェネレータ(generator)

pythonでも、yieldが使えます。 getCategories関数は、yieldで列挙してるから、以下のようにfor文で取り出せます。うれしい機能ですね。
for cat in getCategories(article):
    print(cat)

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

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

2018年03月04日

言語処理100本ノックでPython入門 #20 - gzipの読み込みとJSON


本日から、言語処理100本ノック 2015の第3章: 正規表現へ入ります。 まずは問題20。

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

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

■ Pythonのコード
import json
import gzip
def extract(title): with gzip.open('jawiki-country.json.gz', 'rt', encoding='utf8') as fin: for line in fin: jsd = json.loads(line) if jsd['title'] == title: return jsd['text'] return ''
def main(): article = extract('イギリス') with open('england-article.txt', 'w', encoding='utf8') as fout: fout.write(article)
if __name__ == '__main__': main()

■ 今回のトピックなど


gzipファイルの読み込み

import gzip
とgzipモジュールをインポートし、gzip.openメソッドでオープンします。
with gzip.open('jawiki-country.json.gz', 'rt', encoding='utf8') as fin:
    for line in fin:
        ...
2番目の引数を'rt'として、テキストファイルであることを指定します。
'r' にしてしまうとバイナリモードでオープンしてしまうので、注意が必要です。
後は、通常のテキストファイルと同じように扱えます。


JSONデータの扱い

JSONを読むには、
import json
とjsonモジュールをインポートします。

で、以下のようにjson.loadでJOSNデータを読み込みます・
with gzip.open('jawiki-country.json.gz', 'rt', encoding='utf8') as fin:
    jsd = json.load(fin)

あれ、例外が発生しました。
json.decoder.JSONDecodeError: Extra data: line 2 column 1 (char 27837)

どうも、json形式じゃないよと、例外が出ているようです。

実際に、jsonファイルの中を見てみたら、最初が [] で囲まれてないから、配列ではありません。
このファイルは、「1行1行が、json形式になってる」ということらしいです。

そのため、以下のようなコードを書くことで、無事読み込んで、該当のtextを取り出すことができました。
def extract(title):
    with gzip.open('chap03/jawiki-country.json.gz', 'rt', encoding='utf8') as fin:
        for line in fin:
            jsd = json.loads(line)
            ......
    return ''

load(), loads()の違いってなんだろうということだけど、loads()の末尾のsは、stringのsらしいです。
load一つで、オーバーロードしてくれたほうがありがたいんだけどなー。

で、loads/loadの結果は、辞書形式になるようなので、jsd['title']のようにして、各項目にアクセスできます。

結果は、'england-article.txt'に書き出しています。 問題文に「問題21-29では,ここで抽出した記事本文に対して実行せよ.」とあるので、問題21以降は、この'england-article.txt' を入力ファイルとします。


■結果

結果の一部を載せます。
{{redirect|UK}}
{{基礎情報 国
|略名 = イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
|公式国名 = {{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|イギリスの国章]]
|国章リンク = ([[イギリスの国章|国章]])
|標語 = {{lang|fr|Dieu et mon droit}}<br/>([[フランス語]]:神と私の権利)
|国歌 = [[女王陛下万歳|神よ女王陛下を守り給え]]
|位置画像 = Location_UK_EU_Europe_001.svg
|公用語 = [[英語]](事実上)
|首都 = [[ロンドン]]
|最大都市 = ロンドン
……以下省略

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

2018年03月01日

言語処理100本ノックでPython入門 #19 - 辞書のソート


言語処理100本ノック 2015の第2章最後の問題です。

■ 問題
hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる
各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.確認にはcut, uniq, sortコマンドを用いよ.

■ Pythonのコード
def appearanceRate(filename):
    prefDict = {}
    with open(filename, 'r', encoding='utf8') as fin:
        for line in fin:
            pref = line.split()[0]
            if pref in prefDict:
                prefDict[pref] += 1
            else:
                prefDict[pref] = 1
    return sorted(prefDict.items(), key=lambda x: x[1], reverse=True)

def main(): lines = appearanceRate('hightemp.txt') for k, _ in lines: print(k)
if __name__ == '__main__': main()

■ 辞書の操作と並び替え

今回も辞書と並び替えの問題ですね。 辞書のキーに1列目の文字列、辞書の値にその出現回数を記憶しています。 これを sortedでソートします。
key=lambda x: x[1]
出現回数を並び替えのキーにしています。

xを辞書と仮定すると、
x[0] : Key
x[1] : Value
でアクセスできます。


辞書にキーが存在するか

in キーワードを使って辞書にキーが存在するかを調べられます。

prefDict = {}
if pref in prefDict:
    …

逆順にソート


reverse引数にTrueを渡してやれば、逆順にソートできます。
return sorted(prefDict.items(), key=lambda x: x[1], reverse=True)
`x[1]`で、辞書に格納された要素(value)を取り出しています。

もちろんこの記法は、sortの時だけではなく、どこでも利用できます。dic[0]がキー、dic[1]で値を取り出せます。 for文でも以下のように使えます。
for k, v in dic:
    print(k, v)
実に面白いです。


■ 結果
山形県
埼玉県
群馬県
山梨県
愛知県
静岡県
千葉県
岐阜県
大阪府
和歌山県
愛媛県
高知県
  
Posted by gushwell at 21:55Comments(0)Python

2018年02月27日

言語処理100本ノックでPython入門 #18 - リストのソート


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

■ 問題

hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

18. 各行を3コラム目の数値の降順にソート
各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).

■ Pythonのコード
def sortBytemp(filename):
    with open(filename, 'r', encoding='utf8') as fin:
        lines = fin.readlines()
        #lines.sort(key=lambda x: float(x.split()[2]))
        #return lines
        return sorted(lines, key=lambda x: float(x.split()[2]))

def main(): lines = sortBytemp('hightemp.txt') for line in lines: print(line.rstrip())
if __name__ == '__main__': main()

■ リストの並び替え

いっぺんにメモリに読み込んでそれを並びかえることとします。 リストにsortメソッドが用意されています。調べてみると引数keyで比較キーを指定することができるみたいです。
lambdaキーワードを使って、以下のように書いてみたら、意図通りに並び替えてくれました。
lines.sort(key=lambda x: float(x.split()[2]))

C#風に書くと、
lines.OrderBy(s => double.Parse(s.split('\t')[2]));
みたいな感じですかね。

でも、このsortはリストそのものを並び替えてしまうんですね。
return lines.sort(key=lambda x: float(x.split()[2]))
って書いて、結果が表示されないので、あれっってなりました。

もし、元のリストの並びを保持したい場合には、
return sorted(lines, key=lambda x: float(x.split()[2]))
って書けば、新たなソート済みリストを生成してくれるみたいです。

■ 結果
千葉県	茂原	39.9	2013-08-11
埼玉県	鳩山	39.9	1997-07-05
大阪府	豊中	39.9	1994-08-08
山梨県	大月	39.9	1990-07-19
山形県	鶴岡	39.9	1978-08-03
愛知県	名古屋	39.9	1942-08-02
岐阜県	美濃	40	2007-08-16
群馬県	前橋	40	2001-07-24
山形県	酒田	40.1	1978-08-03
千葉県	牛久	40.2	2004-07-20
静岡県	佐久間	40.2	2001-07-24
愛媛県	宇和島	40.2	1927-07-22
群馬県	館林	40.3	2007-08-16
群馬県	上里見	40.3	1998-07-04
愛知県	愛西	40.3	1994-08-05
埼玉県	越谷	40.4	2007-08-16
山梨県	勝沼	40.5	2013-08-10
和歌山県	かつらぎ	40.6	1994-08-08
静岡県	天竜	40.6	1994-08-04
山梨県	甲府	40.7	2013-08-10
山形県	山形	40.8	1933-07-25
埼玉県	熊谷	40.9	2007-08-16
岐阜県	多治見	40.9	2007-08-16
高知県	江川崎	41	2013-08-12
  
Posted by gushwell at 22:35Comments(0)Python

2018年02月25日

言語処理100本ノックでPython入門 #17 - 集合


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

■ 問題
hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

17. 1列目の文字列の異なり
1列目の文字列の種類(異なる文字列の集合)を求めよ.確認にはsort, uniqコマンドを用いよ.

■ Pythonのコード
def gatherPrefecture(filename):
    collection = set([])
    with open(filename, 'r', encoding='utf8') as fin:
        for line in fin:
            pref = line.split()[0]
            collection.add(pref)
    return collection
def main(): prefs = gatherPrefecture('hightemp.txt') for name in prefs: print(name)
if __name__ == '__main__': main()


■ 今回のトピック

問題6でやったように、集合を扱うには、set()関数を使います。
s = set([1 ,2, 3])

このプログラムでは、
collection = set([])
で空のSetを作成しています。

addメソッドで要素を追加します。重複は勝手に除去してくれるので便利です。
collection.add(item)
今回は、それだけかな。
 
 
■ 結果
群馬県
埼玉県
和歌山県
高知県
岐阜県
千葉県
愛媛県
山梨県
大阪府
愛知県
山形県
静岡県
  
Posted by gushwell at 21:30Comments(0)Python

2018年02月22日

言語処理100本ノックでPython入門 #16 - リストをN分割


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

■問題


hightemp.txtは,日本の最高気温の記録を「都道府県」「地点」「℃」「日」のタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,hightemp.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.

16. ファイルをN分割する
自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.同様の処理をsplitコマンドで実現せよ.


■ 最初に書いたPythonのコード
import sys
import os.path
def split(filename, n): with open(filename, 'r', encoding='utf8') as fin: lines = fin.readlines() total = len(lines) div = [(total + i) // n for i in range(n)] start = 0 for i in range(n): end = start + div[i] with open(getFileName(filename, i+1), 'w', encoding='utf8') as fout: fout.writelines(lines[start:end]) start = end
def getFileName(filename, n): name, ext = os.path.splitext(filename) return name + str(n) + ext
def main(): n = int(sys.argv[1]) split('hightemp.txt', n)
if __name__ == '__main__': main()

■ 本日のトピック等

演算子 //

除算をもとめる演算子ですが、結果は小数点以下は切り捨てられます。


total個をn個に分割する方法
div = [(total + i) // n for i in range(n)]
誰が考えたのか、とてもすっきりと書けますね。
ネットで調べていたら見つけました。 多少効率悪いけどそれを補って余りある簡潔さです。
例えば、tital = 24, n = 5 の場合は、divは`[4, 5, 5, 5, 5]`となります。


ファイルパスを拡張子とそれ以外に分割する
import os.path
name, ext = os.path.splitext(filename)
extには、ピリオドを含んだ文字列が返ります。
name + ext = filenameです。


ブロック内の変数のスコープ

with内で宣言?した変数は、withの外でも使えるみたいです。
というか、withに限らず、for文やif文でも同じようです。

この辺りは、JavaScriptのvar変数みたいですね。C#とは明らかに違っています。Pythonはかなり緩いですね。どちらがいいんだろう。


■次に書いたPythonのコード

もし、数十万行ある巨大なファイルだったら?
昔の人間なので、全部メモリに入れるのは気が引けます。ということで省メモリバージョンも作成しました。
2度読みしないといけないのだけど、まあ仕方がないです。
import sys
import os.path

def getFileName(filename, n): name, ext = os.path.splitext(filename) return name + str(n) + ext
def countLine(filename): count = 0 with open(filename, 'r', encoding='utf8') as fin: for _ in fin: count += 1 return count
def split(filename, n): total = countLine(filename) div = [(total + i) // n for i in range(n)] with open(filename, 'r', encoding='utf8') as fin: i = 0 for m in div: i += 1 with open(getFileName(filename, i), 'w', encoding='utf8') as fout: for _ in range(m): line = fin.readline() fout.write(line)
def main(): n = int(sys.argv[1]) split('hightemp.txt', n)
if __name__ == '__main__': main()

■ 本日のトピック等(2)

インクリメント演算子が無い
i++
って書いたら、エラーになりました
i += 1
は、大丈夫みたいです。


■ 結果

3分割した場合の結果です。

hightemp1.txt
高知県	江川崎	41	2013-08-12
埼玉県	熊谷	40.9	2007-08-16
岐阜県	多治見	40.9	2007-08-16
山形県	山形	40.8	1933-07-25
山梨県	甲府	40.7	2013-08-10
和歌山県	かつらぎ	40.6	1994-08-08
静岡県	天竜	40.6	1994-08-04
山梨県	勝沼	40.5	2013-08-10

hightemp2.txt
埼玉県	越谷	40.4	2007-08-16
群馬県	館林	40.3	2007-08-16
群馬県	上里見	40.3	1998-07-04
愛知県	愛西	40.3	1994-08-05
千葉県	牛久	40.2	2004-07-20
静岡県	佐久間	40.2	2001-07-24
愛媛県	宇和島	40.2	1927-07-22
山形県	酒田	40.1	1978-08-03

hightemp3.txt
岐阜県	美濃	40	2007-08-16
群馬県	前橋	40	2001-07-24
千葉県	茂原	39.9	2013-08-11
埼玉県	鳩山	39.9	1997-07-05
大阪府	豊中	39.9	1994-08-08
山梨県	大月	39.9	1990-07-19
山形県	鶴岡	39.9	1978-08-03
愛知県	名古屋	39.9	1942-08-02
  
Posted by gushwell at 22:00Comments(0)Python