什么时候在Python中hash(n)== n?


100

我一直在玩Python的hash函数。对于小整数,它hash(n) == n总是出现。但是,这不会扩展为大量:

>>> hash(2**100) == 2**100
False

我并不感到惊讶,我知道哈希值取值范围有限。这个范围是多少?

我尝试使用二进制搜索来找到最小的数字hash(n) != n

>>> import codejamhelpers # pip install codejamhelpers
>>> help(codejamhelpers.binary_search)
Help on function binary_search in module codejamhelpers.binary_search:

binary_search(f, t)
    Given an increasing function :math:`f`, find the greatest non-negative integer :math:`n` such that :math:`f(n) \le t`. If :math:`f(n) > t` for all :math:`n \ge 0`, return None.

>>> f = lambda n: int(hash(n) != n)
>>> n = codejamhelpers.binary_search(f, 0)
>>> hash(n)
2305843009213693950
>>> hash(n+1)
0

2305843009213693951有什么特别之处?我注意到它小于sys.maxsize == 9223372036854775807

编辑:我正在使用Python3。我在Python 2上运行了相同的二进制搜索,得到了不同的结果2147483648,我注意到这是 sys.maxint+1

我也玩过[hash(random.random()) for i in range(10**6)]以估计哈希函数的范围。最大值始终低于上面的n。比较最小值,似乎Python 3的哈希值始终为正值,而Python 2的哈希值可以为负值。


9
您是否检查过数字的二进制表示形式?
John Dvorak

3
'0b11111111111111111111111111111111111111111111111111111111111111'很好奇!所以 n+1 == 2**61-1
上校恐慌

2
似乎与系统有关。对于我的python,散列n用于整个64位int范围。
丹尼尔(Daniel)

1
请注意哈希值的规定用途:它们用于在字典查找期间快速比较字典关键字。换句话说,实现定义的并且由于比许多可以具有哈希值的值短,即使在合理的输入空间中也很可能发生冲突。
CVn

2
嗯,不2147483647等于sys.maxint(不sys.maxint+1),并且如果“N = 0b1111111111111111111111111111111111111111111111111111111111111”那么也不n+1 == 2**61n == 2**61-1(未n+1 == 2**61-1)?
phoog,2016年

Answers:


73

基于文件中的python文档pyhash.c

对于数字类型,数字x的哈希值是基于对x的减乘以模数质数得出的P = 2**_PyHASH_BITS - 1。它的设计使 hash(x) == hash(y)x和y在数值上相等时,即使x和y具有不同的类型。

因此,对于64/32位计算机,减少量将为2 _PyHASH_BITS -1,但是什么是_PyHASH_BITS

您可以在pyhash.h头文件中找到该文件,对于64位计算机,该头文件已定义为61(您可以在pyconfig.h文件中阅读更多说明)。

#if SIZEOF_VOID_P >= 8
#  define _PyHASH_BITS 61
#else
#  define _PyHASH_BITS 31
#endif

因此首先基于您的平台,例如在我的64位Linux平台上,减少幅度是2 61 -1,即2305843009213693951

>>> 2**61 - 1
2305843009213693951

也可以使用math.frexp来获取尾数和尾数sys.maxint,对于64位机器,该尾数和尾数表明max int为2 63

>>> import math
>>> math.frexp(sys.maxint)
(0.5, 64)

您可以通过一个简单的测试来查看差异:

>>> hash(2**62) == 2**62
True
>>> hash(2**63) == 2**63
False

阅读有关python哈希算法的完整文档https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

如注释中所述,您可以使用sys.hash_info(在python 3.X中),这将为您提供用于计算哈希的参数的结构序列。

>>> sys.hash_info
sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash24', hash_bits=64, seed_bits=128, cutoff=0)
>>> 

除了我在前inf几行中描述的模数之外,您还可以获得以下值:

>>> hash(float('inf'))
314159
>>> sys.hash_info.inf
314159

3
sys.hash_info出于完整性考虑,很高兴提及。
Mark Dickinson

78

23058430092136939512^61 - 1。它是最大的Mersenne素数,适合64位。

如果您只需要将值mod取一个数字就可以进行哈希处理,那么大的Mersenne素数是一个不错的选择-它易于计算并且可以确保可能性的均匀分布。(尽管我个人永远不会这样散列)

计算浮点数的模数特别方便。它们具有将整数乘以的指数成分2^x。既然2^61 = 1 mod 2^61-1,您只需要考虑(exponent) mod 61

请参阅:https//en.wikipedia.org/wiki/Mersenne_prime


8
您说您永远不会这样散列。您是否有其他建议,说明如何以合理的方式有效地计算int,float,Decimals和Fractions 确保跨类型的x == y保证hash(x) == hash(y)?(Decimal('1e99999999')例如,数字特别成问题:例如,您不需要在散列之前将其扩展为相应的整数。)
Mark Dickinson

@MarkDickinson我怀疑他正在尝试在这种简单的快速哈希值与也关心使输出看起来随机的加密哈希值之间进行区分。
Mike Ounsworth

4
@MarkDickinson模数是一个很好的开始,但是我会再将其混合使用,尤其是将一些高位混合到低位。看到整数序列可以被2的整数次除的情况并不少见。看到散列表具有2的幂的情况也很常见。例如,在Java中,如果您有一个整数序列可以被16整除,并且如果将它们用作HashMap中的键,则只会使用1/16个存储桶(至少在我正在查看的源版本中)!我认为散列应该至少看起来有些随意,以避免出现这些问题
Matt Timmermans

是的,位混合样式的哈希值远远优于数学启发式的哈希值。位混合指令是如此便宜,以至于您可以以相同的成本获得许多指令。而且,现实世界的数据似乎没有与比特混合不能很好地工作的模式。但是有些模式对模数来说是可怕的。
usr

9
@usr:当然可以,但有点混合哈希是不可行的位置:需求,对于散列工作intfloatDecimalFraction对象和x == y暗示hash(x) == hash(y),即使xy有不同类型强加一些比较严格的限制。如果只是为整数编写一个哈希函数而不必担心其他类型,那将是完全不同的事情。
Mark Dickinson

9

哈希函数返回的是纯整数int,这意味着返回的值大于-sys.maxint和小于sys.maxint,这意味着如果传递sys.maxint + x给它,结果将为-sys.maxint + (x - 2)

hash(sys.maxint + 1) == sys.maxint + 1 # False
hash(sys.maxint + 1) == - sys.maxint -1 # True
hash(sys.maxint + sys.maxint) == -sys.maxint + sys.maxint - 2 # True

同时2**200n倍大于sys.maxint-我的猜测是,哈希将范围去了-sys.maxint..+sys.maxint,直到它停止在普通整数在这个范围内,如上面的代码段n次..

因此,通常,对于任何n <= sys.maxint

hash(sys.maxint*n) == -sys.maxint*(n%2) +  2*(n%2)*sys.maxint - n/2 - (n + 1)%2 ## True

注意:这适用于python 2。


8
这对于Python 2可能是正确的,但对于Python 3绝对不是(它没有sys.maxint,并且使用不同的哈希函数)。
interjay 2013年

0

可以在这里找到cpython中int类型实现。

它只返回值,除了-1,则返回-2

static long
int_hash(PyIntObject *v)
{
    /* XXX If this is changed, you also need to change the way
       Python's long, float and complex types are hashed. */
    long x = v -> ob_ival;
    if (x == -1)
        x = -2;
    return x;
}

6
这不包括大的值,这是由执行PyLong,而不是PyInt
interjay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.