Elasticsearch で 文字の正規化を icu_normalizer でおこなう

# 概要
Elasticsearch の char_filter のひとつ，icu_normalizer の使い方メモです．
オプションの設定方法がよく分からなかったので調べてみました．

icu plugin は char_filter 以外にもいろいろ利用可能ですが，ここで扱うのは char_filter だけです．

# こんなかんじ

icu_normalizer を使うと，「㌶」→「ヘクタール」など，いい感じに変換してくれます．
※ 下の画面は，「Å」→「å 」「㌢」→「セ」「ン」「チ」，「①」→「1」，「⑵」→「(」「2」「)」，「Ⅲ」→「i」「i」「i」となっている例です．

![kopf.png](https://qiita-image-store.s3.amazonaws.com/0/44487/87fa2d47-bb8f-fc5c-e364-82e92a07bb03.png)

# インストール

github: https://github.com/elasticsearch/elasticsearch-analysis-icu
**バージョンごとにブランチが切られているので，それぞれのブランチの README.md を読んでください．**

Elasticsearch のバージョンにあわせてプラグインのバージョンを選んでインストールします．
下記は es が1.3の場合，

```
% bin/plugin -install elasticsearch/elasticsearch-analysis-icu/2.3.0
```

# オプションの設定方法

設定方法はこちら：[icu analysis plugin](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/analysis-icu-plugin.html)
ドキュメントによると，`name` と `mode` が設定できると書いてあります．

|オプション名|とりうる値|
|---|---|
|`name`|`nfc`, `nfkc`, `nfkc_cf`|
|`mode`|`compose`, `decompose`|

```json:設定例
{
    "index" : {
        "analysis" : {
            "analyzer" : {
                "normalize" : {
                    "tokenizer" : "standard",
                    "filter" : "my_icu_normalizer"
                }
            },
            "filter" : {
                "my_icu_normalizer" : {
                    "type" : "icu_normalizer",
                    "name" : "nfkc_cf",
                    "mode" : "compose"
                }
            }
        }
    }
}
```

# デフォルトのオプション

説明には書いてない(?)みたいだったので，コードを追ってみました．

```java:src/main/java/org/elasticsearch/index/analysis/IcuNormalizerCharFilterFactory.java
public IcuNormalizerCharFilterFactory(Index index, @IndexSettings Settings indexSettings, @Assisted String name, @Assisted Settings settings) {
        super(index, indexSettings, name);
        this.name = settings.get("name", "nfkc_cf");
        String mode = settings.get("mode");
        if (!"compose".equals(mode) && !"decompose".equals(mode)) {
            mode = "compose";
        }
        this.normalizer = Normalizer2.getInstance(
            null, this.name, "compose".equals(mode) ? Normalizer2.Mode.COMPOSE : Normalizer2.Mode.DECOMPOSE);
    }
```

|name|mode|
|---|---|
|`nfkc_cf`|`compose`|

がデフォルトのようです．

# 設定による動作の違い

## なんで２つのオプションの組み合わせなの？

Unicode の設定といえば NFC, NFD, NFKC, NFKD と思っていました．ちなみに，おおざっぱな意味は大体下記のような感じです．(いろいろ話が細かいので，詳細は文献をあたるなどしてください:bow:)．

|正規化の種類|意味|例|
|:---------|:--|:--|
|NFC|正規分解して合成|「か」+「゛」→「が」|
|NFD|正規分解して分解|「が」→「か」+「゛」|
|NFKC|互換分解して合成|NFCと大体同じ．ただし，「㌢」→「センチ」のように展開|
|NFKD|互換分解して分解|NFDと大体同じ．ただし，「㌢」→「センチ」のように展開|

正規分解と互換分解は正規化用のルールが違うことを意味しています．「①」→「1」のように正規化されるのは互換分解の方です．合成か分解かは，「が」のように「か」と「゛」のサロゲートペアでも表せる文字をどちらで表現するかを合わせてくれます．

というのが，大体の理解でしたが，`nfc`はあるけど，`nfd`がないのはナンデ？

答えはICUのAPI([`Normalizer2#getInstance(InputStream, String,Normalizer2.Mode)`](http://icu-project.org/apiref/icu4j/com/ibm/icu/text/Normalizer2.html))見たら分かりました．こう書いてあります．

>Returns a Normalizer2 instance which uses the specified data file (an ICU data file if data=null, or else custom binary data) and which composes or decomposes text according to the specified mode. Returns an unmodifiable singleton instance.
    * Use data=null for data files that are part of ICU's own data.
    * Use name="nfc" and COMPOSE/DECOMPOSE for Unicode standard NFC/NFD.
    * Use name="nfkc" and COMPOSE/DECOMPOSE for Unicode standard NFKC/NFKD.
    * Use name="nfkc_cf" and COMPOSE for Unicode standard NFKC_CF=NFKC_Casefold. 

ようするに，name と mode の組み合わせで，NFC, NFD, NFKC, NFKD を表現するという事みたいです．

|name|mode|正規化方法|
|:--|:--|:----|
|`nfc`|`compose`|NFC|
|`nfc`|`decompose`|NFD|
|`nfkc`|`compose`|NFKC|
|`nfkc`|`decompose`|NFKD|

## nfkc_cf とは何か

`nfkc_cf` は NFKC の大文字小文字統制もやってくれる版です．たとえば，「Å」なら「å 」にしてくれます．逆に言うと，nfkc では大文字小文字の統制はしない．

# 結論

**オプションはデフォルトで使っておけば問題なさそうです！** やり過ぎ感がある場合は NFC ぐらいにするのがいいのかもしれませんね．いろいろ調べてはみましたが，インデックス張るのに decompose しなきゃいけないシチュエーションも思いつかないので，確かに `nfc`, `nfck`, `nfck_cf` だけあればいい気がします．