Marc と Jon が指摘したファンダメンタルズは悪くありませんが、結果の分布の均一性という点では最適とはほど遠いものです。悲しいことに、Knuth から非常に多くの人々によってコピーされた「素数による乗算」アプローチは、多くの場合、最良の選択ではありません。多くの場合、より安価に関数を計算することでより良い分散を実現できます (ただし、これは最新のハードウェアでは非常にわずかです)。実際、ハッシュの多くの側面に素数を投入することは万能薬ではありません。
このデータがかなりのサイズのハッシュ テーブルに使用される場合は、 Bret Mulvey の優れた研究と、C# で簡単に実行できるさまざまな最新の (そしてそれほど最新ではない) ハッシュ手法の説明を読むことをお勧めします。
さまざまなハッシュ関数の文字列の動作は、文字列が短い (大まかに言えば、ビットがオーバーフローし始める前にハッシュされる文字数) か長いかに大きく偏っていることに注意してください。
Jenkins One at a time ハッシュは、実装が最もシンプルで簡単なものの 1 つであり、最高のものの 1 つでもあります。
private static unsafe void Hash(byte* d, int len, ref uint h)
{
for (int i = 0; i < len; i++)
{
h += d[i];
h += (h << 10);
h ^= (h >> 6);
}
}
public unsafe static void Hash(ref uint h, string s)
{
fixed (char* c = s)
{
byte* b = (byte*)(void*)c;
Hash(b, s.Length * 2, ref h);
}
}
public unsafe static int Avalanche(uint h)
{
h += (h<< 3);
h ^= (h>> 11);
h += (h<< 15);
return *((int*)(void*)&h);
}
これを次のように使用できます。
uint h = 0;
foreach(string item in collection)
{
Hash(ref h, item);
}
return Avalanche(h);
次のように、複数の異なるタイプをマージできます。
public unsafe static void Hash(ref uint h, int data)
{
byte* d = (byte*)(void*)&data;
AddToHash(d, sizeof(int), ref h);
}
public unsafe static void Hash(ref uint h, long data)
{
byte* d= (byte*)(void*)&data;
Hash(d, sizeof(long), ref h);
}
内部の知識がなく、オブジェクトとしてフィールドにしかアクセスできない場合は、単純にそれぞれに対して GetHashCode() を呼び出して、その値を次のように組み合わせることができます。
uint h = 0;
foreach(var item in collection)
{
Hash(ref h, item.GetHashCode());
}
return Avalanche(h);
残念ながら、 sizeof(T) を実行できないため、各構造体を個別に実行する必要があります。
リフレクションを使用したい場合は、すべてのフィールドで構造的同一性とハッシュを行う関数を型ごとに構築できます。
安全でないコードを回避したい場合は、ビット マスキング手法を使用して、それほど手間をかけずに int (および文字列を処理する場合は char) から個々のビットを引き出すことができます。