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


[言語処理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等を用いてもよい．

###61. KVSの検索
>60で構築したデータベースを用い，特定の（指定された）アーティストの活動場所を取得せよ．

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

```python:main.py
# coding: utf-8
import re
import leveldb

fname_db = 'test_db'

# keyをnameとidに分解するための正規表現
pattern = re.compile(r'''
    ^
    (.*)	# name
    \t      # 区切り
    (\d+)   # id
    $
    ''', re.VERBOSE + re.DOTALL)

# LevelDBオープン
db = leveldb.LevelDB(fname_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

	# 活動場所のチェック、表示
	area = value.decode()
	if area != '':
		print('{}(id:{})の活動場所:{}'.format(name, id, area))
	else:
		print('{}(id:{})の活動場所は登録されていません'.format(name, id))
	hit = True

if not hit:
	print('{}は登録されていません'.format(clue))
```

####実行結果：

```console:実行結果
アーティスト名を入力してください--> Oasis
Oasis(id:20660)の活動場所:United Kingdom
Oasis(id:286198)の活動場所:United States
Oasis(id:377879)の活動場所:United Kingdom
```

例になっているOasisは3件該当しました。

```console:実行結果
アーティスト名を入力してください--> SMAP
SMAP(id:265728)の活動場所:Japan
```

このデータベースでは、SMAPはまだ日本で活動中です。

アーティストとして登録されていても、活動場所の情報がない場合があります。

```console:実行結果
アーティスト名を入力してください--> 比嘉真優子
比嘉真優子(id:1075206)の活動場所は登録されていません
```

比嘉真優子さんは、活動場所が登録されていないアーティストの一覧を眺めていて、たまたま漢字で目立ったので例に使わせていただきました。沖縄出身の方のようです。

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

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

###LevelDBの検索

keyで探すだけであれば、`LevelDB.Get()`でkeyを指定すればvalueが返ってきて終わりなのですが、[前問](http://qiita.com/segavvy/items/14216054ef9becee7b3b)で作ったデータベースは、アーティスト名の重複に対応するため、keyをアーティスト名＋'\t'＋ユニーク識別子にしています。ユニーク識別子は事前には分からないので、イテレータを取得してチェックする形にしました。

イテレータの取得は`LevelDB.RangeIter()`で行いますが、普通に全件取得してチェックしていたらKVSを使うメリットがなくなってしまうため、LevelDBのkeyが常にソートされていることを利用しています。
`LevelDB.RangeIter()`はkey_fromでイテレータの開始条件を指定できるので、探したいアーティスト名＋'\t'を指定してそれ以降だけをチェックすれば、ユニーク識別子がなんであっても該当アーティストのvalueが直接取得できます。
なお、key_toで終了条件も指定できるのですが、LevelDBのソートロジックが明確には分からず指定方法がうまく思いつかなかったので、今回は指定していません。その代わりに、アーティスト名が目的のものではなくなったらbreakするようにしました。

###表記ゆれ、Unicodeコードポイント

余談ですが、私が好きなフュージョンバンドのT-SQUAREがなぜか登録されていません。

```console:実行結果
アーティスト名を入力してください--> T-SQUARE
T-SQUAREは登録されていません
```

不思議に思いちょっとデータを覗いたところ、`-`（ハイフン）がいつもの文字（Unicodeコードポイント：45）ではなく別の文字（同：8208）になっていました。その文字で入力すればヒットします。

```console:実行結果
アーティスト名を入力してください--> T‐SQUARE
T‐SQUARE(id:9707)の活動場所:Japan
```

こういった表記のゆれは検索漏れの原因になってしまうので、検索の仕組みを作ろうとした場合、なかなか頭の痛いテーマではないかと思います。これまでの問題でも、形態素解析して原型にして扱うとかステミングするとか、いろいろな手法を学んできましたが、他にも大小文字や全半角、異体字や新旧漢字、漢字なのか開く（＝ひらがなにする）のか、フルスペルなのか略すのかとか、いろいろありそうです。

ちなみにUnicodeコードポイントは、Pythonでは[`ord()`](http://docs.python.jp/3/library/functions.html#ord)で調べられます。

Unicodeコードポイントって何？という方は、ギークを目指してさんの[文字コードの考え方から理解するUnicodeとUTF-8の違い](http://equj65.net/tech/charcode/)の解説が分かりやすいかと思います。

　
62本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<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)）です。*