哈希函数从整数坐标对提供唯一的uint

时间:2009-03-25 16:49:02

标签: hash hashtable

一般问题: 我有一个很大的2d点空间,人口稀疏点。 可以把它想象成一个涂有黑点的大白色帆布。 我必须迭代并搜索这些点很多。 画布(点空间)可以是巨大的,接近极限 在设置点之前,int及其大小是未知的。

这让我想到了哈希的想法:

理想: 我需要一个带2D点的哈希函数,返回一个唯一的uint32。 这样就不会发生碰撞。 你可以假设的数量 uint32很容易计算画布上的点数。

重要事项:事先无法知道画布的大小 (甚至可能会改变),

之类的东西

canvaswidth * y + x

遗憾的是,这是不可能的。

我也试过很天真的

abs(x)+ abs(y)

但是会产生太多的碰撞。

妥协: 一个哈希函数,提供非常低冲突概率的密钥。

任何想法?谢谢你的帮助。

祝你好运, 安德烈亚斯。

编辑: 我不得不改变问题文本中的内容: 我改变了“能够计算画布点数的假设” 使用uint32“into”能够计算画布上的点数(或者通过uint32计算要存储的坐标对的数量)。 我的原始问题没有多大意义,因为我会有一个sqrt(max(uint32))xsqrt(max(uint32))大小的画布,这是唯一可表示的 通过16位移位和OR。

我希望这没关系,因为所有答案对于更新的假设仍然最有意义

很抱歉。

11 个答案:

答案 0 :(得分:28)

Cantor的enumeration of pairs

   n = ((x + y)*(x + y + 1)/2) + y

可能很有趣,因为它最接近原始的canvaswidth * y + x,但适用于任何x或y。但是对于真实世界的int32哈希,而不是整数对到整数的映射,你可能会更好地使用一些操作,例如Bob Jenkin的mix并用x,y和salt调用它。 / p>

答案 1 :(得分:16)

GUARANTEED无冲突的哈希函数不是哈希函数:)

您可以考虑使用二进制空间分区树(BSP)或XY树(密切相关),而不是使用哈希函数。

如果你想将两个uint32散列到一个uint32中,请不要使用像Y& 0xFFFF因为丢弃了一半的比特。做点什么

(x * 0x1f1f1f1f) ^ y

(您需要首先转换其中一个变量以确保散列函数不可交换)

答案 2 :(得分:4)

与Emil一样,但处理x中的16位溢出的方式产生的冲突更少,并且计算的指令更少:

hash = ( y << 16 ) ^ x;

答案 3 :(得分:2)

你的“理想”是不可能的。

你想要一个映射(x,y) - &gt; i其中x,y和i都是32位数量,保证不会生成i的重复值。

原因如下:假设有一个函数hash(),以便hash(x,y)给出不同的整数值。 x的值为2 ^ 32(约40亿),y的值为2 ^ 32。所以hash(x,y)有2 ^ 64(约1600万亿)个可能的结果。但是32位int中只有2 ^ 32个可能的值,因此hash()的结果将不适合32位int。

另见http://en.wikipedia.org/wiki/Counting_argument

通常,您应该始终设计数据结构来处理冲突。 (除非你的哈希很长(至少128位),非常好(使用加密哈希函数),你感觉很幸运。)

答案 4 :(得分:1)

也许?

hash = ((y & 0xFFFF) << 16) | (x & 0xFFFF);

只要x和y可以存储为16位整数,就可以正常工作。但是,不知道这会导致更大整数的碰撞次数。一个想法可能仍然是使用此方案,但将其与压缩方案相结合,例如采用2 ^ 16的模数。

答案 5 :(得分:1)

如果你能做=((y&amp; 0xffff)&lt;&lt; 16)| (x&amp; 0xffff)然后你可以将一个可逆的32位混音应用到一个,比如Thomas Wang的

uint32_t hash( uint32_t a)
    a = (a ^ 61) ^ (a >> 16);
    a = a + (a << 3);
    a = a ^ (a >> 4);
    a = a * 0x27d4eb2d;
    a = a ^ (a >> 15);
    return a;
}

这样你就可以得到一个随机的结果,而不是一维的高位和另一维的低位。

答案 6 :(得分:1)

根据您的用例,可以使用Quadtree并将点替换为分支名称字符串。它实际上是点的稀疏表示,并且需要一个自定义的四叉树结构,当您从画布上添加点时添加分支来扩展画布,但它避免了碰撞,并且您将获得快速最近邻搜索等好处。

答案 7 :(得分:1)

您可以递归地将XY平面划分为单元格,然后将这些单元格划分为子单元格等。

Gustavo Niemeyer于2008年发明了他的Geohash地理编码系统。

亚马逊的开源地理图书馆计算任何经度 - 纬度坐标的哈希值。生成的Geohash值是63位数。碰撞的概率取决于散列的分辨率:如果两个对象比内在分辨率更接近,则计算的散列将是相同的。

enter image description here

了解更多:

https://en.wikipedia.org/wiki/Geohash https://aws.amazon.com/fr/blogs/mobile/geo-library-for-amazon-dynamodb-part-1-table-structure/ https://github.com/awslabs/dynamodb-geo

答案 8 :(得分:0)

如果你已经在使用语言或平台,那么所有对象(甚至像整数这样的原始对象)都会实现内置的哈希函数(Java平台语言,如Java,.NET平台语言,如C#。还有其他像Python,Ruby,等)。 您可以使用内置散列值作为构建块,并将“散列风格”添加到混合中。像:

// C# code snippet 
public class SomeVerySimplePoint { 

public int X;
public int Y;

public override int GetHashCode() {
   return ( Y.GetHashCode() << 16 ) ^ X.GetHashCode();
}

}

还有针对每个可能的哈希生成算法比较的“预定义百万点集”运行的测试用例,例如计算时间,所需内存,密钥冲突计数和边缘情况(值太大或太小)方便。

答案 9 :(得分:0)

你可以做到

a >= b ? a * a + a + b : a + b * b

taken from here

适用于正平面中的点。如果您的坐标也可以在负轴上,那么您将不得不这样做:

A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
A >= B ? A * A + A + B : A + B * B;

但要将输出限制为uint,您必须保持输入的上限。如果是这样,那么事实证明你知道边界。换句话说,在编程中编写一个函数是不切实际的,而不需要输入和输出的整数类型,如果是这样,那么每个整数类型肯定会有一个下限和上限。

public uint GetHashCode(whatever a, whatever b)
{
    if (a > ushort.MaxValue || b > ushort.MaxValue || 
        a < ushort.MinValue || b < ushort.MinValue)
    {    
        throw new ArgumentOutOfRangeException();
    }

    return (uint)(a * short.MaxValue + b); //very good space/speed efficiency
    //or whatever your function is.
}

如果您希望输出对于未知输入范围严格uint,则根据该范围将有合理数量的冲突。我建议的是有一个可以溢出但未选中的函数。 Emil的解决方案很棒,在C#中:

return unchecked((uint)((a & 0xffff) << 16 | (b & 0xffff))); 

请参阅Mapping two integers to one, in a unique and deterministic way了解众多选项..

答案 10 :(得分:0)

Fibonacci哈希对整数对非常有效

乘数0x9E3779B9

其他字数1 / phi =(sqrt(5)-1)/ 2 * 2 ^ w round to odd

a1 + a2 *乘数

这将为近距离对提供非常不同的值

我不知道所有配对的结果