Rustで自然言語処理100本ノック後半戦 第7章

## まずはじめに
後半も全部終わったら書こうと思っていたのですが, 分量が大変なことになりそうなのでここからは1-2章ずつくらい.

#### お題目
http://www.cl.ecei.tohoku.ac.jp/nlp100/

#### 前半戦
http://qiita.com/Yasu-umi/items/9d799e34c630c69fbd5c

#### 作業場所
https://github.com/Yasu-umi/rust_nlp_100_examples

## 前半の後始末
Vec\<T>を返していた関数をItarator\<Item=T>なtraitを持つstructにしたりしました.
ex: https://github.com/Yasu-umi/rust_nlp_100_examples/blob/master/src/structs.rs#L186
計測していませんが[049](https://github.com/Yasu-umi/rust_nlp_100_examples/blob/master/src/bin/049.rs)辺りでは体感でわかるくらい速度に差が出ています. 
一体何回collectしていたのか…
他にもこういう箇所はあるので順次直していきたいところです.
[cabocha-rs](https://github.com/Yasu-umi/cabocha-rs)の方も, Treeを受け取ってtokenやchunkのiteratorを作れるようちょっとコードを追加しました.

## 第7章前半: KVS
与えられたJSONの配列をKVSに突っ込んで検索とかしましょうというやつです.
ここではredisを使い[redis-rs](https://crates.io/crates/redis)にお世話になります.
大体のメソッドは

```rust:src/rust_utils.rs
let client = try!(redis::Client::open(url));
let connect = try!(client.get_connection());
```

で得られるconnectに生えていますが, DBをflushするコマンドはなかったので

```rust:src/rust_utils.rs
cmd("FLUSHDB").execute(connect);
```

としてやる必要があります.

valueを取得するgetでは, 返り値の型をFromRedisValueに許される範囲内でこちらから指定してやる必要があり

```rust:src/rust_utils.rs
pub fn get_value_by_key<K, V>(connect: &redis::Connection, key: K)
    -> Result<V, redis::RedisError>
    where K: ToRedisArgs, V: FromRedisValue {
    let res: V = try!(connect.get(key));
    Ok(res)
}
```

こういう感じの関数を

```rust:src/bin/061.rs
if let Ok(area) = redis_utils::get_value_by_key::<&String, String>(&connect, name) {
    println!("{}", area);
} else {
    println!("not found");
}
```

こんな感じで呼んでやって中身を確認しています.


> 62: KVS内の反復処理
> 60で構築したデータベースを用い,活動場所が「Japan」となっているアーティスト数を求めよ.

ではkeyではなくvalueを舐める必要があり、redisの文字列型にそういうコマンドはないようなので(要出典)

```rust:src/rust_utils.rs
pub fn get_values_iter<'a, K, V>(connect: &'a redis::Connection)
    -> Result<impl Iterator<Item=V> + 'a, redis::RedisError>
    where K: ToRedisArgs + FromRedisValue + 'a, V : FromRedisValue + 'a {
    let iter = try!(get_keys_iter::<K>(connect));
    Ok(iter.flat_map(move |key| get_value_by_key::<K, V>(connect, key)))
}
```

のように`ToRedisArgs + FromRedisValue`な型を`connection.scan()`に当ててやると, うまく回ります.


## 第7章後半: ドキュメント指向
前半がKVだったのに対して後半はjsonをそのまま全部突っ込んで検索しちゃいたいドキュメント指向DBを扱います.
ここではお題に従ってmongodbを使います.
ドライバはmongoの公式が作っている[mongodb](https://github.com/mongodb-labs/mongo-rust-driver-prototype)にお世話になります. (まさか公式が作ってくれているとは！

```rust:src/mongo_utils.rs
pub fn create_collection(config: &Config) -> Option<coll::Collection> {
    Client::connect(config.mongo_host.as_str(), 27017)
        .map(|client| client.db("test").collection("artist")).ok()
}
```

まずはDBに接続, DB名をcollection名を指定して[mongodb::coll::Collection](https://docs.rs/mongodb/0.3.0/mongodb/coll/struct.Collection.html)を取得します.

mongoのindex作成時には、indexを張る部分が空になる可能性がある場合, あらかじめ教えてやらないと, insert時にコケてしまいます.
なのでsparseなindexを作る関数なんかも用意しおきます. (デフォルト引数とかあったらnewのだけで済むんでしょうね

```rust:src/mongo_utils.rs
pub fn create_sparse_option() -> Option<coll::options::IndexOptions> {
    let mut option = coll::options::IndexOptions::new();
    option.sparse = Some(true);
    Some(option)
}
```

DBをdrop, inedexを作りいざinsertとなるわけですが, 1件ずつinsertしているととんでもない時間がかかってしまいます (レコード数は920000以上です.
そこで以下のような感じで1000件ずつ`insert_many`してやります.
bufferingの仕方に問題がある気がするのですがもっと良い実装あれば是非教えてください.

```rust:src/mongo_utils.rs
pub fn insert_artists(collection: &coll::Collection, artists: Vec<Artist>) -> Option<()> {
    let artists_len = artists.len();
    let mut buffer_vec = Vec::new();
    for (i, artist) in artists.iter().enumerate() {
        if let Ok(Bson::Document(document)) = to_bson(&artist) {
            buffer_vec.push(document);
        }
        if i % 1000 == 999 || i == artists_len - 1 {
            if let Ok(_) = collection.insert_many(buffer_vec, None) {
                buffer_vec = Vec::new();
            } else {
                return None;
            }
        }
    }
    Some(())
}
```

そして, これだと本当に全件ちゃんと入っているのかかなり怪しいので,

```rust:src/bin/064.rs
let count = collection.count(Some(OrderedDocument::new()), None)
    .ok().expect("Failed to execute count.");
println!("insert {} / {}", count, artists_len);
```

あらかじめ入れるレコード数を確認しておき実際に入った数と比較をしてdoneとなります.
何をするにも`#[macro_use(doc, bson)]extern crate bson;`が必要になってしまう点以外は特に問題なくドンドン進みます.

## 7章最後の問題: Webアプリケーション
> 69: Webアプリケーションの作成
> ユーザから入力された検索条件に合致するアーティストの情報を表示するWebアプリケーションを作成せよ.アーティスト名,アーティストの別名,タグ等で検索条件を指定し,アーティスト情報のリストをレーティングの高い順などで整列して表示せよ.

Webアプリケーション！Webアプリケーションです.
rustにはいくつかwebフレームワークがあります.ここでは[nickel](https://github.com/nickel-org/nickel.rs)と[iron](https://github.com/iron/iron)どちらにするか迷い、ひとまず最低限の機能だけがある様子のironを使ってみることにしました.

何はともあれhello worldです

```rust:src/bin/069.rs
let mut router = Router::new();
router.get("/", |_: &mut Request| {
    Ok(Response::with((status::Ok, "Hello world!")))
}, "hello_world");

Iron::new(router).http(iron_host.as_str())
    .expect("Failed to create server");
```

チョー簡単です. 素晴しい.

nameを取ってそれにマッチするartistの情報を全部返せとのことなので

```rust:src/bin/069.rs
router.get("/artist/:name", move |req: &mut Request| {
    if let Some(name) = req.extensions.get::<Router>().map(|query| query.find("name")).unwrap_or(None) {
        if let Ok(artists) = artists_by_name(&config, name) {
            if let Ok(body) = serde_json::to_string(&artists) {
                return Ok(Response::with((ContentType::json().0, status::Ok, body)));
            }
        }
    }
    Ok(Response::with(status::InternalServerError))
}, "artist_name");
```

こんな感じのルーティングで.

## そしてさいごに

7章はredis, mongo, webアプリとかなり盛り沢山でした. もうこれで簡単なwebアプリケーションくらいなら, rustでもささっと書けるんじゃないでしょうか…
今まで気づいていなかったのですが, [crates.io](https://crates.io/)に登録すると,
 何もしなくても[docs.rs](https://docs.rs/)でドキュメントが見れるようになるんですね.
もちろんソースを眺めるのが一番ですが, 手っ取り早くライブラリを使うために入出力を見る分にはdocsがあるのが大変助かりました.
次章はついに8章機械学習です.
可能な限りライブラリを使わず手実装でやっていきたいと思います.