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


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第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）からタグと被タグ数（タグ付けされた回数）のリストを検索するためのデータベースを構築せよ．さらに，ここで構築したデータベースを用い，アーティスト名からタグと被タグ数を検索せよ．

####出来上がったコード：

```python: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はこんな感じでした。

```console:実行結果
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のファイルがある場合は登録処理をスキップするようにしました。スキップした場合は、その旨のメッセージを表示します。

```console:実行結果
既存のDBを使います。
アーティスト名を入力してください--> T‐SQUARE
T‐SQUARE(id:9707)のタグ情報:
	fusion(1)
```

アーティストが登録されていない場合は、次のようになります。

```console:実行結果
既存のDBを使います。
アーティスト名を入力してください--> segavvy
segavvyは登録されていません
```

###オブジェクトの格納

この問題では、タグと被タグ数をvalueに格納する必要があります。今回はjson形式の文字列にして格納してみました。

json形式からの変換は[問題20](http://qiita.com/segavvy/items/dc1e63fd8f7bd5d99eea)以降ちょくちょく出てきましたが、json形式へ変換するのは今回初めてかもしれません。これまでjson形式の読み込みで使ってきた[`json.loads()`](http://docs.python.jp/3/library/json.html#json.loads)の逆に相当するのが[`json.dumps()`](http://docs.python.jp/3/library/json.html#json.dumps)です。今回はこれを使ってタグと被タグ数の情報をvalueに格納しました。

jsonの処理についての詳細は[json — JSON エンコーダおよびデコーダ](http://docs.python.jp/3/library/json.html#module-json)などを参照してください。

　
64本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第7章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示 - 非営利 - 継承 3.0 非移植](https://creativecommons.org/licenses/by-nc-sa/3.0/)（[日本語訳](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.ja)）です。*