签署了正面近乎完美的哈希

时间:2012-07-11 05:59:12

标签: java bit-manipulation hash long-integer perfect-hash

我有一个整数类型,比如long,其值介于Long.MIN_VALUE = 0x80...0( - 2 ^ 63)和Long.MAX_VALUE = 0x7f...f(2 ^ 63 - 1)之间。我想以干净有效的方式将~50%碰撞哈希到相同类型的正整数(即1和Long.MAX_VALUE之间)。

我的第一次尝试是这样的:

  • Math.abs(x) + 1
  • (x & Long.MAX_VALUE) + 1

但是这些和类似的方法总是会遇到某些值的问题,即当x0 / Long.MIN_VALUE / Long.MAX_VALUE时。当然,天真的解决方案是使用2 if语句,但我正在寻找更清洁/更短/更快的东西。有什么想法吗?

注意:假设我在Java中工作,没有隐式转换为boolean并定义了shift语义。

9 个答案:

答案 0 :(得分:9)

最简单的方法是将符号位置零,然后将零映射到其他值:

Long y = x & Long.MAX_VALUE;
return (y == 0)? 42: y;

这很简单,只使用一个if / ternary运算符,平均得出约50%的冲突率。有一个缺点:它将4个不同的值(0,42,MIN_VALUE,MIN_VALUE + 42)映射到一个值(42)。所以对于这个值,我们有75%的碰撞,而对于其他值 - 恰好是50%。

最好更均匀地分配碰撞:

return (x == 0)? 42: (x == Long.MIN_VALUE) ? 142: x & Long.MAX_VALUE;

此代码为2个值提供67%的冲突,为其他值提供50%的冲突。您不能更均匀地分配碰撞,但可以选择这两个最常碰撞的值。缺点是此代码使用两个ifs / ternary运算符。

只使用一个if / ternary运算符时,可以避免单个值发生75%的冲突:

Long y = x & Long.MAX_VALUE;
return (y == 0)? 42 - (x >> 7): y;

此代码为2个值提供67%的冲突,为其他值提供50%的冲突。选择这些最大碰撞值的自由度较少:0映射到42(您可以选择几乎任何值); MIN_VALUE映射到42 - (MIN_VALUE >> 7)(您可以将MIN_VALUE从1移到63,但只能确保A - (MIN_VALUE >> B)不会溢出。)


在没有条件运算符的情况下(但代码更复杂),可以得到相同的结果(2个值的67%冲突和其他值的50%冲突):

Long y = x - 1 - ((x >> 63) << 1);
Long z = y + 1 + (y >> 63);
return z & Long.MAX_VALUE;

这为值“1”和“MAX_VALUE”提供了67%的冲突。如果为某些其他值获得大多数碰撞更方便,只需将此算法应用于x + A,其中“A”是任意数字。

此解决方案的改进版本:

Long y = x + 1 + ((x >> 63) << 1);
Long z = y - (y >> 63);
return z & Long.MAX_VALUE;

答案 1 :(得分:3)

假设您要将所有值折叠到正空间中,为什么不将符号位置零?

您可以通过利用MAX_VALUE只是零符号位后跟一个例如

的事实,使用单个按位运算来执行此操作。
int positive = value & Integer.MAX_VALUE;

或者长期:

long positive = value & Long.MAX_VALUE;

如果你想要一个更好的&#34;具有伪随机质量的散列,您可能希望首先通过另一个散列函数来pss该值。我最喜欢的快速哈希是George Marsaglia的XORshift家族。这些具有良好的属性,它们将整个int / long数字空间完美地映射到自身,因此在将符号位置零后,您仍将获得恰好50%的碰撞。

这是Java中的快速XORshift实现:

public static final long xorShift64(long a) {
    a ^= (a << 21);
    a ^= (a >>> 35);
    a ^= (a << 4);
    return a;
}

public static final int xorShift32(int a) {
    a ^= (a << 13);
    a ^= (a >>> 17);
    a ^= (a << 5);
    return a;
}

答案 2 :(得分:1)

从信息理论视图中,您可以将2^64值映射到2^63-1值。

因此,使用模运算符进行映射是微不足道的,因为它总是具有非负结果:

y = 1 + x % 0x7fffffffffffffff;  // the constant is 2^63-1

这可能相当昂贵,那么还有什么可能呢?

简单数学2^64 = 2 * (2^63 - 1) + 2表示我们将有两个源值映射到一个目标值,除了两个特殊情况,其中三个将转到一个。将它们视为两个特殊的64位值,称为x1x2,每个值与另外两个源值共享一个目标。在上面的mod表达式中,这通过“换行”发生。目标值y=2^31-2y=2^31-3有三个映射。所有其他人都有两个。由于我们不得不使用比mod更复杂的东西,让我们寻找一种方法,以低成本在任何我们喜欢的地方映射特殊值

为了说明,我们可以将[-8..7]中的4位有符号整数x映射到[1..7]中的y,而不是64位空间。

一个简单的方法是将[1..7]中的x值映射到自己,然后问题就会缩小到[-8..0]中的xy的映射在[1..7]。注意,这里有9个源值,如上所述只有7个目标。

显然有很多策略。此时你可能会看到一个gazzilion。我只会描述一个特别简单的。

对于除特殊情况y = 1 - xx1 == -8之外的所有值,请x2 == -7。因此整个散列函数变为

y = x <= -7 ? S(x) : x <= 0 ? 1 - x : x;

此处S(x)是一个简单的函数,说明x1x2的映射位置。根据您对数据的了解选择S。例如,如果您认为不太可能出现高目标值,请使用S(x) = -1 - x将它们映射到6和7。

最终的映射是:

-8: 7    -7: 6    -6: 7    -5: 6    -4: 5    -3: 4    -2: 3    -1: 2
 0: 1     1: 1     2: 2     3: 3     4: 4     5: 5     6: 6     7: 7

将此逻辑提升到64位空间,您将拥有

y = (x <= Long.MIN_VALUE + 1) ? -1 - x : x <= 0 ? 1 - x : x;

在此框架内可以进行许多其他类型的调整。

答案 3 :(得分:1)

我会选择最简单但不是完全浪费时间的版本:

public static long postiveHash(final long hash) {
    final long result = hash & Long.MAX_VALUE;
    return (result != 0) ? result : (hash == 0 ? 1 : 2);
}

此实现为所有两个可能的输入支付一个条件操作:0和MIN_VALUE。这两个被赋予不同的值映射与第二个条件。我怀疑你得到了(代码)简单性和(计算)复杂性的更好组合。

当然,如果你可以忍受更差的发行版,那么会更简单。通过将空间限制为1/4而不是1/2 -1,您可以得到:

public static long badDistribution(final long hash) {
    return (hash & -4) + 1;
}

答案 4 :(得分:1)

如果值为正,则可以直接使用,否则,反转所有位:

x >= 0 ? hash = x : hash = x ^ Long.MIN_VALUE

但是,如果x的值相关(意味着:类似的对象产生x的相似值),则可以将此值加扰一点,可能与

hash = a * (hash + b) % (Long.MAX_VALUE) + 1

对于某些正常量ab,其中a应该非常大而b会阻止0始终映射到1 }。这也将整个事物映射到[1,Long.MAX_VALUE]而不是[0,Long.MAX_VALUE]。通过更改ab的值,您还可以实现更复杂的哈希函数,例如cooko hashing,这需要两个不同的哈希函数。

这种解决方案绝对应该是首选,而不是每次使用时为相同值提供“奇怪的碰撞分布”的解决方案。

答案 5 :(得分:1)

您可以使用unsigned shift运算符在没有任何条件和单个表达式的情况下执行此操作:

public static int makePositive(int x) {
  return (x >>> 1) + (~x >>> 31);
}

答案 6 :(得分:0)

只是为了确保,你有一个很长的想要将它哈希到一个int?

你可以......

(int) x                 // This results in a meaningless number, but it works
(int) (x & 0xffffffffl) // This will give you just the low order bits
(int) (x >> 32)         // This will give you just the high order bits
((Long) x).hashcode()   // This is the high and low order bits XORed together

如果你想保持很长时间,你可以做...

x & 0x7fffffffffffffffl // This will just ignore the sign, Long.MIN_VALUE -> 0
x & Long.MAX_VALUE      // Should be the same I think

如果得到0就不行......

x & 0x7ffffffffffffffel + 1 // This has a 75% collision rate.

大声思考......

((x & Long.MAX_VALUE) << 1) + 1 // I think this is also 75%

我认为你需要75%或者有点丑陋:

(x > 0) ? x : (x < 0) ? x & Long.MAX_VALUE : 7

答案 7 :(得分:0)

这似乎是最简单的:

(x % Long.MAX_VALUE) + 1

我对所有方法的速度比较感兴趣。

答案 8 :(得分:0)

只需将您的输入值与Long.MAX_VALUE对比,并将其与1.一起使用。不需要任何其他内容。

例如:

long hash = (input & Long.MAX_VALUE) | 1;