2つのオブジェクトのハッシュコードを組み合わせるための迅速で簡単な方法を人々が推奨できますか。効率的に処理するハッシュテーブルがあるので、衝突をあまり心配していません。可能な限り迅速にコードを生成するものを必要としています。
SOとウェブの周りを読んでいると、いくつかの主要な候補者がいるようです:
人々は何を推奨し、その理由は何ですか?
私は個人的にXORを回避します。これは、2つの等しい値が0になることを意味します-つまり、hash(1、1)== hash(2、2)== hash(3、3) etc.また、hash(5、0)== hash(0、5)など、時々発生する可能性があります。Ihave設定されたハッシュに意図的に使用しました-一連のアイテムをハッシュしたい場合、あなたしないでください順序に注意してください、それは素晴らしいです。
私は通常使用します:
unchecked
{
int hash = 17;
hash = hash * 31 + firstField.GetHashCode();
hash = hash * 31 + secondField.GetHashCode();
return hash;
}
これは、Josh BlochがEffective Javaで提案している形式です。前回私が同様の質問に答えたとき、これが詳細に議論された記事を見つけることができました-IIRC、それがなぜうまく機能するのかは誰にもわかりませんが、実際にはそうです。また、覚えやすく、実装しやすく、任意の数のフィールドに拡張するのも簡単です。
Jon Skeetの回答で概説されているテンプレートは一般的にハッシュ関数ファミリーとしてうまく機能しますが、定数の選択は重要であり、回答に記載されている_17
_と_31
_のシードは機能しません一般的なユースケースではまったく問題ありません。ほとんどの使用例では、ハッシュ値は_int.MaxValue
_よりもゼロにはるかに近く、共同でハッシュされるアイテムの数は数十以下です。
整数タプル_{x, y}
_をハッシュする場合、_-1000 <= x <= 1000
_および_-1000 <= y <= 1000
_の場合、異常な衝突率は約98.5%です。たとえば、_{1, 0} -> {0, 31}
_、_{1, 1} -> {0, 32}
_などです。カバレッジを拡張して、_3 <= n <= 25
_であるnタプルも含めると、約38%の衝突率でそれほどひどくなりません。しかし、私たちはもっと良いことができます。
_public static int CustomHash(int seed, int factor, params int[] vals)
{
int hash = seed;
foreach (int i in vals)
{
hash = (hash * factor) + i;
}
return hash;
}
_
ランダムな整数i
のさまざまなランダムnタプルに対してシードと因子のさまざまな値を使用して上記の方法をテストするモンテカルロサンプリング検索ループを作成しました。許容範囲は_2 <= n <= 25
_(n
はランダムですが範囲の下限に偏っています)と_-1000 <= i <= 1000
_です。シードと因子のペアごとに、少なくとも1200万回の固有の衝突テストが実行されました。
約7時間実行した後、検出された最良のペア(シードと係数の両方が4桁以下に制限されていた)は_seed = 1009
_、_factor = 9176
_で、衝突率は0.1131%でした。 5桁および6桁の領域では、さらに優れたオプションがあります。しかし、簡潔にするために上位4桁のパフォーマーを選択しました。これは、すべての一般的なint
およびchar
ハッシュシナリオで非常によく機能します。また、より大きな等級の整数でも問題なく動作するようです。
「プライムであること」は、シードやファクターとして優れたパフォーマンスを発揮するための一般的な前提条件ではなかったように思われることは注目に値しますが、役立つかもしれません。上記の_1009
_は実際には素数ですが、_9176
_は素数ではありません。 factor
を_9176
_の近くのさまざまな素数に変更し(_seed = 1009
_を残しながら)、これに関するバリエーションを明示的にテストしましたが、すべて上記のソリューションよりもパフォーマンスが低下しました。
最後に、汎用のReSharper推奨関数ファミリーであるhash = (hash * factor) ^ i;
および元のCustomHash()
と比較したところ、上記のように非常に優れています。 ReSharper XORスタイルは、一般的なユースケースの仮定で20〜30%の範囲の衝突率を持っているようであり、私の意見では使用すべきではありません。
.NETCore 2.1を使用している場合は、複合ハッシュコードの生成に役立つ System.HashCode 構造体の使用を検討してください。追加と結合の2つの操作モードがあります。
Combine
を使用した例。これは通常より簡単で、最大8つの項目に対して機能します。
public override int GetHashCode()
{
return HashCode.Combine(object1, object2);
}
Add
の使用例:
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(this.object1);
hash.Add(this.object2);
return hash.ToHashCode();
}
長所:
IEqualityComparer
インスタンスを受け取るオーバーロード短所:
HashCode
がその一部になるかどうかもわかりません。私は、.NET Frameworkチームが System.String.GetHashCode() 実装のテストで適切な作業を行ったと想定しているので、それを使用します。
// System.String.GetHashCode(): http://referencesource.Microsoft.com/#mscorlib/system/string.cs,0a17bbac4851d0d4
// System.Web.Util.StringUtil.GetStringHashCode(System.String): http://referencesource.Microsoft.com/#System.Web/Util/StringUtil.cs,c97063570b4e791a
public static int CombineHashCodes(IEnumerable<int> hashCodes)
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
int i = 0;
foreach (var hashCode in hashCodes)
{
if (i % 2 == 0)
hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ hashCode;
else
hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ hashCode;
++i;
}
return hash1 + (hash2 * 1566083941);
}
別の実装は System.Web.Util.HashCodeCombiner.CombineHashCodes(System.Int32、System.Int32) および System.Array.CombineHashCodes(System.Int32、System.Int32) からです。 =メソッド。これはもっと簡単ですが、おそらく上記の方法ほど良い分布はありません:
// System.Web.Util.HashCodeCombiner.CombineHashCodes(System.Int32, System.Int32): http://referencesource.Microsoft.com/#System.Web/Util/HashCodeCombiner.cs,21fb74ad8bb43f6b
// System.Array.CombineHashCodes(System.Int32, System.Int32): http://referencesource.Microsoft.com/#mscorlib/system/array.cs,87d117c8cc772cca
public static int CombineHashCodes(IEnumerable<int> hashCodes)
{
int hash = 5381;
foreach (var hashCode in hashCodes)
hash = ((hash << 5) + hash) ^ hashCode;
return hash;
}
タプルの組み合わせロジックを使用します。この例では、c#7タプルを使用しています。
(field1, field2).GetHashCode();
速度を求めており、衝突が多すぎない場合は、XORが最も高速です。ゼロ付近のクラスタリングを防ぐには、次のようにします。
finalHash = hash1 ^ hash2;
return finalHash != 0 ? finalHash : hash1;
もちろん、いくつかのプロトタイピングは、パフォーマンスとクラスタリングについてのアイデアを与えるはずです。
関連するtoString()関数(さまざまなフィールドが表示される場所)があると仮定すると、そのハッシュコードを返すだけです。
this.toString().hashCode();
これはそれほど高速ではありませんが、衝突をかなり回避するはずです。
入力ハッシュが同じサイズで、均等に分散され、互いに関連がない場合、XORで問題ありません。さらに、高速です。
私がこれを提案している状況はあなたがしたいところです
H = hash(A) ^ hash(B); // A and B are different types, so there's no way A == B.
もちろん、AとBが妥当な(無視できない)確率で同じ値にハッシュされることが期待できる場合は、このようにXOR=を使用しないでください。