web-dev-qa-db-fra.com

Quand hash (n) == n en Python?

J'ai joué avec la fonction de hachage de Python . Pour les petits entiers, il apparaît toujours hash(n) == n. Cependant, cela ne concerne pas les grands nombres:

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

Je ne suis pas surpris, je comprends que le hash prend une gamme finie de valeurs. Quelle est cette gamme?

J'ai essayé d'utiliser recherche binaire pour trouver le plus petit nombre 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

Quelle est la particularité de 2305843009213693951? Je remarque que c'est moins que sys.maxsize == 9223372036854775807

Edit: J'utilise Python 3. J'ai effectué la même recherche binaire sur Python 2 et obtenu un résultat différent, 2147483648, noté sys.maxint+1.

J'ai également joué avec [hash(random.random()) for i in range(10**6)] pour estimer l'étendue de la fonction de hachage. Le maximum est toujours inférieur à n ci-dessus. En comparant min, il semble que le hachage de Python 3 soit toujours valorisé, alors que celui de Python 2 peut prendre des valeurs négatives.

97
Colonel Panic

Basé sur la documentation python dans pyhash.c fichier:

Pour les types numériques, le hachage d'un nombre x est basé sur la réduction de x modulo le P = 2**_PyHASH_BITS - 1 premier. Il est conçu pour que hash(x) == hash(y) chaque fois que x et y sont numériquement égaux, même si x et y ont des types différents.

Donc, pour une machine 64/32 bits, la réduction serait de 2 _PyHASH_BITS  - 1, mais qu'est-ce que _PyHASH_BITS?

Vous pouvez le trouver dans pyhash.h fichier d’en-tête qui, pour un ordinateur 64 bits, a été défini sur 61 (vous pouvez en savoir plus dans le fichier pyconfig.h).

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

Donc tout d’abord, c’est basé sur votre plate-forme, par exemple dans ma plate-forme Linux 64 bits, la réduction est de 261-1, qui est 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Vous pouvez également utiliser math.frexp afin d'obtenir la mantisse et l'exposant de sys.maxint qui, pour une machine 64 bits, indiquent que max int est égal à 263

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

Et vous pouvez voir la différence par un simple test:

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

Lisez la documentation complète sur l'algorithme de hachage python https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Comme mentionné dans le commentaire, vous pouvez utiliser sys.hash_info (en python 3.X) qui vous donnera une séquence de structure de paramètres utilisés pour le calcul de hachages

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

En plus du module que j'ai décrit dans les lignes précédentes, vous pouvez également obtenir la valeur inf comme suit:

>>> hash(float('inf'))
314159
>>> sys.hash_info.inf
314159
70
Kasrâmvd

2305843009213693951 est 2^61 - 1. C'est le plus gros prime de Mersenne qui s'intègre dans 64 bits.

Si vous devez faire un hachage en prenant simplement la valeur mod un nombre, un grand nombre premier de Mersenne est un bon choix - il est facile à calculer et garantit une répartition uniforme des possibilités. (Bien que personnellement je ne ferais jamais un hash de cette façon)

Il est particulièrement pratique de calculer le module pour les nombres à virgule flottante. Ils ont un composant exponentiel qui multiplie le nombre entier par 2^x. Depuis 2^61 = 1 mod 2^61-1, il suffit de considérer le (exponent) mod 61.

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

77
Matt Timmermans

La fonction de hachage retourne plain int cela signifie que la valeur renvoyée est supérieure à -sys.maxint et inférieure à sys.maxint, ce qui signifie que si vous lui passez sys.maxint + x, le résultat serait -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

Pendant ce temps, 2**200 est une n fois supérieure à sys.maxint - je suppose que le hachage dépasserait la plage -sys.maxint..+sys.maxint n fois jusqu'à ce qu'il s'arrête sur un entier simple dans cette plage, comme dans les extraits de code ci-dessus ..

Donc généralement, pour tout n <= sys.maxint :

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

Remarque: ceci est vrai pour Python 2.

9
Andriy Ivaneyko

L'implémentation pour le type int dans cpython peut être trouvée ici.

Il renvoie simplement la valeur, sauf pour -1, qu'il renvoie -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;
}
0
Jieter