当前位置: 首页 > 面试题库 >

Python Frozenset哈希算法/实现

薛文斌
2023-03-14
问题内容

我目前正在尝试了解为Python的内置frozenset数据类型定义的哈希函数背后的机制。该实现显示在底部,以供参考。我特别感兴趣的是选择此分散操作的原理:

lambda h: (h ^ (h << 16) ^ 89869747) * 3644798167

h每个元素的哈希值在哪里。有人知道这些来自哪里吗?(也就是说,是否有任何特定的原因来选择这些数字?)还是只是简单地任意选择了它们?

这是来自官方CPython实现的代码片段,

static Py_hash_t
frozenset_hash(PyObject *self)
{
    PySetObject *so = (PySetObject *)self;
    Py_uhash_t h, hash = 1927868237UL;
    setentry *entry;
    Py_ssize_t pos = 0;

    if (so->hash != -1)
        return so->hash;

    hash *= (Py_uhash_t)PySet_GET_SIZE(self) + 1;
    while (set_next(so, &pos, &entry)) {
        /* Work to increase the bit dispersion for closely spaced hash
           values.  The is important because some use cases have many
           combinations of a small number of elements with nearby
           hashes so that many distinct combinations collapse to only
           a handful of distinct hash values. */
        h = entry->hash;
        hash ^= (h ^ (h << 16) ^ 89869747UL)  * 3644798167UL;
    }
    hash = hash * 69069U + 907133923UL;
    if (hash == -1)
        hash = 590923713UL;
    so->hash = hash;
    return hash;
}

以及Python中的等效实现:

def _hash(self):
    MAX = sys.maxint
    MASK = 2 * MAX + 1
    n = len(self)
    h = 1927868237 * (n + 1)
    h &= MASK
    for x in self:
        hx = hash(x)
        h ^= (hx ^ (hx << 16) ^ 89869747)  * 3644798167
        h &= MASK
    h = h * 69069 + 907133923
    h &= MASK
    if h > MAX:
        h -= MASK + 1
    if h == -1:
        h = 590923713
    return h

问题答案:

除非Raymond
Hettinger(代码的作者)陷入困境,否则我们将永远无法确定;-)但是,这些东西中的“科学”通常比您期望的要少:您采用一些通用原则,一个测试套件,然后对它们进行弄乱。常量几乎是任意的,直到结果看起来“足够好”为止。

一些通用原则“显然”在这里起作用:

  1. 要获得所需的快速“位分散”,您需要乘以一个大整数。由于在许多平台上CPython的哈希结果必须适合32位,因此最好使用需要32位的整数。而且,的确(3644798167).bit_length() == 32

  2. 为避免系统地丢失低阶位,您需要乘以一个奇数整数。3644798167很奇怪。

  3. 更一般而言,为避免在输入哈希中增加模式,您需要乘以质数。3644798167是质数。

  4. 而且,您还需要一个乘法器,该乘法器的二进制表示形式没有明显的重复模式。 bin(3644798167) == '0b11011001001111110011010011010111'。那真是一团糟,这是一件好事;-)

在我看来,其他常量完全是任意的。的

if h == -1:
    h = 590923713

部分是由于另一个原因而需要的:在内部,CPython-1从整数值C函数获取一个返回值,表示“需要引发异常”;即,这是错误返回。因此,您将永远不会-1在CPython中看到任何对象的哈希码。返回的值而不是-1完全任意的-
每次只需要是 相同的 值(而不是-1)即可。

编辑:玩

我不知道雷蒙德用什么来测试。这是我将要使用的:查看一组连续整数的所有子集的哈希统计信息。这些是有问题的,因为hash(i) == i有很多整数i

>>> all(hash(i) == i for i in range(1000000))
True

简单地将异或散列在一起将对此类输入产生大量抵消。

因此,这里有一个小函数可以生成所有子集,另一个函数是对所有哈希码进行简单的异或运算:

def hashxor(xs):
    h = 0
    for x in xs:
        h ^= hash(x)
    return h

def genpowerset(xs):
    from itertools import combinations
    for length in range(len(xs) + 1):
        for t in combinations(xs, length):
            yield t

然后是一个驱动程序,以及一个显示碰撞统计信息的小功能:

def show_stats(d):
    total = sum(d.values())
    print "total", total, "unique hashes", len(d), \
          "collisions", total - len(d)

def drive(n, hasher=hashxor):
    from collections import defaultdict
    d = defaultdict(int)

    for t in genpowerset(range(n)):
        d[hasher(t)] += 1
    show_stats(d)

使用简单易用的哈希器是灾难性的:

>> drive(20)
total 1048576 unique hashes 32 collisions 1048544

kes!OTOH_hash()在这种情况下使用专为冷冻食品设计的产品可以完美地完成工作:

>>> drive(20, _hash)
total 1048576 unique hashes 1048576 collisions 0

然后,您可以尝试使用该工具,看看究竟有什么作用-以及没有-真正的作用_hash()。例如,如果

    h = h * 69069 + 907133923

已移除。而且我不知道为什么会有那条线。同样,如果^ 89869747删除了内部循环中的,它将继续在这些输入上做完美的工作-
不知道为什么也是如此。初始化可以从以下方式更改:

    h = 1927868237 * (n + 1)

至:

    h = n

这里也没有伤害。所有这些都符合我的期望:由于已经说明的原因,至关重要的是内部循环中的乘法常数。例如,向其添加1(使用3644798168),然后它不再是质数或奇数,并且统计信息降级为:

total 1048576 unique hashes 851968 collisions 196608

仍然相当有用,但是绝对更糟。将其更改为较小的素数,例如13,更糟糕的是:

total 1048576 unique hashes 483968 collisions 564608

使用具有明显二进制模式(例如)的乘法器0b01010101010101010101010101010101,然后再次恶化:

total 1048576 unique hashes 163104 collisions 885472

玩吧!这些东西很有趣:-)



 类似资料:
  • 问题内容: 我想知道什么是Java哈希算法的最佳和最快实现,尤其是MD5和SHA-2 512(SHA512)或256。我想要一个函数来获取字符串作为参数并返回哈希作为结果。谢谢你 编辑:这是用于将每个URL映射到唯一的哈希。由于MD5在这方面的可靠性不高,因此我对寻找SHA-2算法的最佳和最快实现更感兴趣。请注意,我知道即使SHA-2可能也会为某些URL产生相同的哈希,但是我可以接受。 问题答案:

  • 主要内容:哈希表是什么,哈希查找算法哈希查找算法又称 散列查找算法,是一种借助哈希表(散列表)查找目标元素的方法,查找效率最高时对应的时间复杂度为 O(1)。 哈希查找算法适用于大多数场景,既支持在有序序列中查找目标元素,也支持在无序序列中查找目标元素。讲解哈希查找算法之前,我们首先要搞清楚什么是哈希表。 哈希表是什么 哈希表(Hash table)又称 散列表,是一种存储结构,通常用来存储多个元素。 和其它存储结构(线性表、树等)

  • 本文向大家介绍一致性哈希算法?相关面试题,主要包含被问及一致性哈希算法?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 一致性哈希算法在1997年由麻省理工学院提出,设计目标是为了解决因特网中的热点(Hot pot)问题,初衷和CARP(缓冲阵列路由协议,Cache Array Routing Protocol)十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT(D

  • 你需要在这个练习中实现下面这三个哈希函数: FNV-1a 以创造者Glenn Fowler、Phong Vo 和 Landon Curt Noll的名字命名。这个算法产生合理的数值并且相当快。 Adler-32 以Mark Adler命名。一个比较糟糕的算法,但是由来已久并且适于学习。 DJB Hash 由Dan J. Bernstein (DJB)发明的哈希算法,但是难以找到这个算法的讨论。它非

  • 一致性哈希算法 tencent2012笔试题附加题 问题描述: 例如手机朋友网有n个服务器,为了方便用户的访问会在服务器上缓存数据,因此用户每次访问的时候最好能保持同一台服务器。 已有的做法是根据ServerIPIndex[QQNUM%n]得到请求的服务器,这种方法很方便将用户分到不同的服务器上去。但是如果一台服务器死掉了,那么n就变为了n-1,那么ServerIPIndex[QQNUM%n]与S

  • 本文向大家介绍PHP哈希表实现算法原理解析,包括了PHP哈希表实现算法原理解析的使用技巧和注意事项,需要的朋友参考一下 在PHP内核中,其中一个很重要的数据结构就是HashTable。我们常用的数组,在内核中就是用HashTable来实现。那么,PHP的HashTable是怎么实现的呢?最近在看HashTable的数据结构,但是算法书籍里面没有具体的实现算法,刚好最近也在阅读PHP的源码,于是参考