Edited at

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

More than 1 year has passed since last update.

言語処理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)からタグと被タグ数(タグ付けされた回数)のリストを検索するためのデータベースを構築せよ.さらに,ここで構築したデータベースを用い,アーティスト名からタグと被タグ数を検索せよ.



出来上がったコード:


main.py

# 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 非移植日本語訳)です。