言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。
第7章: データベース
artist.json.gzは,オープンな音楽データベースMusicBrainzの中で,アーティストに関するものをJSON形式に変換し,gzip形式で圧縮したファイルである.このファイルには,1アーティストに関する情報が1行にJSON形式で格納されている.JSON形式の概要は以下の通りである.
フィールド 型 内容 例 id ユニーク識別子 整数 20660 gid グローバル識別子 文字列 "ecf9f3a3-35e9-4c58-acaa-e707fba45060" name アーティスト名 文字列 "Oasis" sort_name アーティスト名(辞書順整列用) 文字列 "Oasis" area 活動場所 文字列 "United Kingdom" aliases 別名 辞書オブジェクトのリスト aliases[].name 別名 文字列 "オアシス" aliases[].sort_name 別名(整列用) 文字列 "オアシス" begin 活動開始日 辞書 begin.year 活動開始年 整数 1991 begin.month 活動開始月 整数 begin.date 活動開始日 整数 end 活動終了日 辞書 end.year 活動終了年 整数 2009 end.month 活動終了月 整数 8 end.date 活動終了日 整数 28 tags タグ 辞書オブジェクトのリスト tags[].count タグ付けされた回数 整数 1 tags[].value タグ内容 文字列 "rock" rating レーティング 辞書オブジェクト rating.count レーティングの投票数 整数 13 rating.value レーティングの値(平均値) 整数 86 artist.json.gzのデータをKey-Value-Store (KVS) およびドキュメント志向型データベースに格納・検索することを考える.KVSとしては,LevelDB,Redis,KyotoCabinet等を用いよ.ドキュメント志向型データベースとして,MongoDBを採用したが,CouchDBやRethinkDB等を用いてもよい.
###63. オブジェクトを値に格納したKVS
KVSを用い,アーティスト名(name)からタグと被タグ数(タグ付けされた回数)のリストを検索するためのデータベースを構築せよ.さらに,ここで構築したデータベースを用い,アーティスト名からタグと被タグ数を検索せよ.
####出来上がったコード:
# coding: utf-8
import gzip
import json
import re
import leveldb
fname = 'artist.json.gz'
fname_db = 'test_db'
# keyをnameとidに分解するための正規表現
pattern = re.compile(r'''
^
(.*) # name
\t # 区切り
(\d+) # id
$
''', re.VERBOSE + re.DOTALL)
# LevelDBオープン、ない時だけ作成
try:
db = leveldb.LevelDB(fname_db, error_if_exists=True)
# gzファイル読み込み、パース
with gzip.open(fname, 'rt') as data_file:
for line in data_file:
data_json = json.loads(line)
# name+idとtagsをDBへ追加
key = data_json['name'] + '\t' + str(data_json['id'])
value = data_json.get('tags') # tagsはないことがあるのでチェック
if value is None:
value = []
db.Put(key.encode(), json.dumps(value).encode())
# 確認のため登録件数を表示
print('{}件登録しました。'.format(len(list(db.RangeIter(include_value=False)))))
except:
db = leveldb.LevelDB(fname_db)
print('既存のDBを使います。')
# 条件入力
clue = input('アーティスト名を入力してください--> ')
hit = False
# アーティスト名+'\t'で検索
for key, value in db.RangeIter(key_from=(clue + '\t').encode()):
# keyをnameとidに戻す
match = pattern.match(key.decode())
name = match.group(1)
id = match.group(2)
# 異なるアーティストになったら終了
if name != clue:
break
# タグ情報取得
tags = json.loads(value.decode())
print('{}(id:{})のタグ情報:'.format(name, id))
if len(tags) > 0:
for tag in tags:
print('\t{}({})'.format(tag['value'], tag['count']))
else:
print('\tタグはありません')
hit = True
if not hit:
print('{}は登録されていません'.format(clue))
####実行結果:
例になっているOasisはこんな感じでした。
921337件登録しました。
アーティスト名を入力してください--> Oasis
Oasis(id:20660)のタグ情報:
rock(1)
britpop(3)
british(4)
uk(1)
britannique(1)
rock and indie(1)
england(1)
manchester(1)
Oasis(id:286198)のタグ情報:
タグはありません
Oasis(id:377879)のタグ情報:
morning glory(1)
oasis(1)
なお、DBの登録は手元の環境だと1分弱待たされるので、DBのファイルがある場合は登録処理をスキップするようにしました。スキップした場合は、その旨のメッセージを表示します。
既存のDBを使います。
アーティスト名を入力してください--> T‐SQUARE
T‐SQUARE(id:9707)のタグ情報:
fusion(1)
アーティストが登録されていない場合は、次のようになります。
既存のDBを使います。
アーティスト名を入力してください--> segavvy
segavvyは登録されていません
###オブジェクトの格納
この問題では、タグと被タグ数をvalueに格納する必要があります。今回はjson形式の文字列にして格納してみました。
json形式からの変換は問題20以降ちょくちょく出てきましたが、json形式へ変換するのは今回初めてかもしれません。これまでjson形式の読み込みで使ってきたjson.loads()
の逆に相当するのがjson.dumps()
です。今回はこれを使ってタグと被タグ数の情報をvalueに格納しました。
jsonの処理についての詳細はjson — JSON エンコーダおよびデコーダなどを参照してください。
64本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第7章で用いているデータのライセンスはクリエイティブ・コモンズ 表示 - 非営利 - 継承 3.0 非移植(日本語訳)です。