GetHashCodeをオーバーライドするための最適なアルゴリズムは何ですか?


1449

.NETでは、このGetHashCodeメソッドは.NET基本クラスライブラリ全体の多くの場所で使用されます。適切に実装することは、コレクション内でアイテムをすばやく見つけるか、同等かどうかを判断するときに特に重要です。

GetHashCodeパフォーマンスを低下させないようにカスタムクラスを実装する方法について、標準的なアルゴリズムまたはベストプラクティスはありますか?


38
この質問と以下の記事を読んだ後、のオーバーライドを実装できますGetHashCode。他の方にもお役に立てれば幸いです。Eric Lippertによって作成されたGetHashCodeのガイドラインとルール
rene

4
「または平等を決定する」:いいえ!同じハッシュコードを持つ2つのオブジェクトは必ずしも同じではありません。
Thomas Levesque

1
@ThomasLevesqueそうです、同じハッシュコードを持つ2つのオブジェクトは必ずしも同じではありません。しかし、それでものGetHashCode()非常に多くの実装で使用されていEquals()ます。それが私がその声明で意味したことです。 2つのオブジェクトが異なるハッシュコードを持っている場合、それらは等しくないオブジェクトである必要があり、残りの等式チェックを実行する必要がないため、GetHashCode()inside Equals()はしばしば不等式を決定するショートカットとして使用されます。
bitbonk

3
@bitbonk通常、両方ともGetHashCode()Equals()両方のオブジェクトのすべてのフィールドを調べる必要があります(ハッシュコードが等しいかチェックされていない場合、Equalsはこれを行う必要があります)。このため、GetHashCode()内部への呼び出しEquals()は冗長であることが多く、パフォーマンスが低下する可能性があります。Equals()短絡させることもできるので、はるかに高速になります。ただし、ハッシュコードがキャッシュされ、GetHashCode()チェックが高速になり、価値がある場合もあります。詳細については、この質問を参照してください。
NotEnoughData 2017

2020年
Rick Davin

回答:


1604

私は通常、Josh Blochのすばらしい 効果的なJavaで提供される実装のようなものを使います。それは高速で、衝突を起こしそうにないかなり良いハッシュを作成します。2つの異なる素数、たとえば17と23を選び、次のようにします。

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

コメントに記載されているように、大きい素数を選択して乗算する方が良い場合があります。どうやら486187739は良いです...そして、私が小さい数で見たほとんどの例は素数を使用する傾向がありますが、非素数がよく使用される少なくとも類似のアルゴリズムがあります。たとえば、後ほど重要なFNVの例では、明らかにうまく機能する数値を使用しましたが、初期値は素数ではありません。(ただし、乗算定数素数です。それがどれほど重要であるかはよくわかりません。)

これは、XOR主に2つの理由でハッシュコードを使用する一般的な方法よりも優れています。2つのintフィールドを持つ型があるとします。

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

ちなみに、以前のアルゴリズムは、C#コンパイラが匿名型に現在使用しているものです。

このページにはかなりの数のオプションがあります。ほとんどの場合、上記は「十分」であり、覚えて正しく理解するのは非常に簡単です。FNVの代替も同様に簡単ですが、別の定数を使用し、XORADD、結合操作としてではなく、します。それは見えます、何か以下のようなコードを、これは代わりに、32ビットのハッシュ値ごとの、バイトごとに一回の反復を実行するように変更が必要となるので、通常のFNVアルゴリズムは、個々のバイトで動作します。FNVは可変長のデータ用にも設計されていますが、ここで使用する方法は常に同じ数のフィールド値に対してです。この回答へのコメントは、ここでのコードが実際には(テストされたサンプルケースでは)上記の追加アプローチほどうまく機能しないことを示唆しています。

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

注意すべき点の1つは、ハッシュコードに依存するコレクションに追加した後に、等価性に敏感な(つまりハッシュコードに敏感な)状態が変化しないようにするのが理想的であるということです。

ドキュメントに従って:

不変の参照型のGetHashCodeをオーバーライドできます。一般に、可変参照型の場合、次の場合にのみGetHashCodeをオーバーライドする必要があります。

  • 変更できないフィールドからハッシュコードを計算できます。または
  • オブジェクトがハッシュコードに依存するコレクションに含まれている間、可変オブジェクトのハッシュコードが変更されないようにすることができます。

8
あなたが言及する本で説明されているアルゴリズムは、実際には少し詳細であり、特にフィールドのさまざまなデータ型に対して何をすべきかを説明しています。例:タイプが長いフィールド(int)(field ^ f >>> 32)の場合、単にGetHashcodeを呼び出すのではありません。long.GetHashCodesはそのように実装されていますか?
bitbonk 2008年

13
うん、Int64.GetHashCodeはまさにそれを行います。もちろん、Javaではボクシングが必要です。それは私に思い出させる-本へのリンクを追加する時間...
ジョン・スキート

77
23は良い選択ではありませんDictionary<TKey,TValue>。(。net 3.5 SP1の時点で)ある素数を法として良い分布を仮定しているからです。そして23はそれらの1つです。したがって、Capacity 23のディクショナリがある場合、最後の貢献のみGetHashCodeが複合ハッシュコードに影響します。私はむしろ23の代わりに29を使用したいので
CodesInChaos

23
@CodeInChaos:バケットに影響を与えるのは最後の貢献のみです。そのため、最悪の場合、ディクショナリ内の23エントリすべてを調べる必要があります。それでも、各エントリの実際のハッシュコードを確認するので、コストはかかりません。もしあなたがそんなに小さな辞書を持っているなら、それはそれほど重要ではないでしょう。
Jon Skeet

20
@Vajda:私は通常、有効なハッシュコードとして0を使用しますnull-これはフィールドを無視することと同じではありません。
Jon Skeet

431

匿名型

Microsoftはすでに優れた汎用HashCodeジェネレーターを提供しています。プロパティ/フィールド値を匿名型にコピーしてハッシュするだけです。

new { PropA, PropB, PropC, PropD }.GetHashCode();

これは、任意の数のプロパティで機能します。ボクシングは使用しません。匿名型のフレームワークにすでに実装されているアルゴリズムを使用するだけです。

ValueTuple-C#7の更新

@cactuaroidがコメントで言及しているように、値のタプルを使用できます。これにより、いくつかのキーストロークが節約され、さらに重要なことに、純粋にスタック上で実行されます(ガベージなし)。

(PropA, PropB, PropC, PropD).GetHashCode();

(注:匿名型はクラスとして実装されているため、匿名型を使用する元の手法ではヒープ、つまりガベージにオブジェクトが作成されるようですが、これはコンパイラーによって最適化される可能性があります。これらのオプションをベンチマークすることは興味深いですが、タプルオプションのほうが優れているはずです。)


85
はい、匿名のGetHashCode実装は非常に効果的です(ところで、Jon Skeetの回答と同じです)が、このソリューションの唯一の問題は、GetHashCode呼び出し時に新しいインスタンスを生成することです。特に大きなハッシュ化されたコレクションへの集中的なアクセスの場合、それは少しオーバーヘッド
っぽいかもしれませ

5
@digEmAll良い点は、私は新しいオブジェクトを作成するオーバーヘッドについては考えていませんでした。Jon Skeetの答えは最も効率的であり、ボクシングを使用しません。(@KumbaチェックされていないVBを解決するには、Int64(long)を使用し、計算後に切り捨てます。)
Rick Love

42
new { PropA, PropB, PropC, PropD }.GetHashCode()あまりにも言うことができる
sehe

17
VB.NETは匿名型の作成でKeyを使用する必要があります。New With {Key PropA}.GetHashCode()それ以外の場合、GetHashCodeは同じ「識別」プロパティを持つ異なるオブジェクトに対して同じハッシュコードを返しません。
David Osborne

4
その場合は@キース、ハッシュコードが計算されるたびに列挙するのではなく、IEnumerableをリスト値としてどこかに保存することを検討します。GetHashCode内で毎回ToListを計算すると、多くの状況でパフォーマンスが低下する可能性があります。
Rick Love

105

これが私のハッシュコードヘルパーです。
これは、ジェネリック型引数を使用するため、ボクシングが発生しないという利点があります。

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode<T>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(this int hashCode, T arg)
    {
        unchecked
        {
            return 31 * hashCode + arg.GetHashCode();   
        }
    }

また、流暢なインターフェースを提供する拡張メソッドがあるため、次のように使用できます。

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

またはこのように:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}

5
T[]すでにそうであるように、別に必要はありませんIEnumerable<T>
naffal

5
これらのメソッドをリファクタリングして、コアロジックを1つの関数に制限できます
nawfal

12
ちなみに、31はCPUでのシフトと減算であり、非常に高速です。
Chui Tey 2013

4
@nightcoderではparamsを使用できます。
ANeves、2015

6
@ChuiTeyこれはすべてのメルセンヌプライムに共通するものです。
Pharap

63

ヘルパーライブラリにHashingクラスがあり、この目的で使用しています。

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

次に、単に次のように使用できます。

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

私はそのパフォーマンスを評価しなかったので、どんなフィードバックでも歓迎します。


26
まあ、フィールドが値型の場合、ボクシングが発生します。
ナイトコーダー2010

5
「OverflowExceptionをキャッチすることで、後で拡張できます」のunchecked目的は、で必要なオーバーフローの例外を回避することGetHashCodeです。そのため、値がオーバーフローintしてもまったく問題はありません。
Tim Schmelter 14

1
このアルゴリズムの1つの問題は、nullでいっぱいの配列は、長さに関係なく常に0を返すということです
Nathan Adams

2
このヘルパーメソッドは、新しいオブジェクトも割り当てます[]
James Newton-King

1
@NathanAdamsが述べているように、null完全にスキップされるという事実は、予期しない結果をもたらす可能性があります。それらをスキップする代わりにinput[i].GetHashCode()input[i]がnull のときの代わりに定数値を使用する必要があります。
David Schwartz

58

Jon Skeetの実装を使用した私のヘルパークラスは次のとおりです。

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

使用法:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

System.Int32の拡張メソッドの記述を避けたい場合:

public readonly struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

それでもヒープ割り当ては回避され、まったく同じ方法で使用されます。

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

編集(2018年5月):EqualityComparer<T>.DefaultgetterがJIT組み込みになりました- プルリクエストこのブログの投稿でStephen Toubによって言及されています。


1
:私はあることを三次オペレータの行を変更しますvar h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
ビル・バリー

との三項演算子は、値型の場合にメモリを割り当てる命令にobj != nullコンパイルされると思います。代わりに、メソッドの仮想呼び出しにコンパイルするwhichを使用できます。boxTobj.Equals(null)Equals
Martin Liversage 2014

なぜならthis.hashCode != h。同じ値を返しません。
–ŞafakGür2015

コメントを編集するのではなく、削除してください。新しい構造体を作成してから、hashCodeを非読み取り専用に変更して、「unchecked {th​​is.hashCode ^ = h * 397;} return this;」を実行する方が有益ですか。例えば?
エリックカールソン2015年

不変性にはその利点があります(なぜ、可変構造体が悪であるのですか?)。パフォーマンスについては、ヒープにスペースを割り当てないため、私が行うことはかなり安価です。
ŞafakGUR

30

.NET Standard 2.1以降

.NET Standard 2.1以降を使用している場合は、System.HashCode構造体を使用できます。使用方法は2つあります。

HashCode.Combine

このCombineメソッドは、最大8つのオブジェクトを指定して、ハッシュコードを作成するために使用できます。

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

このAddメソッドは、コレクションを処理するのに役立ちます。

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}

GetHashCodeを簡単に

完全なブログ投稿を読むことができます ' GetHashCode Made Easy詳細とコメントについては」を参照してください。

使用例

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<string> Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}

実装

public struct HashCode : IEquatable<HashCode>
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach<T>(IEnumerable<T> items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And<T>(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach<T>(IEnumerable<T> items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}

何が良いアルゴリズムを作るのですか?

速度

ハッシュコードを計算するアルゴリズムは高速である必要があります。通常、単純なアルゴリズムの方が高速です。

確定的

ハッシュアルゴリズムは確定的である必要があります。つまり、同じ入力が与えられた場合、常に同じ出力を生成する必要があります。

衝突を減らす

ハッシュコードを計算するアルゴリズムは、ハッシュの衝突を最小に保つ必要があります。ハッシュ衝突は、GetHashCode2つの異なるオブジェクトに対する2 つの呼び出しが同一のハッシュコードを生成するときに発生する状況です。衝突は許可されますが(衝突が許可されていないという誤解がある場合もあります)、衝突は最小限に抑える必要があります。

優れたハッシュ関数は、予想される入力を出力範囲全体で可能な限り均等にマッピングする必要があります。均一性が必要です。

防止のDoS

.NET Coreでは、アプリケーションを再起動するたびに異なるハッシュコードを取得します。これは、サービス拒否攻撃(DoS)を防ぐためのセキュリティ機能です。.NET Frameworkの場合、次のApp.configファイルを追加して、この機能有効にする必要があります。

<?xml version ="1.0"?>  
<configuration>  
   <runtime>  
      <UseRandomizedStringHashAlgorithm enabled="1" />  
   </runtime>  
</configuration>

この機能のため、ハッシュコードは、それが作成されたアプリケーションドメインの外部で使用したり、コレクションのキーフィールドとして使用したり、永続化したりしないでください。

詳細については、こちらをご覧ください

暗号的に安全ですか?

アルゴリズムは、暗号化ハッシュ関数である必要はありません。つまり、次の条件を満たす必要はありません。

  • 特定のハッシュ値を生成するメッセージを生成することは不可能です
  • 同じハッシュ値を持つ2つの異なるメッセージを見つけることは不可能です
  • メッセージに小さな変更を加えると、ハッシュ値が大幅に変更されるため、新しいハッシュ値は古いハッシュ値と相関関係がないように見えます(雪崩効果)。

29

Equals()が複数のフィールドを比較するほとんどの場合、GetHash()が1つのフィールドまたは複数のフィールドでハッシュするかどうかは重要ではありません。ハッシュの計算が本当に安く(割り当てを行わないでください)、高速であること(重い計算やデータベース接続がないこと)を確認し、適切な分散を提供する必要があります。

重労働はEquals()メソッドの一部である必要があります。ハッシュは、できるだけ少ないアイテムでEquals()を呼び出すことを可能にする非常に安価な操作である必要があります。

そして最後のヒント:GetHashCode()が複数のアプリケーション実行で安定していることに依存しないでください。多くの.Netタイプは、再起動後もハッシュコードが同じであることを保証しないため、メモリデータ構造ではGetHashCode()の値のみを使用する必要があります。


10
「Equals()が複数のフィールドを比較するほとんどの場合、GetHash()が1つのフィールドまたは複数のフィールドでハッシュするかどうかは重要ではありません。」ハッシュ化されていないフィールドのみが異なるオブジェクトの場合、ハッシュの衝突が発生するため、これは危険なアドバイスです。これが頻繁に発生すると、ハッシュベースのコレクション(HashMap、HashSetなど)のパフォーマンスが低下します(最悪の場合、最大でO(n))。
sleske 2010

10
これは実際にはJavaで発生しました。JDKの初期のバージョンでは、String.hashCode()は文字列の先頭のみを考慮していました。これは、末尾のみが異なるHashMapのキーとして文字列を使用した場合にパフォーマンスの問題を引き起こします(これは、URLの場合などで一般的です)。そのため、アルゴリズムが変更されました(JDK 1.2または1.3では信じています)。
sleske 2010

3
その1つのフィールドが「良い分布を提供する」(私の回答の最後の部分)場合は、1つのフィールドで十分です。適切な分布を提供しない場合、(そしてその直後に)別の計算が必要になります。(例えば、単に別のフィールドを使用良好な分布を提供し、または複数のフィールドを使用)
バートHuijben

GetHashCodeメモリ割り当ての実行に問題はないと思います。ただし、メモリ割り当てが初めて使用されるときにのみ実行されます(その後の呼び出しでキャッシュされた結果が返されるだけ)。重要なことは、衝突を回避するために長い時間を費やすことではなく、「体系的な」衝突を回避することです。タイプに2つのintフィールドがoldXありnewX、頻繁に1ずつ異なる場合、のハッシュ値oldX^newXはそのようなレコードの90%にoldX+newX
1、2、4

1
...より洗練された関数よりも、50万の異なるハッシュ値を持つ1,000,000のコレクションは、各ハッシュ値に2つの関連付けられたものがある場合は非常にうまくいき、1つのハッシュ値が500,001のものと他の1つがそれぞれ持っている場合は非常に悪くなります。
スーパーキャット2013

23

最近まで、私の答えはジョン・スキートのここに非常に近かったでしょう。ただし、私は最近、2のべき乗のハッシュテーブル、つまり内部テーブルのサイズが8、16、32などのハッシュテーブルを使用するプロジェクトを開始しました。素数サイズを優先するのには十分な理由がありますが、 2の累乗サイズにもいくつかの利点があります。

そして、それはほとんど吸いました。したがって、少し実験と調査を行った後、次の方法でハッシュを再ハッシュし始めました。

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

そして、2のべき乗のハッシュテーブルは、これ以上問題がありませんでした。

上記の方法ではうまくいかないので、これは私を混乱させました。より正確には、オリジナルGetHashCode()が非常に特定の方法で貧弱でない限り、それは機能しないはずです。

ハッシュコードを再混合しても、優れたハッシュコードを改善することはできません。考えられる唯一の影響は、さらにいくつかの衝突を導入することです。

ハッシュコードを再混合しても、恐ろしいハッシュコードを改善することはできません。考えられる効果は、たとえば、値53での多数の衝突を多数の値18,3487,291に変更することだけだからです。

ハッシュコードを再混合しても、その範囲全体で絶対的な衝突を回避するのに少なくともかなりうまく機能したハッシュコードのみを改善できます(2 32可能な値)でを回避することはできましたが、ハッシュテーブルで実際に使用するためにモジュロダウンしたときに衝突を回避することはできませんでした。2のべき乗テーブルのより単純なモジュロはこれをより明白にしましたが、それはまた、より一般的な素数テーブルでマイナスの影響を及ぼしましたが、それはそれほど明白ではありませんでした(リハッシュの追加の作業はメリットを上回るでしょう) 、しかし利点はまだそこにあります)。

編集:私はまた、おそらく2の累乗であるという事実よりも、衝突に対する感度を高めたであろうオープンアドレス指定を使用していました。

そして、それは.NET(またはここでの研究)のstring.GetHashCode()実装がこの方法でどれほど改善できるか(衝突が少ないため、テストの実行が約20から30倍速くなる)と、自分のハッシュコードの影響をさらに妨げていました。改善される可能性があります(それ以上)。

私が過去にコーディングし、実際にこのサイトで回答の基礎として使用したすべてのGetHashCode()実装は、私が考えたよりもはるかに悪かった。ほとんどの場合、それは多くの用途に「十分」でしたが、私はもっと良いものを望んでいました。

そのため、そのプロジェクトを一方的に(とにかくペットプロジェクトでした)、. NETで適切に分散された優れたハッシュコードをすばやく生成する方法を検討し始めました。

最終的に、私はSpookyHashを.NET に移植することにしました。実際、上記のコードはSpookyHashを使用して32ビット入力から32ビット出力を生成する高速パスバージョンです。

さて、SpookyHashはコードの一部を覚えるのに適した時間ではありません。私のポートは、速度を上げるために多くを手動でインライン化したため、さらに少なくなっています*。しかし、それがコードの再利用の目的です。

それから、元のプロジェクトがより良いハッシュコードを生成する方法の問題を生成したのと同じように、そのプロジェクトを一方の側に置きまし

それから私は戻ってきて、多くのオーバーロードを生成して、ほぼすべてのネイティブ型(decimal† を除く)をハッシュコードに簡単にフィードしました。

特に、アルゴリズムが最適化されている64ビットマシンでは、ボブジェンキンスが移植した元のコードの方が高速であるため、ボブジェンキンスはほとんどの功績がありますfast。

完全なコードはhttps://bitbucket.org/JonHanna/spookilysharp/srcで確認できますが、上記のコードはそれを簡略化したバージョンであると考えてください

ただし、すでに記述されているため、より簡単に使用できます。

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

これはシード値も取るので、信頼できない入力を処理する必要があり、ハッシュDoS攻撃から保護したい場合は、稼働時間などに基づいてシードを設定し、攻撃者が結果を予測できないようにすることができます。

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

*これの大きな驚きは、(x << n) | (x >> -n)改善されたものを返した回転メソッドを手動でインライン化したことです。私はジッターが私にそれをインライン化したと確信していましたが、プロファイリングはそうではありませんでした。

decimalはC#からのものですが、.NETの観点からはネイティブではありません。それに関する問題は、それ自体GetHashCode()が精度を重要なものとして扱うのに対し、それ自体Equals()は重要ではないということです。どちらも有効な選択肢ですが、そのように混在させることはできません。独自のバージョンを実装する場合、どちらか一方を選択する必要がありますが、どちらを希望するかわかりません。

comparison比較として。文字列で使用した場合、64ビットのSpookyHashはstring.GetHashCode()32ビットよりもかなり高速でstring.GetHashCode()、64ビットよりも少し高速です。これは、32ビットのSpookyHashよりもかなり高速ですが、妥当な選択であるほど十分高速です。


複数のハッシュ値を1つに結合する場合long、中間結果に値を使用し、最終結果をに変更する傾向がありますint。それは良い考えのように思えますか?私の懸念は、たとえばhash =(hash * 31)+ nextFieldを使用すると、一致する値のペアはハッシュの上位27ビットにのみ影響するということです。計算をaに拡張してlong、ものをラップすることで、その危険を最小限に抑えることができます。
スーパーキャット2014

@supercatそれはあなたの最終的な変更の分布に依存します。SpookilySharpライブラリは、理想的には(オブジェクトを作成する必要がないため)blittable型へのポインターを渡すか、直接処理する列挙型の1つを渡すことで、配布が良好であることを保証しますが、まだblittableがない場合データまたは適切な列挙型で、.Update()上記の回答に従って複数の値で呼び出すとうまくいきます。
Jon Hanna

@JonHannaあなたが遭遇した問題のある振る舞いをもっと正確にしてもいいですか?値オブジェクトの実装を簡単にするライブラリ(ValueUtils)を実装しようとしています。2のべき乗のハッシュテーブルでのハッシュの混和性が低いことを示すテストセットが気に入っています。
Eamon Nerbonne、2014年

@EamonNerbonne私は「全体の時間がそのように遅くなった」ほど正確なものはありません。編集で追加したように、私がオープンアドレッシングを使用していたという事実は、2の累乗係数よりも重要であった可能性があります。私はいくつかの異なるアプローチを比較する特定のプロジェクトでいくつかのテストケースを実行する予定ですので、その後はより良い答えが得られるかもしれませんが、それは優先度は高くありません(差し迫った必要のない個人的なプロジェクト) 、それで私はそれに到達したときにそれを取得します...)
Jon Hanna

@JonHanna:ええ、私は個人的なプロジェクトのスケジュールがどうなるか知っています-頑張ってください!いずれにせよ、私は最後のコメントをうまく表現しなかったのがわかります。問題のある入力を求めるつもりであり、必ずしも結果として生じた問題の詳細ではありません。私はそれをテストセット(またはテストセットのインスピレーション)として使用したいです。いずれにせよ-ペットプロジェクトの成功を祈ってください:-)。
Eamon Nerbonne、2014年

13

これは良いものです:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

そしてここにそれを使用する方法があります:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}

1
キーはどのように決定されますか?GetHashCode()はパラメータを取らないため、何らかの方法で決定する必要のある2つのキーを使用してこれを呼び出す必要があります。申し訳ありませんが、これ以上の説明がなければ、これは賢く見えるだけですが、それほど良くはありません。
Michael Stum

そして、なぜ一般的なオーバーロードが必要なのですか?すべてのオブジェクトにはGetHashCode()メソッドがあるため、型は重要ではありません(コードでは使用されません)。そのため、params配列パラメーターを使用して常にメソッドを使用できます。または、ここで何か不足していますか?
gehho

4
ジェネリックスの代わりにオブジェクトを使用すると、ボクシングとメモリ割り当てが得られますが、GetHashCodeでは必要ありません。だからジェネリックは行く方法です。
CodesInChaos 2010年

1
末尾のシフト/ XOR手順(h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);彼らはとても私には冗長入力し、見た目のいずれかに依存しない:コードの臭いを持っている。
sehe

1
@Magnusはい、元のコメントを削除します。これは他のいくつかのソリューションほど高速ではないかもしれませんが、あなたが言うように重要ではないことに注意してください。配布は素晴らしいです。ここにあるほとんどのソリューションよりも優れているので、私から+1してください!:)
nawfal

11

とおりhttps://github.com/dotnet/coreclr/pull/14863、超シンプルでハッシュコードを生成するための新しい方法があります!書くだけ

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

これにより、実装の詳細を気にすることなく、高品質のハッシュコードが生成されます。


それは甘い追加のように見えます...出荷される.NET Coreのバージョンを知る方法はありますか?
Dan J

1
@DanJなんて幸せな偶然でしょう。corefxのHashCode変更はコメントの数時間前にマージされました:)タイプは.NET Core 2.1で出荷される予定です。
James Ko

それは素晴らしいです-そしてかなりのターンアラウンドタイム。賛成。:)
Dan J

@DanJさらに良いニュース-dotnet-core MyGetフィードでホストされているCoreFXのナイトリービルドで今すぐ入手できるはずです。
James Ko

我々はなく、かなりしているので、仕事で私を助けていない-甘いという最先端が、良いを知っています。乾杯!
Dan J

9

上にJon Skeet投稿したアルゴリズムの別の流暢な実装がありますが、割り当てやボクシング操作は含まれていません。

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

使用法:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

HashValueジェネリック型の制約により、コンパイラーはクラスで呼び出されないようにします。ただしHashObject、ジェネリック引数を追加するとボクシング操作も追加されるため、コンパイラのサポートはありません。


8

これが私の単純なアプローチです。私はこれに古典的なビルダーパターンを使用しています。タイプセーフ(ボックス化/ボックス化解除)であり、.NET 2.0と互換性があります(拡張メソッドなどはありません)。

これは次のように使用されます。

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

そしてここに実際のビルダークラスがあります:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

Mangusの回答のように、gethashcode関数内でのオブジェクトの作成を回避できます。静的なハッシュ関数(スターターハッシュに関心がある人)を呼び出すだけです。また、AddItems<T>(params T[] items)ヘルパークラスでは(AddItem(T)毎回呼び出すよりも)メソッドをより頻繁に使用できます。
nawfal 2013

そしてthis.result * Prime2 * item.GetHashCode()、頻繁に使用されるときにどのようなメリットがあると思いますthis.result * Prime2 + item.GetHashCode()か?
nawfal 2013

他のAddItems<T>(params T[] items)理由でこれ以上使用できないtypeof(T1) != typeof(T2)
bitbonk

ああはい、見逃しました。
nawfal 2013

5

ReSharperユーザーは、GetHashCode、Equalsなどをで生成できReSharper -> Edit -> Generate Code -> Equality Membersます。

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

4

プロパティが8つ以下(できれば)である場合は、別の方法を紹介します。

ValueTuple 構造体であり、固体を持っているように見えます GetHashCode実装があるようです。

つまり、これを簡単に行うことができます。

// Yay, no allocations and no custom implementations!
public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

のがの.NETのコアの現在の実装を見てみましょうValueTupleさんをGetHashCode

これはからValueTupleです:

    internal static int CombineHashCodes(int h1, int h2)
    {
        return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
    }

    internal static int CombineHashCodes(int h1, int h2, int h3)
    {
        return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
    }

そして、これはからHashHelperです:

    public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();

    public static int Combine(int h1, int h2)
    {
        unchecked
        {
            // RyuJIT optimizes this to use the ROL instruction
            // Related GitHub pull request: dotnet/coreclr#1830
            uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
            return ((int)rol5 + h1) ^ h2;
        }
    }

英語で:

  • 左回転(循環シフト)h1を5ポジション。
  • 結果とh1を加算します。
  • 結果をh2とXORします。
  • 上記の操作を{静的ランダムシード、h1}で実行することから始めます。
  • 以降の各項目について、前の結果と次の項目(h2など)に対して操作を実行します。

このROL-5ハッシュコードアルゴリズムのプロパティについて詳しく知っておくと便利です。

残念ながら、ValueTuple私たち自身のために延期することは、私たちGetHashCodeが望んで期待するほど速くはないかもしれません。関連する議論のこのコメントは、直接呼び出しのHashHelpers.Combine方がパフォーマンスが高いことを示しています。裏側では、それは内部のものなので、ここで得たものの多くを犠牲にしてコードをコピーする必要があります。また、Combineランダムシードを最初に使用することを忘れないようにする必要があります。その手順をスキップした場合、結果はどうなるのかわかりません。


h1 >> 27それを無視するために0 と仮定すると、h1 << 5と等しいh1 * 32ため、と同じになりますh1 * 33 ^ h2このページによると、「修正されたバーンスタイン」と呼ばれています。
cactuaroid 2018

3

私の作業のほとんどはデータベース接続で行われます。つまり、私のクラスはすべてデータベースからの一意の識別子を持っています。私は常にデータベースのIDを使用してハッシュコードを生成しています。

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

つまり、オブジェクトPersonとAccountがあり、両方にID = 1がある場合、それらは同じハッシュコードを持つことになります。そして、それは大丈夫ではありません。
pero 2010年

15
実際、上記のコメントは正しくありません。ハッシュコードの衝突の可能性は常に存在します(ハッシュコードはバケットを見つけるだけで、個々のオブジェクトは見つけません)。そのため、このような実装(混合オブジェクトを含むハッシュコードの場合)は、多くの衝突を引き起こします。これは望ましくありませんが、ハッシュテーブルに単一タイプのオブジェクトしかなかったとしても、まったく問題はありません。また、均等に分散されませんが、system.objectの基本実装も分散されないため、あまり心配する必要はありません...
piers7

2
idは整数なので、ハッシュコードは単なるidにすることができます。GetHashCodeを整数で呼び出す必要はありません(これは恒等関数です)
Darrel Lee

2
@DarrelLeeがtomoの_idはGuidの可能性があります。_id.GetHashCode意図が明確であるため、コーディングを行うことをお勧めします。
nawfal 2013

2
@ 1224は使用パターンによっては、指定した理由でひどい場合がありますが、すばらしい場合もあります。穴のない一連の数値がある場合、完全なハッシュが得られます。これは、どのアルゴリズムでも生成できるよりも優れています。それが事実であることがわかっている場合は、それを当てにして等価チェックをスキップすることもできます。
Jon Hanna

3

必要に応じて素数を上げる方が簡単なことを除いて、nightcoderのソリューションとほとんど同じです。

PS:これは、口の中で少し吐くときの1つです。これは、デフォルトの9つの方法で1つのメソッドにリファクタリングできることを知っていますが、遅くなるため、目を閉じてそれを忘れようとします。

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

2
nullを処理しません。
JJS

1

上記の答えとして選択された実装を使用して、浮動小数点数と小数に問題が発生しました。

このテストは失敗します(floats; 2つの値を負に切り替えてもハッシュは同じです):

        var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m};
        var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

しかし、このテストは(intで)成功します:

        var obj1 = new { A = 100m, B = 100m, C = 100, D = 100};
        var obj2 = new { A = 100m, B = 100m, C = -100, D = -100};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

プリミティブ型にGetHashCodeを使用しないように実装を変更しましたが、うまく機能するようです

    private static int InternalComputeHash(params object[] obj)
    {
        unchecked
        {
            var result = (int)SEED_VALUE_PRIME;
            for (uint i = 0; i < obj.Length; i++)
            {
                var currval = result;
                var nextval = DetermineNextValue(obj[i]);
                result = (result * MULTIPLIER_VALUE_PRIME) + nextval;

            }
            return result;
        }
    }



    private static int DetermineNextValue(object value)
    {
        unchecked
        {

                int hashCode;
                if (value is short
                    || value is int
                    || value is byte
                    || value is sbyte
                    || value is uint
                    || value is ushort
                    || value is ulong
                    || value is long
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    return Convert.ToInt32(value);
                }
                else
                {
                    return value != null ? value.GetHashCode() : 0;
                }
        }
    }

1
ケースでは、それ以外の意図uncheckedは影響しませんConvert.ToInt32uintlongfloatdoubledecimalここにすべてのオーバーフローすることができます。
Mark Hurd、2014

1

マイクロソフトはいくつかのハッシュ方法をリードしています...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

複数の大きなintでこれを使用できると思います:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

マルチタイプの場合も同じです。最初にすべてをint使用に変換してGetHashCode() から、int値がXORされ、結果がハッシュになります。

IDとしてハッシュを使用する人(つまり、一意の値を意味します)の場合、ハッシュは当然桁数に制限されます。ハッシュアルゴリズムでは、少なくともMD5の5バイトだったと思います。

複数の値をハッシュ値に変換することができ、それらのいくつかは同じであるため、識別子として使用しないでください。(多分いつか私はあなたのコンポーネントを使うつもりです)


7
ハッシュコードを作成するために整数をXoringすることは、実際の値との衝突の数が特に多くなる傾向がある有名なアンチパターンです。
Jon Hanna

ここではすべてが整数を使用しており、ハッシュが同じであるという保証はありませんでした。衝突が発生することがほとんどないため、ハッシュを変化させようとしただけです。
deadManN 2015

はい。ただし、2番目と5番目は衝突を回避しようとしません。
Jon Hanna

1
はい、そのアンチパターンは非常に一般的です。
Jon Hanna

2
到達するバランスがあります。Spookyhashのような非常に優れたハッシュコードを使用すると、衝突を大幅に回避できますが、計算時間はこれらのどれよりもはるかに長くなります(ただし、非常に大量のデータをハッシュする場合、Spookyhashは非常に高速です)。xoringの前に値の1つを単純にシフトすることは、衝突を適切に削減するためのわずかな追加コストです。素数の乗算により、時間と品質の両方が再び増加します。したがって、シフトとマルチのどちらが良いかは議論の余地があります。プレーンxorは、多くの場合、実際のデータに多くの衝突があり、回避するのが最善です
Jon Hanna

1

これは、Josh Blochの実装を実装する静的ヘルパークラスです。また、ボクシングを「防止」するための明示的なオーバーロードを提供し、特に長いプリミティブに対してハッシュを実装することもできます。

等しい実装に一致する文字列比較を渡すことができます。

ハッシュ出力は常にintであるため、ハッシュ呼び出しをチェーンするだけで済みます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace Sc.Util.System
{
    /// <summary>
    /// Static methods that allow easy implementation of hashCode. Example usage:
    /// <code>
    /// public override int GetHashCode()
    ///     => HashCodeHelper.Seed
    ///         .Hash(primitiveField)
    ///         .Hsh(objectField)
    ///         .Hash(iEnumerableField);
    /// </code>
    /// </summary>
    public static class HashCodeHelper
    {
        /// <summary>
        /// An initial value for a hashCode, to which is added contributions from fields.
        /// Using a non-zero value decreases collisions of hashCode values.
        /// </summary>
        public const int Seed = 23;

        private const int oddPrimeNumber = 37;


        /// <summary>
        /// Rotates the seed against a prime number.
        /// </summary>
        /// <param name="aSeed">The hash's first term.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int rotateFirstTerm(int aSeed)
        {
            unchecked {
                return HashCodeHelper.oddPrimeNumber * aSeed;
            }
        }


        /// <summary>
        /// Contributes a boolean to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aBoolean">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, bool aBoolean)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (aBoolean
                                ? 1
                                : 0);
            }
        }

        /// <summary>
        /// Contributes a char to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aChar">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, char aChar)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aChar;
            }
        }

        /// <summary>
        /// Contributes an int to the developing HashCode seed.
        /// Note that byte and short are handled by this method, through implicit conversion.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aInt">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, int aInt)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aInt;
            }
        }

        /// <summary>
        /// Contributes a long to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aLong">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, long aLong)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (int)(aLong ^ (aLong >> 32));
            }
        }

        /// <summary>
        /// Contributes a float to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aFloat">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, float aFloat)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + Convert.ToInt32(aFloat);
            }
        }

        /// <summary>
        /// Contributes a double to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aDouble">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, double aDouble)
            => aSeed.Hash(Convert.ToInt64(aDouble));

        /// <summary>
        /// Contributes a string to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aString">The value to contribute.</param>
        /// <param name="stringComparison">Optional comparison that creates the hash.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(
                this int aSeed,
                string aString,
                StringComparison stringComparison = StringComparison.Ordinal)
        {
            if (aString == null)
                return aSeed.Hash(0);
            switch (stringComparison) {
                case StringComparison.CurrentCulture :
                    return StringComparer.CurrentCulture.GetHashCode(aString);
                case StringComparison.CurrentCultureIgnoreCase :
                    return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.InvariantCulture :
                    return StringComparer.InvariantCulture.GetHashCode(aString);
                case StringComparison.InvariantCultureIgnoreCase :
                    return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.OrdinalIgnoreCase :
                    return StringComparer.OrdinalIgnoreCase.GetHashCode(aString);
                default :
                    return StringComparer.Ordinal.GetHashCode(aString);
            }
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// Each element may be a primitive, a reference, or a possibly-null array.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, IEnumerable aArray)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (object item in aArray) {
                ++countPlusOne;
                if (item is IEnumerable arrayItem) {
                    if (!object.ReferenceEquals(aArray, arrayItem))
                        aSeed = aSeed.Hash(arrayItem); // recursive call!
                } else
                    aSeed = aSeed.Hash(item);
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// You must provide the hash function for each element.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <param name="hashElement">Required: yields the hash for each element
        /// in <paramref name="aArray"/>.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (T item in aArray) {
                ++countPlusOne;
                aSeed = aSeed.Hash(hashElement(item));
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null object to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, object aObject)
        {
            switch (aObject) {
                case null :
                    return aSeed.Hash(0);
                case bool b :
                    return aSeed.Hash(b);
                case char c :
                    return aSeed.Hash(c);
                case int i :
                    return aSeed.Hash(i);
                case long l :
                    return aSeed.Hash(l);
                case float f :
                    return aSeed.Hash(f);
                case double d :
                    return aSeed.Hash(d);
                case string s :
                    return aSeed.Hash(s);
                case IEnumerable iEnumerable :
                    return aSeed.Hash(iEnumerable);
            }
            return aSeed.Hash(aObject.GetHashCode());
        }


        /// <summary>
        /// This utility method uses reflection to iterate all specified properties that are readable
        /// on the given object, excluding any property names given in the params arguments, and
        /// generates a hashcode.
        /// </summary>
        /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use
        /// the <see cref="Seed"/>.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param>
        /// <param name="ignorePropertyNames">Optional.</param>
        /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashAllProperties(
                this int aSeed,
                object aObject,
                BindingFlags propertySelector
                        = BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.GetProperty,
                params string[] ignorePropertyNames)
        {
            if (aObject == null)
                return aSeed.Hash(0);
            if ((ignorePropertyNames != null)
                    && (ignorePropertyNames.Length != 0)) {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (!propertyInfo.CanRead
                            || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0))
                        continue;
                    aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            } else {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (propertyInfo.CanRead)
                        aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            }
            return aSeed;
        }


        /// <summary>
        /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to
        /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on the Key or Value here if that itself is a KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePair">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair)
            => aSeed.Hash(keyValuePair.Key)
                    .Hash(keyValuePair.Value);

        /// <summary>
        /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/>
        /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of
        /// KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePairs">The values to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeysAndValues<TKey, TValue>(
                this int aSeed,
                IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs)
        {
            if (keyValuePairs == null)
                return aSeed.Hash(null);
            foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) {
                aSeed = aSeed.HashKeyAndValue(keyValuePair);
            }
            return aSeed;
        }
    }
}

イペス:バグを見つけた!HashKeysAndValuesこの方法は、修正されました:それが起動しますHashKeyAndValue
Steven Coco

0

からポリフィルHashCodeしたい場合netstandard2.1

public static class HashCode
{
    public static int Combine(params object[] instances)
    {
        int hash = 17;

        foreach (var i in instances)
        {
            hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0));
        }

        return hash;
    }
}

注:とともに使用するとstruct、ボクシングのためにメモリが割り当てられます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.