【问题标题】:Hash function for 2D point in limited Euclidean space有限欧几里得空间中二维点的散列函数
【发布时间】:2014-02-04 09:46:10
【问题描述】:

我将许多具有地理位置的对象存储为以米为单位的 2D 点 (x,y)。为了代表世界,我使用了一个划分为 1 平方公里的单元格的网格。目前我正在为此使用HashMap<Position, Object>。任何其他地图或适当的数据结构都可以,但我的解决方案有效,所以我只对解决细节感兴趣。

我已经阅读了很多关于制作好的散列函数的文章,特别是对于 2D 点。到目前为止,还没有真正好的解决方案(被评为尽可能无碰撞)。

为了测试一些想法,我编写了一个非常简单的 java 程序来生成从任意数 (-1000,-1000) 到 (1000, 1000) (x1, y1 -> x2,y2) 的点的哈希码并存储它们在HashSet<Integer> 中,这是我的结果:

# java HashTest
4000000 number of unique positions
test1: 3936031 (63969 buckets, 1,60%) collisions using Objects.hash(x,y)
test2: 0 (4000000 buckets, 100,00%) collisions  using (x << 16) + y
test3: 3998000 (2000 buckets, 0,05%) collisions using x
test4: 3924037 (75963 buckets, 1,90%) collisions using x*37 + y
test5: 3996001 (3999 buckets, 0,10%) collisions using x*37 + y*37
test6: 3924224 (75776 buckets, 1,89%) collisions using x*37 ^ y
test7: 3899671 (100329 buckets, 2,51%) collisions using x*37 ^ y*37
test8: 0 (4000000 buckets, 100,00%) collisions using PerfectlyHashThem
test9: 0 (4000000 buckets, 100,00%) collisions using x << 16 | (y & 0xFFFF)

图例:碰撞次数,桶(碰撞),perc(碰撞)

这些哈希函数中的大多数都表现得很糟糕。事实上,唯一好的解决方案是将 x 移动到整数的前 16 位。我想,限制是两个最远的点不能超过Integer.MAX_INT的平方根,即面积必须小于46 340平方公里。

这是我的测试函数(只是为每个新的哈希函数复制):

  public void test1() {

    HashSet<Integer> hashCodes = new HashSet<Integer>();
    int collisions = 0;

    for (int x = -MAX_VALUE; x < MAX_VALUE; ++x) {
      for (int y = -MAX_VALUE; y < MAX_VALUE; ++y) {
        final int hashCode = Objects.hash(x,y);

        if (hashCodes.contains(hashCode))
          collisions++;

        hashCodes.add(hashCode);
      }
    }

    System.console().format("test1: %1$s (%2$s buckets, %3$.2f%%) collisions using Objects.hash(x,y)\n", collisions, buckets(collisions), perc(collisions));
  }

我在这里想错了吗?我应该微调素数以获得更好的结果吗?

编辑:

添加了更多哈希函数(test8 和 test9)。 test8 来自@nawfal 在Mapping two integers to one, in a unique and deterministic way 的回复(从short 转换为int)。

【问题讨论】:

  • 我现在也尝试过使用素数 71 和 97。桶的数量从 37 个几乎翻了一番。
  • 请尝试 31。我想看看结果。
  • 对于您的号码,应该可以创建 0 次冲突。 -1000 到 1000 乘以 -1000 到 1000 有 40 亿个不同的坐标。我认为您在某处做错了什么。请张贴代码。
  • 31 的结果比我使用 37 的示例稍微“差”一些。例如:test4: 3936031 (63969 个桶,1,60%) 使用 x*31 + y 的碰撞

标签: java hash hashcode


【解决方案1】:
public void test1() {

    int MAX_VALUE = 1000;

    HashSet<Integer> hashCodes = new HashSet<Integer>();
    int collisions = 0;

    for (int x = -MAX_VALUE; x < MAX_VALUE; ++x) {
        for (int y = -MAX_VALUE; y < MAX_VALUE; ++y) {
            final int hashCode = ((x+MAX_VALUE)<<16)|((y+MAX_VALUE)&0xFFFF);

            if (hashCodes.contains(hashCode))
                collisions++;

            hashCodes.add(hashCode);
        }
    }

    System.out.println("Collisions: " + collisions + " // Buckets: " +  hashCodes.size());
}

打印:碰撞:0 // 存储桶:4000000

【讨论】:

  • 谢谢!将其添加到测试列表中。它似乎与 test2 没有太大区别,只是将 y 截断为短?
  • 可能差不多,嗯。但是除了没有碰撞之外,您还想要什么?
  • 我更想知道这个理论是否正确。你看不出它有什么问题,或者我是如何测试它的?
  • 什么理论?另外,您没有说明如何计算 perc(collisions) 和 buckets(collision) 或它们是什么。我假设 buckets(collision) 是 2000x2000 - 碰撞只是给了一个坏名字?
  • 抱歉含糊不清。我将用一个似乎非常适合解决这个问题的答案更新我的帖子。
【解决方案2】:

我有一个类似的问题,答案是使用 Cantor 配对功能。这里: Mapping two integers to one, in a unique and deterministic way.

康托尔配对函数也可用于负整数,使用双射。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    • 2022-09-22
    • 2021-01-16
    相关资源
    最近更新 更多