【问题标题】:When is hash(n) == n in Python?Python 中的 hash(n) == n 什么时候出现?
【发布时间】:2016-10-03 09:51:20
【问题描述】:

我一直在玩 Python 的 hash function。对于小整数,它总是出现hash(n) == n。然而,这并没有扩展到大量:

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

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

我尝试使用binary search 找到最小的数字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

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

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

【问题讨论】:

  • 你检查过数字的二进制表示吗?
  • '0b1111111111111111111111111111111111111111111111111111111111111'很好奇!所以n+1 == 2**61-1
  • 似乎与系统相关。使用我的 python,整个 64 位 int 范围的哈希为 n
  • 请注意散列值的规定用途:它们用于在字典查找期间快速比较字典键。 换句话说,实现定义的,并且由于是比许多可以具有哈希值的值短,即使在合理的输入空间中也很可能发生冲突。
  • 嗯,不是2147483647等于sys.maxint(未sys.maxint+1),并且如果 'N = 0b1111111111111111111111111111111111111111111111111111111111111' 然后不n+1 == 2**61n == 2**61-1(未n+1 == 2**61-1)?

标签: python python-2.7 python-3.x hash python-internals


【解决方案1】:

哈希函数返回plain 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 次,直到它停止在该范围内的纯整数上,就像上面的代码 sn-ps ..

一般来说,对于任何 n :

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

注意:这适用于 python 2。

【讨论】:

  • 这可能适用于 Python 2,但绝对不适用于 Python 3(它没有 sys.maxint,并且使用不同的哈希函数)。
【解决方案2】:

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

对于数字类型,数字 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 平台中,减少是 261-1,即2305843009213693951

>>> 2**61 - 1
2305843009213693951

您还可以使用math.frexp 来获取sys.maxint 的尾数和指数,这对于64 位机器显示最大int 为263

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

【讨论】:

  • 为了完整起见,最好提及sys.hash_info
【解决方案3】:

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

如果您必须仅通过取值 mod 某个数字来进行散列,那么大梅森素数是一个不错的选择 - 它易于计算并确保可能性的均匀分布。 (虽然我个人永远不会这样散列)

计算浮点数的模数特别方便。它们有一个指数分量,将整数乘以2^x。由于2^61 = 1 mod 2^61-1,你只需要考虑(exponent) mod 61

见:https://en.wikipedia.org/wiki/Mersenne_prime

【讨论】:

  • 你说你永远不会用这种方式做哈希。您是否有替代建议,以使计算整数、浮点数、小数、分数确保x == y保证hash(x) == hash(y)跨类型的计算效率合理? (像Decimal('1e99999999') 这样的数字尤其成问题,例如:您不想在散列之前将它们扩展为相应的整数。)
  • @MarkDickinson 我怀疑他试图区分这种简单的闪电式快速散列和加密散列也关心使输出看起来随机。
  • @MarkDickinson 模数是一个好的开始,但我会将它混合起来,尤其是将一些高位混合到低位。整数序列可被 2 的幂整除的情况并不少见。容量为 2 的幂的哈希表也并不罕见。例如,在 Java 中,如果您有一个可被 16 整除的整数序列,并且您将它们用作 HashMap 中的键,您将只使用 1/16 的存储桶(至少在我正在查看的源版本中)!我认为哈希应该至少看起来有点随机以避免这些问题
  • 是的,位混合风格的散列远优于数学启发的散列。位混合指令非常便宜,您可以以相同的成本拥有许多指令。此外,现实世界的数据似乎没有适用于位混合的模式。但是有些模式对于模数来说是可怕的。
  • @usr: 当然可以,但是在这里混合位哈希是不可行的:要求哈希适用于 intfloatDecimalFraction 对象以及 @987654334 @ 暗示 hash(x) == hash(y) 即使 xy 具有不同的类型也会施加一些相当严格的约束。如果只是为整数编写一个哈希函数,而不用担心其他类型,那将是完全不同的事情。
【解决方案4】:

implementation for the int type in cpython can be found here.

它只返回值,除了-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;
}

【讨论】:

  • 这不包括大值,由PyLong而不是PyInt实现。
猜你喜欢
  • 1970-01-01
  • 2010-09-08
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-28
  • 2016-08-31
  • 1970-01-01
相关资源
最近更新 更多