【问题标题】:hash function providing unique uint from an integer coordinate pair哈希函数提供来自整数坐标对的唯一 uint
【发布时间】:2010-10-15 12:14:02
【问题描述】:

一般问题: 我有一个大的 2d 点空间,点点稀疏。 把它想象成一块洒满黑点的白色大画布。 我必须遍历并搜索这些点很多。 画布(点空间)可能很大,接近极限 int 的大小,在设置点之前它的大小是未知的。

这让我想到了散列:

理想: 我需要一个采用 2D 点的散列函数,返回一个唯一的 uint32。 这样就不会发生碰撞。 你可以假设 Canvas 上的点很容易被 uint32 数数。

重要提示:不可能事先知道画布的大小 (它甚至可能改变), 所以像

画布宽度 * y + x

很遗憾,这是不可能的。

我也试过很幼稚的

abs(x) + abs(y)

但这会产生太多的冲突。

妥协: 一种散列函数,可为键提供非常低碰撞概率。

有什么想法吗?感谢您的帮助。

最好的问候, 安德烈亚斯 T.

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

我希望这没问题,因为所有答案在更新的假设下仍然最有意义

对不起。

【问题讨论】:

标签: hash hashtable


【解决方案1】:

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

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

亚马逊的开源地理库计算任何经纬度坐标的哈希值。生成的 Geohash 值为 63 位数字。冲突的概率取决于散列的分辨率:如果两个对象比固有分辨率更接近,则计算出的散列将是相同的。

阅读更多:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      斐波那契散列非常适合整数对

      乘数 0x9E3779B9

      其他字长 1/phi = (sqrt(5)-1)/2 * 2^w 取整为奇数

      a1 + a2*乘数

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

      我不知道所有对的结果

      【讨论】:

        【解决方案4】:

        你可以的

        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 以获取大量选项..

        【讨论】:

          【解决方案5】:

          康托尔的enumeration of pairs

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

          可能很有趣,因为它最接近您的原始画布宽度 * y + x,但适用于任何 x 或 y。但是对于现实世界的 int32 哈希,而不是整数对到整数的映射,您可能最好使用 Bob Jenkin 的mix 之类的位操作并使用 x,y 和盐调用它。

          【讨论】:

          • +1。我的回答执行起来更快,但我向您的出色回答致敬! :-)
          • 谢谢,这个散列的优点是它在实践中比 (y
          【解决方案6】:

          如果您已经在使用所有对象(甚至像整数这样的原始对象)都已实现内置哈希函数的语言或平台(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();
          }
          
          }
          

          还有像“预定义的百万点集”这样的测试用例针对不同方面的每个可能的哈希生成算法进行比较,比如计算时间、所需内存、键冲突计数和边缘情况(值太大或太小)可能得心应手。

          【讨论】:

          • 这与 Jason 对给定问题的解决方案相同
          【解决方案7】:

          如果你能做到 = ((y & 0xffff)

          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;
          }
          

          这样你得到一个看起来随机的结果,而不是一个维度的高位和另一个维度的低位。

          【讨论】:

          【解决方案8】:

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

          您想要一个映射 (x, y) -> 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 位)、非常好(使用加密哈希函数),并且您感觉很幸运。

          【讨论】:

          • 是的,谢谢。我知道这一点。不过,我的问题是,与画布大小相比,画布上的点数非常少,所以我认为,应该有一些方法可以保持无碰撞。一些巧妙的编码技巧,什么的。
          【解决方案9】:

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

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

          如果您想将两个 uint32 散列到一个 uint32 中,请不要使用 Y & 0xFFFF 之类的东西,因为这会丢弃一半的位。做类似的事情

          (x * 0x1f1f1f1f) ^ y
          

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

          【讨论】:

          • 嗨,据我所知,散列函数只需提供从较大空间到较小(散列)空间的满射映射。您的功能看起来很有趣,不幸的是我不明白这些功能在这里有什么帮助?看起来它在墙上运行了至少 8 位 x (?)
          • “据我所知,散列函数只需提供从较大空间到较小(散列)空间的满射映射”。我认为这是对“保证无冲突的哈希函数不是哈希函数”的回复。两者都是正确的 - 为证明,请参阅en.wikipedia.org/wiki/Counting_argument
          • x * 0x1f1f1f1f 不会将 x 的任何位运行到墙上,乘以奇数模 2**32 是双射映射!
          【解决方案10】:

          与 Emil 类似,但在 x 中处理 16 位溢出的方式会产生更少的冲突,并且需要更少的指令来计算:

          hash = ( y << 16 ) ^ x;
          

          【讨论】:

          • 谢谢,我只是想评论一下为什么我不喜欢这个,但是当我读到这个并操作我的大脑进行改变时,我明白了,在上述假设下,我无法拥有更大的画布比 sqrt(MAX_UINT) 的 2 次方,并且可以唯一地存储在 2x16 位...
          • 如果你知道原始的 x 和 y 是 16 位的,这是很好的可逆的。唯一的问题是,由于符号位是特殊处理的,所以当可能出现负 x 时,您必须小心一点。我想确切的实现将取决于您的语言如何处理符号位,以及它如何定义&gt;&gt;&lt;&lt;。但在 javascript 中,它很简单:y= hash&gt;&gt;16; x=(hash&lt;&lt;16)&gt;&gt;16; if (x&lt;0) { y = y^-1 }.
          【解决方案11】:

          也许?

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

          只要 x 和 y 可以存储为 16 位整数即可。不过,不知道这会导致多少次碰撞导致更大的整数。一个想法可能是仍然使用该方案,但将其与压缩方案结合起来,例如取 2^16 的模数。

          【讨论】:

            猜你喜欢
            • 2016-12-07
            • 1970-01-01
            • 2013-09-12
            • 2018-06-23
            • 1970-01-01
            • 2019-07-28
            • 2014-05-14
            • 2014-05-11
            • 2014-05-11
            相关资源
            最近更新 更多