我有一个整数类型,比如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
但是这些和类似的方法总是会遇到某些值的问题,即当x
为0
/ Long.MIN_VALUE
/ Long.MAX_VALUE
时。当然,天真的解决方案是使用2 if语句,但我正在寻找更清洁/更短/更快的东西。有什么想法吗?
注意:假设我在Java中工作,没有隐式转换为boolean并定义了shift语义。
答案 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位值,称为x1
和x2
,每个值与另外两个源值共享一个目标。在上面的mod
表达式中,这通过“换行”发生。目标值y=2^31-2
和y=2^31-3
有三个映射。所有其他人都有两个。由于我们不得不使用比mod
更复杂的东西,让我们寻找一种方法,以低成本在任何我们喜欢的地方映射特殊值
为了说明,我们可以将[-8..7]中的4位有符号整数x
映射到[1..7]中的y
,而不是64位空间。
一个简单的方法是将[1..7]中的x
值映射到自己,然后问题就会缩小到[-8..0]中的x
到y
的映射在[1..7]。注意,这里有9个源值,如上所述只有7个目标。
显然有很多策略。此时你可能会看到一个gazzilion。我只会描述一个特别简单的。
对于除特殊情况y = 1 - x
和x1 == -8
之外的所有值,请x2 == -7
。因此整个散列函数变为
y = x <= -7 ? S(x) : x <= 0 ? 1 - x : x;
此处S(x)
是一个简单的函数,说明x1
和x2
的映射位置。根据您对数据的了解选择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
对于某些正常量a
和b
,其中a
应该非常大而b
会阻止0
始终映射到1
}。这也将整个事物映射到[1,Long.MAX_VALUE]而不是[0,Long.MAX_VALUE]。通过更改a
和b
的值,您还可以实现更复杂的哈希函数,例如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;