【问题标题】:Order-independent Hash Algorithm顺序无关的哈希算法
【发布时间】:2015-08-24 10:28:53
【问题描述】:

我目前正在为我的自定义编程语言开发一个集合库。我已经有几种数据类型(Collection、List、Map、Set)和它们的实现(可变和不可变),但到目前为止我缺少的是hashCodeequals。虽然这些对于 Lists 来说没有问题,因为它们是有序集合,但对于 Sets 和 Maps 起着特殊的作用。如果两个 Set 具有相同的大小和相同的元素,则它们被认为是相等的,并且 Set 维护它们的顺序不应影响它们的相等性。由于 equals-hashCode-contract,hashCode 实现也必须反映这种行为,这意味着具有相同元素但顺序不同的两个集合应该具有相同的哈希码。 (这同样适用于 Maps,它在技术上是一组键值对)

示例(伪代码):

let set1: Set<String> = [ "a", "b", "c" ]
let set2: Set<String> = [ "b", "c", "a" ]
set1 == set2       // should return true
set1.hashCode == set2.hashCode // should also return true

我如何实现一个相当好的哈希算法,让上面示例中的hashCodes 返回相同的值?

【问题讨论】:

  • 集合中的一对(总和,乘积)怎么样?对于不同的数字集(据我所见),它们两者在一起并不常见。
  • 例如(e1.hashCode() + e2.hashCode() + ... + en.hashCode()) ^ (e1.hashCode() * e2.hashCode() * ... * en.hashCode())?
  • 你有没有尝试看看 Java 是如何实现这个的?
  • 刚刚做了,它总结了元素的hashCode
  • “hash of the sum”和“sum of the hashes”有很大的区别。正如您的示例所示,前者是有问题的。如果单个散列在大范围内分布良好,则后者的问题较少。

标签: java algorithm hash set


【解决方案1】:

JDK 本身针对这个问题提出了以下解决方案。 java.util.Set 接口的合约声明:

返回此集合的哈希码值。集合的哈希码定义为集合中元素的哈希码之和,其中空元素的哈希码定义为零。这确保了 s1.equals(s2) 意味着任何两个集合 s1 和 s2 的 s1.hashCode()==s2.hashCode(),这是 Object.hashCode() 的一般合同所要求的。

使用条目哈希码总和的替代方法是使用 ^ (XOR) 运算符。

Scala 语言使用Murmurhash 算法的排序不变版本(参见私有scala.util.hashing.MurmurHash3 类)来实现其immutable sets 和类似集合的hashCode(或##)方法。

【讨论】:

  • 正如我在 cmets 中所说的,我已经找到了解决这个问题的 JDK 解决方案,但我想知道更有用的无序集合哈希算法,碰撞可能性更小。
  • @Clashsoft 有什么潜在的冲突?如果单个哈希码中只有一个运行良好,那么整个哈希算法将均匀分布。
  • @augurar 实数和 32 位有符号整数之间有非常重要的区别。这是其中之一。写java.set.Util 的人知道他们在做什么,并在这里想出了一个好的策略。
  • Scala 的排序不变散列仅基于元素散列的总和、乘积(不包括零)、计数和异或,因此它不像“排序不变版本”那样抗碰撞的 Murmurhash 算法”使它听起来。这比仅仅对哈希求和要好,但仍然不是那么好。
【解决方案2】:

这是可能实现的伪代码:

String hashCode = null;
for(element : elements){
    hashCode = xor(hashCode, getHashCode(element));
}
return hashCode;

xor 函数应该返回一个与两个参数中最长的字符串一样长的字符串。它将对每个位中的位进行异或,直到到达参数之一的末尾。然后它将从较长的字符串中取出剩余的位并将其附加到上面。

这个实现意味着一个集合的 hashCode 将与其最长元素的 hashCode 一样长。因为您正在对位进行异或运算,所以无论元素的顺序如何,最后哈希码都是相同的。但是,与任何散列实现一样,存在冲突的可能性。

【讨论】:

  • 但是当我需要 int 哈希码时,我会用 String 做什么?这似乎是一个非常足智多谋的解决方案。
  • @Clashsoft 我不确定你想要int 还是String。如果它只是一个 int,那么将各个元素的 hashCode 相加就能得到你需要的东西,只要溢出环绕而不是导致错误。如果溢出导致错误,那么您需要明确处理这种情况并手动包装。相同的概念。
  • 感谢您的回答,但我想找到一个不同的解决方案,而不是对元素的哈希码求和(参见 cmets)。
  • @Clashsoft - 请注意,[1,4] 是否会与 [2,3] 冲突实际上取决于您对散列数的实现。请记住,您是在对数字而不是数字的哈希求和。此外,虽然字符串实现更耗费资源,但这也意味着它在哈希中将有更多位,因此发生冲突的机会更少。
  • 通常情况下,整数的哈希码(如果你不是超级安全的话)就是整数本身。所以1 .hashCode == 1。另外,由于hashCode 被强制为int,所以我最终还是必须使用string.hashCode
【解决方案3】:

您可以计算按字母顺序对您的收藏进行排序的哈希总和。

有 C# 示例 - 我希望你能用 Java 翻译它:)

static String GetHash(List<String> l)
{
    using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
    {
        return BitConverter.ToString(md5.ComputeHash(l.OrderBy(p => p).SelectMany(s => System.Text.Encoding.ASCII.GetBytes(s + (char)0)).ToArray())).Replace("-", "");
    }
}

【讨论】:

    猜你喜欢
    • 2016-06-23
    • 1970-01-01
    • 2015-02-15
    • 2012-06-03
    • 1970-01-01
    • 1970-01-01
    • 2014-12-30
    • 2013-01-31
    • 2021-09-29
    相关资源
    最近更新 更多