web-dev-qa-db-ja.com

この複合GetHashCode()を実装する最良の方法は何ですか

私は単純なクラスを持っています:

public class TileName {
    int Zoom, X, Y;

    public override bool Equals (object obj)
    {
        var o = obj as TileName;
        return (o != null) && (o.Zoom == Zoom) && (o.X == X) && (o.Y == Y);
    }

    public override int GetHashCode ()
    {
        return (Zoom + X + Y).GetHashCode();
    }
}

私が代わりに次のようなことをした場合、ハッシュコードのより良い分布が得られるかどうか私は興味を持っていました:

    public override int GetHashCode ()
    {
        return Zoom.GetHashCode() + X.GetHashCode() + Y.GetHashCode();
    }

このクラスはディクショナリキーとして使用されるため、きちんとした分布があることを確認します。

35
Frank Krueger

Jon Skeetが述べたように (SO answer の場合)、いくつかの素数を選択し、これらを単一のハッシュコードで乗算してから、すべてを合計することがベストプラクティスです。

_public int GetHashCode()
{
    unchecked
    {
        int hash = 17;
        // Maybe nullity checks, if these are objects not primitives!
        hash = hash * 23 + Zoom.GetHashCode();
        hash = hash * 23 + X.GetHashCode();
        hash = hash * 23 + Y.GetHashCode();
        return hash;
    }
}
_

xorハッシュの問題は次のとおりです。

  • XYと等しい場合、_X ^ Y = X ^ X = 0_が保持されるため、ハッシュは単なるZoomになります。
  • xorは対称演算子です。_[Zoom = 3, X = 5, Y = 7]_、_[Zoom = 3, X = 7, Y = 5]_、_[Zoom = 7, X = 5, Y = 3]_などのオブジェクトに対してまったく同じハッシュを生成します。

これらの事実により、xorメソッドは衝突を引き起こす可能性が高くなります。

Jons postに加えて、オーバーフローを明示的に無視するために、uncheckedコンテキストの使用を検討してください。 [〜#〜] msdn [〜#〜] のように言うので:

checkeduncheckedも使用しない場合、定数式は、コンパイル時にチェックされるデフォルトのオーバーフローチェックを使用します。それ以外の場合、式が定数でない場合、ランタイムオーバーフローチェックはコンパイラオプションや環境設定などの他の要因に依存します。

そのため、通常、オーバーフローはチェックされませんが、環境によっては失敗するか、コンパイラオプションを使用してビルドされている場合があります。ただし、この場合は、これらのオーバーフローを明示的にチェックしないようにします。

更新:

ちなみに:someInt.GetHashCode()someIntを返します。このように、これはもちろん最高速であり、単一の衝突のない完全なハッシュ分散です。他にどのようにintをintハッシュにマップしますか? :)だから私が言いたかったこと:あなたの最初のアプローチ:

_return (Zoom + X + Y).GetHashCode();
_

そしてあなたの2番目のもの:

_return Zoom.GetHashCode() + X.GetHashCode() + Y.GetHashCode();
_

まったく同じです。 GetHashCodeを呼び出す必要はなく、両方が衝突する可能性が非常に高くなります。 3つの整数すべてに小さな整数値がある可能性が高い場合、xorメソッドよりもさらに悪いかもしれません。

更新2:

ChaosPandionsの投稿へのコメントで書いたように、これらの3つのint値があり、Xの場合、YZoomは比較的小さい数値(1000または10000未満)です。 )これも良いハッシュジェネレーターかもしれません:

_public int GetHashCode()
{
    return (X << 16) ^ (Y << 8) ^ Zoom;
}
_

ハッシュ値のビットを分散するだけです(読みやすくするためにビッグエンディアンの例):

_00000000 00000000 00000011 00110001    X = 817
00000000 00000000 00011011 11111010    Y = 7162
00000000 00000000 00000010 10010110    Zoom = 662

00000011 00110001 00000000 00000000    X << 16
00000000 00011011 11111010 00000000    Y << 8
00000000 00000000 00000010 10010110    Zoom

00000011 00101010 11111000 10010110    (X << 16) ^ (Y << 8) ^ Zoom
_
69

あなたの質問のどちらの実装も理想的ではありません。たとえば、{ Zoom=1, X=2, Y=3 }{ Zoom=2, X=3, Y=1 }{ Zoom=3, X=1, Y=2 }などの場合とまったく同じハッシュが返されます。

私は通常、次のようなものを使用します。

public override int GetHashCode()
{
    // 269 and 47 are primes
    int hash = 269;
    hash = (hash * 47) + Zoom.GetHashCode();
    hash = (hash * 47) + X.GetHashCode();
    hash = (hash * 47) + Y.GetHashCode();
    return hash;
}

(メモリから、C#コンパイラは匿名型のGetHashCodeメソッドを生成するときに同様の何かを使用すると思います。)

7
LukeH

私は実際にこれが本当に効果的であることがわかりました。

public override int GetHashCode ()
{
    return Zoom.GetHashCode() ^ X.GetHashCode() ^ Y.GetHashCode();
}
5
ChaosPandion
public override int GetHashCode ()
{
    return (Zoom.ToString() + "-" + X.ToString() + "-" + Y.ToString()).GetHashCode();
}
3
James Westgate