快速简单的哈希码组合

Rob*_*obV 55 c# algorithm hash hashcode

人们可以推荐快速简单的方法来组合两个对象的哈希码.我并不太担心碰撞,因为我有一个Hash Table可以有效地处理这个问题我只想要尽可能快地生成代码的东西.

阅读SO和网络似乎有几个主要候选人:

  1. 异或
  2. 使用Prime乘法进行异或
  3. 简单的数字运算,如乘法/除法(溢出检查或环绕)
  4. 构建一个String然后使用String类的Hash Code方法

人们会推荐什么?为什么?

Jon*_*eet 109

我个人会避免异或 - 这意味着任何两个相等的值将导致0 - 所以散列(1,1)==散列(2,2)==散列(3,3)等.另外散列(5,0) ==哈希(0,5)等偶尔会出现.我已经刻意用它集合散列-如果你想凑一个序列的项目,你关心的排序,这是不错的.

我通常使用:

unchecked
{
    int hash = 17;
    hash = hash * 31 + firstField.GetHashCode();
    hash = hash * 31 + secondField.GetHashCode();
    return hash;
}
Run Code Online (Sandbox Code Playgroud)

这就是Josh Bloch在Effective Java中提出的形式.上次我回答了类似的问题时,我设法找到了一篇文章,详细讨论了这个问题 - IIRC,没有人真正知道它为什么运作良好,但确实如此.它易于记忆,易于实现,并且易于扩展到任意数量的领域.

  • 一句警告,这是Berstein哈希的(变体),并且因为没有人知道为什么它在测试中表现良好,所以当散列是关键时不可取.请参阅http://www.eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx.此外,您应该将此代码包装在`unchecked {}`块中.GetHashCode()不应抛出任何异常. (10认同)
  • 从.NET Core 2.1开始,您可以使用System.HashCode类型的Combine方法执行此操作https://docs.microsoft.com/en-us/dotnet/api/system.hashcode.combine (7认同)
  • 看起来像Dan Bernstein(或Chris Torek的)哈希,只是用不同的常量.没人知道为什么这样也行得很好. (4认同)
  • @tofutim:31是一个很好的选择,因为乘以31可以优化为移位和减法.它是否*优化的方式取决于平台.至于为什么这些数字适用于散列 - 正如Henk所说,这有点神秘. (3认同)
  • @ rory.ap:我认为这是一项出色的工作,我非常乐意使用这些数字。虽然我讨厌承认使用常量是“因为别人说过”,但这基本上就是17/31对。 (3认同)

Spe*_*uce 45

虽然乔恩斯基特的回答中列出的模板,一般工作以及哈希函数族,常数的选择是很重要的种子17和因素31作为答案注意不要在所有的常见使用情况运行良好.在大多数用例中,散列值更接近于零int.MaxValue,并且联合散列的项目数量是几十个或更少.

对于散列整数元组的{x, y}地方-1000 <= x <= 1000-1000 <= y <= 1000,它具有几乎98.5%的极差碰撞率.例如{1, 0} -> {0, 31},{1, 1} -> {0, 32}等等.如果我们扩大覆盖范围还包括n元组在那里3 <= n <= 25,但它确实不太可怕的约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;
}
Run Code Online (Sandbox Code Playgroud)

我写了一个蒙特卡罗采样搜索循环,测试上面的方法,各种随机整数的随机n元组的种子和因子值i.允许的范围是2 <= n <= 25(其中n随机但偏向该范围的下端)和-1000 <= i <= 1000.每个种子和因子对至少进行了1200万次独特的碰撞测试.

运行约7小时后,找到的最佳对(种子和因子均限制在4位或更少)为:seed = 1009,, factor = 9176碰撞率为0.1131%.在5位和6位数区域,存在更好的选择.但为了简洁起见,我选择了前4位数的表演者,并且它在所有常见intchar散列场景中表现都很好.它似乎也适用于更大幅度的整数.

值得注意的是,"成为主要"似乎并不是作为种子和/或因素的良好表现的一般先决条件,尽管它可能有所帮助.1009上面提到的实际上是素数,但事实9176并非如此.我明确地测试了这个变化,我改变factor了附近的各种质数9176(离开时seed = 1009),并且它们都比上述解决方案表现更差.

最后,我还与通用的ReSharper推荐函数系列进行hash = (hash * factor) ^ i;了比较CustomHash(),如上所述,原始版本严重优于它.对于常见用例假设,ReSharper XOR样式的碰撞率似乎在20-30%范围内,不应该在我看来使用.

  • 哇.我喜欢这个答案中的工作.令人印象深刻,干得好! (6认同)

chw*_*arr 30

如果您使用的是.NET Core 2.1,请考虑使用System.HashCode结构来帮助生成复合哈希码.它有两种操作模式:添加和组合.

使用示例Combine,通常更简单,最多可用于八个项目:

public override int GetHashCode()
{
    return HashCode.Combine(object1, object2);
}
Run Code Online (Sandbox Code Playgroud)

使用示例Add:

public override int GetHashCode()
{
    var hash = new HashCode();
    hash.Add(this.object1);
    hash.Add(this.object2);
    return hash.ToHashCode();
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • .NET本身的一部分(但请参阅下面的内容)
  • 根据作者和评论者在将其合并到corefx repo之前所做的工作,看起来具有良好的性能和混合特性
  • 自动处理空值
  • IEqualityComparer实例的重载

缺点:

  • 截至2018年8月,仅在针对.NET Core 2.1时可用
    • 我假设这将慢慢进入.NET标准,然后进入各种其他实现.我不知道何时会发生这种情况.
  • 通用,因此它不会处理超级特定情况以及手工制作的代码

  • 您可以参考 https://www.nuget.org/packages/Microsoft.Bcl.HashCode 使其正式在 .NET Framework 4.6.1 或 .NET Standard 2.0 上运行。 (2认同)

Sti*_*ipo 16

我假设.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);
}
Run Code Online (Sandbox Code Playgroud)

另一个实现来自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;
}
Run Code Online (Sandbox Code Playgroud)


Yep*_*kai 9

在元组中使用组合逻辑.该示例使用c#7元组.

(field1, field2).GetHashCode();
Run Code Online (Sandbox Code Playgroud)

  • @RobV元组是值类型,因此它们是堆栈分配并且不施加GC压力. (5认同)
  • 一个问题... (0,1,2).GetHashCode() 和 (0,0,1,2).GetHashCode() 都产生相同的值:35。而最受支持的答案中的方法产生唯一值 0 、1、2:506480 和 0、0、1、2:15699890 (4认同)
  • 哈希代码不保证唯一。您发现了一个并非如此的情况...除非有很多冲突,否则这并不是一个不好的选择(在这种情况下,提交错误是个好主意)。我个人更喜欢使用框架中的内容,而不是实现不同的内容。 (2认同)
  • 它实际上是结构的“ValueTuple”类型([MSDN](https://docs.microsoft.com/en-us/dotnet/csharp/tuples))。注意 `Tuple` 类型是一个类并且它有 GC 压力。我喜欢这种方式。在内部,它类似于@Stipo 的帖子,但非常易于理解和查看。在大多数情况下,这将是不错的选择。 (2认同)