实现此复合GetHashCode()的最佳方法是什么

时间:2010-04-28 22:29:45

标签: c# .net computer-science hash

我有一个简单的课程:

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

这个类将被用作Dictionary键,所以我确实希望确保有一个不错的发行版。

5 个答案:

答案 0 :(得分:63)

如同Jon Skeet in this 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哈希的问题是:

  • 如果X等于Y,那么您的哈希将只是缩放,因为X ^ Y = X ^ X = 0成立
  • xor是一个对称运算符,它会为对象[Zoom = 3, X = 5, Y = 7][Zoom = 3, X = 7, Y = 5][Zoom = 7, X = 5, Y = 3]等生成完全相同的哈希值。

这些事实使得xor方法更容易引起冲突。

除了Jons帖子之外,请考虑使用unchecked上下文,以明确忽略溢出。因为像MSDN一样说:

  

如果checkedunchecked都不是   使用时,常量表达式使用   编译时的默认溢出检查   时间,检查。否则,如果   表达式是非常数的,   运行时溢出检查取决于   其他因素,如编译器选项   和环境配置。

因此,虽然通常会取消选中溢出,但它可能会在某些环境中失败,或者使用某些编译器选项构建。但在这种情况下,您希望明确不检查这些溢出。

更新:

顺便说一句:someInt.GetHashCode()返回someInt。像这样,它当然是最快的,并且没有单一碰撞的完美哈希分布。你怎么把int映射到int-hash? :)所以我想说的是:你的第一个方法:

return (Zoom + X + Y).GetHashCode();

和你的第二个:

return Zoom.GetHashCode() + X.GetHashCode() + Y.GetHashCode();

完全一样。你甚至不必打电话给GetHashCode,两者都很可能发生碰撞。可能甚至比xor方法更糟糕,如果你很可能有三个整数的小整数值。

更新2:

正如我在ChaosPandions的评论中所写的那样:如果你只有这三个int值,XYZoom是相对较小的数字(小于1000或10000) )这个也可能是一个很好的哈希生成器:

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

它只是分配哈希值中的位(big-endian中的示例以便于阅读):

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

答案 1 :(得分:7)

您的问题中的任何一种实现都不是理想的。例如,它们会为{ 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方法时会使用类似的东西。)

答案 2 :(得分:5)

我实际上发现这确实很有效。

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

答案 3 :(得分:3)

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

答案 4 :(得分:3)

我知道这个问题有点陈旧,但是如今您可以使用System.HashCode类轻松创建哈希代码

https://docs.microsoft.com/en-us/dotnet/api/system.hashcode.combine?view=netcore-3.1

在这种情况下,看起来像

public override int GetHashCode()
{
    return HashCode.Combine(Zoom, X, Y);
}