我想散列一组整数,使得整数的顺序对计算的散列值没有影响。即H([32224,12232,564423]) == H([564423,32224,12232])
。
唯一集的数量将在几百万的范围内。速度非常重要,但我需要通过选择的方法了解碰撞的上限。
维基百科在hashing vectors上有一个很好的部分,但我不明白它背后的数学是在代码中自信地实现它们。如果有人能解释一些代码涉及的数学,我将不胜感激。理想情况下,我希望最终的哈希值为32位。如果它有用 - 我将用Java实现它。
更新:由于性能原因(在许多此类集合上运行),我特别希望避免对集合中的整数进行排序。
答案 0 :(得分:6)
一种简单的方法是将各个整数的散列xor或加到一起。 xor和add是可交换的,因此这满足了顺序独立性。
因此:
int hc = 0;
for(int i = 0; i < n; i++) {
hc += a[i];
}
return hc;
或
int hc = 0;
for(int i = 0; i < n; i++) {
hc ^= a[i];
}
return hc;
因为int的哈希码无论如何都是它的值。
实际上,正是 HashSet<Integer>.hashCode
(使用add)会做什么。如果您的整数已经装箱,或者您可以装箱,那么这就是内置的解决方案。
答案 1 :(得分:1)
您可以将所有整数放在Java HashSet中并使用其hashCode。
另一方面,java.util.Set确实在文档中指定了以下内容:
返回此set的哈希码值。一组的哈希码是 被定义为集合中元素的哈希码的总和, 其中null元素的哈希码被定义为零。这个 确保s1.equals(s2)暗示s1.hashCode()== s2.hashCode() 对于任何两套s1和s2,按照一般合同的要求 是Object.hashCode()。
然后是Integer.hashCode()
此对象的哈希码值,等于此Integer对象表示的原始int值。
因此,Java标准库中整数集i1, i2, ... i_n
的hashCode为i1 + i2 + ... + i_n
。
如果数字相当小,您还可以将每个元素乘以一些适当大小的素数。 Knuth使用了2654435761,这对于java int来说太大了,但你可以使用它的2-complement,-1640531527。因此,取C = -1640531527,然后您的代码为C*i1 + C*i2 + ... C*i_n
。
private static final int C = -1640531527;
public static int calculateHash(int[] set) {
int code = 0;
for (int e: set) {
code += C * e;
}
return code;
}
然而,思想中有一个明显的缺陷。要使用hashCode,您需要能够证明2个集合确实相等,因此无论如何最简单的证明方法是对元素进行排序。当然,如果大大少于数百万套,那么也没有那么多碰撞。
答案 2 :(得分:1)
假设您需要速度而没有*Set
类的开销,那么您可以按如下方式编写H
:
/**
* Hashes a set of integers.
*
* @param list to hash
* @return hash code
*/
public static int H(int list[]) {
// XOR all the integers together.
int hashcode = 0;
for (int val : list) {
hashcode ^= val;
}
return hashcode;
}
无论顺序如何都是一样的,而且相对有效。
例如:
public static void main(String[] args) {
System.out.println(Integer.toHexString(H(new int[]{0xabcd,0x1234,0x1111})));
System.out.println(Integer.toHexString(H(new int[]{0x1234,0x1111,0xabcd})));
}
显示器:
a8e8
a8e8
通过执行以下操作,可以将其推广到int
以上:
/**
* Hashes a set of objects.
*
* @param list to hash
* @return hash code
*/
public static int H(Object list[]) {
// XOR all the hashes together.
int hashcode = 0;
for (Object val : list) {
hashcode ^= val.hashCode();
}
return hashcode;
}
main
程序必须使用Integer
数组而不是原始int
。
添加数字应该几乎一样快,并且可以在32位范围内提供更好的分布。如果集合的元素已经在范围内均匀分布,那么xor可能会更好。
但是,使用这两种方法,您可以轻松地使用整数制作碰撞。例如,使用添加方法;
{1000, 1001, 1002}
{0, 1, 3002}
这两个数组都具有相同的H()
。
使用XOR方法;
{0x1010, 0x0101}
{0x1111, 0x0000}
这两者都具有相同的H()
。
类似地,0
元素是有问题的,因为列表具有相同的散列,有或没有它。您可以通过在每次迭代时添加常量值来缓解此问题。例如:
...
hashcode += val.hashCode() + CONSTANT;
...
或者通过包含元素的数量作为初始哈希码:
...
// XOR all the hashes together.
int hashcode = list.length;
...
答案 3 :(得分:1)
我更喜欢求和而不是xoring,因为1)sum用于Set
的hashCode()实现,2)sum作为数组散列的方法,建议在Effective Java 3中使用它更少碰撞 - 易于。我建议你看看openjdk的AbstractSet
实施:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/util/AbstractSet.java?av=f
120 public int hashCode() {
121 int h = 0;
122 Iterator<E> i = iterator();
123 while (i.hasNext()) {
124 E obj = i.next();
125 if (obj != null)
126 h += obj.hashCode();
127 }
128 return h;
129 }
我还建议您制作h long
,然后返回(int) ((h & 0xffffffffL) & h >>> 32))
答案 4 :(得分:0)
这绝不是简单的编程,但你可以从DES算法的S-box中获取灵感:通过这个你可以实现一个很好的分散函数,它将类似的整数映射到非常不同的整数。然后,对这些不相似的整数进行异或运算不应再因碰撞而产生威胁。