web-dev-qa-db-ja.com

迅速でシンプルなハッシュコードの組み合わせ

2つのオブジェクトのハッシュコードを組み合わせるための迅速で簡単な方法を人々が推奨できますか。効率的に処理するハッシュテーブルがあるので、衝突をあまり心配していません。可能な限り迅速にコードを生成するものを必要としています。

SOとウェブの周りを読んでいると、いくつかの主要な候補者がいるようです:

  1. XORing
  2. 素数乗算によるXOR
  3. 乗算/除算のような単純な数値演算(オーバーフローチェックまたはラップアラウンドあり)
  4. 文字列を作成してから文字列クラスのハッシュコードメソッドを使用する

人々は何を推奨し、その理由は何ですか?

52
RobV

私は個人的に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、それがなぜうまく機能するのかは誰にもわかりませんが、実際にはそうです。また、覚えやすく、実装しやすく、任意の数のフィールドに拡張するのも簡単です。

107
Jon Skeet

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%の範囲の衝突率を持っているようであり、私の意見では使用すべきではありません。

41
Special Sauce

.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();
}

長所:

  • .NET自体の一部(ただし、以下のconを参照)
  • 著者とレビュアーが以前に行った作業に基づいて、良好なパフォーマンスとミキシング特性を持っているように見えます これをcorefxリポジトリにマージします
  • ヌルを自動的に処理します
  • IEqualityComparer インスタンスを受け取るオーバーロード

短所:

  • 2018年8月現在、.NET Core 2.1以降を対象とする場合にのみ使用できます。
    • 2019年4月現在、.NET Standard 2.1 Previewの一部です。 .NET Standard 2.1 Previewがリリースされる時期はわかりません。また、HashCodeがその一部になるかどうかもわかりません。
  • 汎用なので、超特殊なケースや手作りのコードは処理できません
25
chwarr

私は、.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;
}
16
Stipo

タプルの組み合わせロジックを使用します。この例では、c#7タプルを使用しています。

(field1, field2).GetHashCode();
7
Yepeekai

速度を求めており、衝突が多すぎない場合は、XORが最も高速です。ゼロ付近のクラスタリングを防ぐには、次のようにします。

finalHash = hash1 ^ hash2;
return finalHash != 0 ? finalHash : hash1;

もちろん、いくつかのプロトタイピングは、パフォーマンスとクラスタリングについてのアイデアを与えるはずです。

0
Ed Power

関連するtoString()関数(さまざまなフィールドが表示される場所)があると仮定すると、そのハッシュコードを返すだけです。

this.toString().hashCode();

これはそれほど高速ではありませんが、衝突をかなり回避するはずです。

0
Thomas Hugel

入力ハッシュが同じサイズで、均等に分散され、互いに関連がない場合、XORで問題ありません。さらに、高速です。

私がこれを提案している状況はあなたがしたいところです

H = hash(A) ^ hash(B); // A and B are different types, so there's no way A == B.

もちろん、AとBが妥当な(無視できない)確率で同じ値にハッシュされることが期待できる場合は、このようにXOR=を使用しないでください。

0
geofftnz