以与订单无关的方式散列一组整数

时间:2013-08-02 16:14:00

标签: java hash integer-hashing

我想散列一组整数,使得整数的顺序对计算的散列值没有影响。即H([32224,12232,564423]) == H([564423,32224,12232])

唯一集的数量将在几百万的范围内。速度非常重要,但我需要通过选择的方法了解碰撞的上限。

维基百科在hashing vectors上有一个很好的部分,但我不明白它背后的数学是在代码中自信地实现它们。如果有人能解释一些代码涉及的数学,我将不胜感激。理想情况下,我希望最终的哈希值为32位。如果它有用 - 我将用Java实现它。

更新:由于性能原因(在许多此类集合上运行),我特别希望避免对集合中的整数进行排序。

5 个答案:

答案 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中获取灵感:通过这个你可以实现一个很好的分散函数,它将类似的整数映射到非常不同的整数。然后,对这些不相似的整数进行异或运算不应再因碰撞而产生威胁。