数値ベクトルからハッシュ値を得る手法の性能比較

# はじめに
数値ベクトルからハッシュ値を得る方法は複数存在します。既存の手法がどの程度のパフォーマンスを示すのか気になったので調査してみました。
これから使用するハッシュ関数は以下のStack Overflowのページから引用しています。

https://stackoverflow.com/questions/20511347/a-good-hash-function-for-a-vector

## 評価方法
今回は性能の指標として、それぞれのハッシュ関数から出力される値をハッシュ値の衝突回数を調査しました。

こちらが、入力値を生成しハッシュ値の衝突をカウントするために使用したコードです。
```cpp

//hashに変更するvectorを生成
std::vector<std::vector<int>> make_vector()
{
    std::vector<std::vector<int>> vectors;
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            for (int k = 0; k < 100; k++)
            {
                vectors.push_back(std::vector<int>{i, j, k});
            }
        }
    }
    return vectors;
}

int main()
{
    const std::vector<std::vector<int>> vectors = make_vector();

    std::ofstream file;
    file.open("vector_hash.csv");

    int count = 0;
    std::unordered_set<int> hash_set;
    for (const std::vector<int> &vec : vectors)
    {
        const std::size_t hash = HASH(vec);
        file << hash << " ";
        if (hash_set.find(hash) != hash_set.end())
        {
            count++;
            continue;
        }

        hash_set.insert(hash);
    }

    file.close();
    std::cout << count << " 回衝突が起こりました " << std::endl;
    return 0;
}
```
大文字のHASHを実際のハッシュ関数に置き換えます。
### 入力データ
vector[I,J,K]のそれぞれの変数を0から99の間で変化させ、100万通りのvectorを生成し、入力データとします。
# 検証
## [Holkannのコード](https://stackoverflow.com/a/27216842) 
[Holkannのユーザーページ](https://stackoverflow.com/users/1609322/holkann)
```cpp:holkann
std::size_t holkann_hash(const std::vector<int> &vec)
{
    std::size_t seed = vec.size();
    for (auto &i : vec)
    {
        seed ^= i + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    return seed;
}
```

### 結果
```
593821回衝突が起こりました
```
ハッシュ値のヒストグラムはこのようになりました。

横軸はハッシュ値を表しています。
![Holkenn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/7d8b68ea-1599-d937-4f94-aa57fe70f3a9.png)





## [seeのコード](https://stackoverflow.com/a/72073933)
[ seeのユーザーページ ](https://stackoverflow.com/users/5302813/see)
```cpp:see
std::size_t see_hash(const std::vector<int> &vec)
{
    std::size_t seed = vec.size();
    for (auto x : vec)
    {
        x = ((x >> 16) ^ x) * 0x45d9f3b;
        x = ((x >> 16) ^ x) * 0x45d9f3b;
        x = (x >> 16) ^ x;
        seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    return seed;
}
 ```

調べてみたところ、Appleが公開しているコードに記載されている、`os_map_32_hash` という関数のようです。

[ソースコードへのリンク](https://opensource.apple.com/source/Libc/Libc-1439.141.1/collections/Source/collections_map.c)
 ### 結果
 ```
 106 回衝突が起こりました 
 ```
ハッシュ値のヒストグラムはこのようになりました。
![see.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/70a70009-3f02-3e2c-32aa-e3892fca1a13.png)




## [Wolfgang Brehmのコード](https://stackoverflow.com/a/68962790)
[Wolfgang Brahmのユーザーページ](https://stackoverflow.com/users/2119377/wolfgang-brehm)
```cpp:wolfgang
template <typename T>
T xorshift(const T &n, int i)
{
    return n ^ (n >> i);
}

uint32_t hash(const uint32_t &n)
{
    uint32_t p = 0x55555555ul; // pattern of alternating 0 and 1
    uint32_t c = 3423571495ul; // random uneven integer constant;
    return c * xorshift(p * xorshift(n, 16), 16);
}

template <typename T, typename S>
typename std::enable_if<std::is_unsigned<T>::value, T>::type constexpr rotl(const T n, const S i)
{
    const T m = (std::numeric_limits<T>::digits - 1);
    const T c = i & m;
    return (n << c) | (n >> ((T(0) - c) & m));
}

std::size_t wolf_hash(const std::vector<int> &vec)
{
    std::size_t ret = 0;
    for (auto &i : vec)
    {
        ret = rotl(ret, 11) ^ hash(i);
    }
    return ret;
}
```


### 結果
```
355430 回衝突が起こりました 
```
ハッシュ値のヒストグラムはこのようになりました。
![wolf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/a017a48b-c8db-3ff8-a11d-7daa131fc5f7.png)




## [Hemilのコード](https://stackoverflow.com/a/74750112)
[Hemilのユーザーページ](https://stackoverflow.com/users/8801881/hemil)
```cpp:hemil
std::size_t hemil_hash(const std::vector<int> &vec)
{
    std::hash<uint32_t> h;
    std::size_t ret = vec.size();
    for (auto &i : vec)
    {
        ret ^= h(i) | i;
    }
    return ret;
}
```
`std::hash`を用いた単純な方法のようです。
### 結果
```
999872 回衝突が起こりました 
```
ハッシュ値のヒストグラムはこのようになりました。
![hemil.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/53a1bb3e-0b0e-1f7d-c273-befdba19de8b.png)




かなりの回数で重複が起こっているようです、ハッシュ値もかなり狭い範囲に集中しています。


## boostのコード


https://live.boost.org/doc/libs/1_42_0/boost/functional/hash/hash.hpp

参考としてboost のハッシュ関数を使用してみました。
```cpp:boost_hash
std::size_t boost_hash(const std::vector<int> &vec)
{
    std::size_t hash = vec.size();
    for (const int e : vec)
    {
        hash ^= e + 0x9e3779b9 + (hash << 6) + (hash >> 2);
    }

    return hash;
}

```

### 結果

```
593821 回衝突が起こりました 
```

ハッシュ値のヒストグラムはこのようになりました。
![boost_hash_vecsize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/aad68ba8-ff45-b7ac-1cb6-bb3eda55c67e.png)


Holkann のコードにおけるハッシュの初期値を変更しただけだったので、似たような結果が得られました。

# 結果まとめ

各関数の衝突回数を棒グラフにまとめると以下のようになりました。

![棒グラフ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092001/9b2089ab-598c-c185-fe16-ee0b9ef5b0f5.png)



この棒グラフより、[seeのコード](#seeのコード)が圧倒的に衝突回数が少なく、関数自体が似ている[boost](#boostのコード)と[Holkann](#holkannのコード)は同等で[Hemil](#hemilのコード)は衝突回数が抜けて多いということがわかります。
ハッシュ値は32bitの整数であり、ハッシュ値の空間は2^32通り（約43億）あるはずです。これに対し入力データは100万通りであるため、ハッシュ値の空間に対して十分な余裕があるにもかかわらず、数十万回の衝突が起こっていたことがかなり驚きでした。

今回は数値ベクトルを入力として試しましたが、文字列などを入力とした場合、どのような結果が出るか検証してみても良いかもしれません。