Algorithm 集合的简单通用哈希函数

Algorithm 集合的简单通用哈希函数,algorithm,collections,hash,Algorithm,Collections,Hash,请标记为重复,但到目前为止,我发现的大多数问题都太具体或比我想要的更复杂。例如,在中,接受的答案似乎是针对散列字符串的 我最近开始在.NET中编程,不幸的是,内置类缺乏执行一些基本操作的能力,如检查相等性和查找哈希。我相信他们有自己的设计理由;不需要为.NET辩护。我只是想知道,当我需要使用集合作为字典的键时,如何避免一个重要的侧重点。例如,我希望两个包含所有相等值的不同列表对象映射到字典中的同一条目。开箱即用,它们不会:列表的默认行为是,列表不等于除其自身之外的任何东西,因此具有相同值的列表的

请标记为重复,但到目前为止,我发现的大多数问题都太具体或比我想要的更复杂。例如,在中,接受的答案似乎是针对散列字符串的

我最近开始在.NET中编程,不幸的是,内置类缺乏执行一些基本操作的能力,如检查相等性和查找哈希。我相信他们有自己的设计理由;不需要为.NET辩护。我只是想知道,当我需要使用集合作为字典的键时,如何避免一个重要的侧重点。例如,我希望两个包含所有相等值的不同列表对象映射到字典中的同一条目。开箱即用,它们不会:列表的默认行为是,列表不等于除其自身之外的任何东西,因此具有相同值的列表的另一个实例是不同的键

实现Equals很简单。我不确定的是散列函数

在我的GetHashCode实现中是否提供了一些我可以调用的东西


如果我必须从头开始写,什么是真正简单但足够好的哈希算法?我可以用SHA1,但我觉得这太过分了。我可以对所有项的散列进行异或运算,但我认为这会产生一些令人讨厌的冲突属性。我不在乎计算哈希是否快得惊人,但我不希望哈希表在具有特定分布的数据集上慢到线性。我想要的是一些简单到我能记住的东西。如果你能解释(或链接到)它的工作原理,那就有好处了。

根据我的经验,如果你有一组东西,并且你想计算它们的散列,最好分别计算每个对象的散列;将所有这些散列值收集到一个数组中。最后,计算散列值数组的散列


所有较简单的技术都会相对较快地失效。(就像将值进行异或运算或乘以幻数求和——这些都有各种病态的失败案例。)最后计算的一个额外数组哈希值成本很小,总体上是值得的。

一个好的哈希函数对任何位的字符串都同样有效,而不仅仅是字符。但是,由于集合可能:

  • 不一定在连续的内存块中,并且
  • 包括您不希望包含在哈希中的部分(例如,从链表的一个元素到另一个元素的指针,对于具有相同内容但您希望具有相同哈希值的不同链表,指针会有所不同)
  • 。。。在我看来,这里的关键问题可能是“组合一组单独的散列值以生成集合的散列值的最佳方式是什么?”

    在我看来,对集合中各个元素的散列值进行异或运算是一种合理的方法。我能立即看到的唯一问题是,它将导致两个集合具有相同的元素,但包含在不同的顺序中,散列到相同的值。避免此问题的算法可能如下所示:

    public override int GetHashCode()
    {
        int hash = 13;
        foreach (var t in this)
        {
            // X is an operation (undefined here) that somehow combines
            // the previous hash value and the item's hash value
            hash = hash X t.GetHashCode();
        }
        return hash;
    }
    
  • 查找集合中项目的哈希值
  • 通过按元素在集合中的显示顺序连接这些哈希值来创建位字符串
  • 使用任何合理的哈希算法为哈希值的位字符串生成哈希值
  • 使用上一步计算的哈希值作为集合的哈希值

  • 在这里要非常小心。如果为
    列表
    (或类似集合)创建
    GetHashCode
    方法,那么它大概会执行以下操作:

    public override int GetHashCode()
    {
        int hash = 13;
        foreach (var t in this)
        {
            // X is an operation (undefined here) that somehow combines
            // the previous hash value and the item's hash value
            hash = hash X t.GetHashCode();
        }
        return hash;
    }
    
    (我建议使用类似的方法来计算散列码。还可以查看(或位混合器)。)

    除非您第一次计算该值并缓存它,否则每次调用
    GetHashCode
    时都会迭代所有项

    因此,您为集合创建了一个
    GetHashCode
    Equals
    ,并将一个实例放入
    字典中。现在,您必须非常小心,不要更改集合(即不要添加或删除任何项目)或集合中的任何项目。否则,
    GetHashCode
    的值将更改,字典将不再工作

    我强烈建议,如果要将集合用作字典的键,请确保集合是不可变的

    还有另外一件事要考虑。列表相等的概念并不像您指出的那么简单。例如,列表
    [1,2,3,4,5]
    [5,1,3,4,2]
    是否相等?这取决于你对平等的定义。当然
    A.Union(B)==A.Intersect(B)
    ,这意味着如果你对相等的定义是“包含相同的项”,那么它们是相等的。但是如果顺序很重要,那么列表就不相等

    如果您的定义是“包含相同的项”,那么我上面展示的哈希代码计算将不起作用,因为哈希代码计算依赖于顺序。因此,如果您想计算这些列表的哈希代码,您必须首先对它们进行排序

    如果列表不能包含重复项,那么计算相等性就是创建一个列表的哈希集,并从该哈希集中的另一个列表中查找每个项。如果列表可能包含重复项,则必须对其进行排序以确定相等性,或者使用某种带有计数的字典。这两个都意味着列表中包含的对象将实现某种形式的相等比较器,等等

    有些平等的定义根本不考虑重复。也就是说,
    [1,2,3]
    将等于
    [3,3,3,2,1,1]

    考虑到不同的平等性差异,以及在定义
    列表
    的行为时考虑到这些差异和更多差异所需的努力,我可以