【问题标题】:How to create a good hash_combine with 64 bit output (inspired by boost::hash_combine)如何创建具有 64 位输出的良好 hash_combine(受 boost::hash_combine 启发)
【发布时间】:2012-01-20 18:49:14
【问题描述】:

目前 Boost 具有 hash_combine 函数,可以输出 32 位无符号整数(准确地说是 size_t)。一些参考资料:

http://www.boost.org/doc/libs/1_43_0/doc/html/hash/reference.html#boost.hash_combine

http://www.boost.org/doc/libs/1_43_0/doc/html/hash/combine.html

Magic number in boost::hash_combine

我想探讨一下如何创建 64 位版本的 hash_combine。

首先要得到 64 位的黄金比例或任何其他无理数。

第二部分是使用班次。这部分相当棘手,我想问一下是否有使用轮班获取哈希值的最佳实践或指南?或者像原始代码一样选择班次:

seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 

完全随机?

还有如何评估hash_combine 的输出以确保它不会比原始哈希函数hash_value 产生更多的冲突?

【问题讨论】:

  • 2^64/φ是0x9E3779B97F4A7C15
  • 感谢 Kerrrek。找到价值不是问题。我感兴趣的是是否有任何规则或最佳实践来使用您在 boost::hash_combine 中看到的移位和添加。或者选择班次和加班是完全随机的。
  • 我觉得你应该file a bug report
  • 嗨,Kenny,我在问一些概念,这些概念可以让我在理解它们之后进行写作。

标签: c++ boost hash


【解决方案1】:

阅读http://burtleburtle.net/bob/hash/doobs.html 了解有关散列函数设计的一些基本信息,阅读http://burtleburtle.net/bob/hash/ 中的其余文章了解更多详细信息。 CityHash 是使用 http://code.google.com/p/smhasher/ 测试的,您可能可以使用相同的测试套件测试您的 hash_combine

虽然我不是散列方面的专家,但最近的散列函数设计让我相信 hash_combine() 使用的 2-shift 技术提升不再是最先进的,可以改进。

【讨论】:

    【解决方案2】:

    如果您只想要一个将 2 个 64 位值散列为一个的 hash_combine,并且不需要新的字符串散列函数,则可以从 CityHash 中提取一小部分代码,如下所示(假设 size_t 为一个 64 位无符号整数,添加您最喜欢的预处理器或模板技巧来验证):

    template <class T> inline void hash_combine(std::size_t& seed, const T& v)
    {
        std::hash<T> hasher;
        const std::size_t kMul = 0x9ddfea08eb382d69ULL;
        std::size_t a = (hasher(v) ^ seed) * kMul;
        a ^= (a >> 47);
        std::size_t b = (seed ^ a) * kMul;
        b ^= (b >> 47);
        seed = b * kMul;
    }
    

    (我认为在此处和其他地方复制此 sn-p 是可以的,因为它不构成 CityHash 代码的“实质性部分”,但请查看 CityHash 来源和许可协议自行决定)

    【讨论】:

    • 你的魔法常数不是 Kerred 提到的那个 0x9E3779B97F4A7C15 那么它来自哪里?
    【解决方案3】:

    boost::hash_combine 不是完全随机的,它甚至分布不均或particularly good

    组合两个散列的一个好方法是首先确保两个散列分布良好,然后您可以将两者与 xor 组合。为确保它们分布良好,请使用good integer hash function

    把它们放在一起你可能有:

    uint64_t xorshift(const uint64_t& n,int i){
      return n^(n>>i);
    }
    uint64_t hash(const uint64_t& n){
      uint64_t p = 0x5555555555555555; // pattern of alternating 0 and 1
      uint64_t c = 17316035218449499591ull;// random uneven integer constant; 
      return c*xorshift(p*xorshift(n,32),32);
    }
    uint64_t hash_combine(const uint64_t& seed, const uint64_t& v) {
      uint64_t c = 17316035218449499591ull;// random integer constant;
      return hash(v)^(seed+c);
    }
    

    如果散列的分布不足以满足您的目的,只需对值进行双重散列,可能像这样:hash(hash(v))^seed

    【讨论】:

    • 此代码在编程中常见的两个重要用例中失败:1) 排列。您提出的 hash_combine 无法区分散列值的不同顺序:hash_combine(hash_combine(0,1),2)==hash_combine(hash_combine(0,2),1)。例如,我需要在光线追踪任务中使用散列函数来散列光线命中对象的顺序。 2)零保存。 hash_combine(0,0)==0。这是非常危险的,因为 0 是最常见的值。提出的散列函数不能区分序列 (0) 与 (0,0) 和 (0,0,0)。这些缺陷加起来给出了 hash(1,0,2)==hash(2,1)==hash(0,0,0,1,2) 等等。
    • 你说Boost hash_combine不好,其实它没有这些缺陷。
    • @AntonSukhinov 这是真的。有几种解决方案,在位置添加 i' to the hashing. return hash(v*(2*i+1))^seed;` ,将散列函数应用于两倍宽度的整数,在这种情况下为 unsigned __int128 组成value 和 seed,每次乘以 3,返回之前再应用一个弱散列函数,我认为在散列中添加一个随机常数可能是最经济的。
    猜你喜欢
    • 2011-06-24
    • 2015-07-05
    • 2018-08-19
    • 1970-01-01
    • 2013-11-26
    • 2014-12-29
    • 2016-06-29
    • 1970-01-01
    相关资源
    最近更新 更多