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


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

###68. ソート
>"dance"というタグを付与されたアーティストの中でレーティングの投票数が多いアーティスト・トップ10を求めよ．

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

```python:main.py
# coding: utf-8
import json
import pymongo
from pymongo import MongoClient

# MongoDBのデータベースtestdbにコレクションartistにアクセス
client = MongoClient()
db = client.testdb
collection = db.artist

# 検索
results = collection.find({'tags.value': 'dance'})

# ソート
results.sort('rating.count', pymongo.DESCENDING)

# 10件表示
for doc in results.limit(10):

	if 'rating' in doc:
		rating = doc['rating']['count']
	else:
		rating = '(none)'		# ratingがないドキュメントもあるので

	print('{}(id:{})\t{}'.format(doc['name'], doc['id'], rating))
```

####実行結果：

```console:実行結果
Madonna(id:89)	26
Björk(id:1022)	23
The Prodigy(id:44954)	23
Rihanna(id:262731)	15
Britney Spears(id:791)	13
Maroon 5(id:66179)	11
Adam Lambert(id:627661)	7
Fatboy Slim(id:255)	7
Basement Jaxx(id:1060)	6
Cornershop(id:798)	5
```

###ソート
ソートは検索結果のCursorオブジェクトに対して[sort()](http://api.mongodb.com/python/3.3.0/api/pymongo/cursor.html#pymongo.cursor.Cursor.sort)（[最新](http://api.mongodb.com/python/current/api/pymongo/cursor.html#pymongo.cursor.Cursor.sort)）で実行します。そこから列挙すればソートされた結果が取得できる流れです。

###ソート対象のフィールドが存在しなかったり型が違ったりする場合の扱い
ソート対象のフィールドが存在しないドキュメントがある場合や、ドキュメント間でそのフィールドの型が異なる場合のルールは、[Comparison/Sort Order](https://docs.mongodb.com/manual/reference/bson-type-comparison-order/)で解説されています。

今回の問題ではrating.countが存在しないドキュメントがヒットしますが、その場合、[Non-existent Fields](https://docs.mongodb.com/manual/reference/bson-type-comparison-order/#non-existent-fields)の説明によるとnullと同じ扱いになり、nullはどの型よりも小さな値として扱われるようです。今回の問題は大きい順でソートしますので、rating.countが存在しないドキュメントは末尾に寄せられています。もし先頭に寄せられる仕様だった場合は、何か工夫しないといけないところでした。

###最初の10件を取り出す
検索結果のCursorオブジェクトに対して[limit()](http://api.mongodb.com/python/3.3.0/api/pymongo/cursor.html#pymongo.cursor.Cursor.limit)（[最新](http://api.mongodb.com/python/current/api/pymongo/cursor.html#pymongo.cursor.Cursor.limit)）で取り出せます。（@takatsugukatoさん、アドバイスありがとうございます。）
　
69本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<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)）です。*