Kiedy hash (n) == n w Pythonie?

100

Bawiłem się funkcją skrótu Pythona . W przypadku małych liczb całkowitych pojawia się hash(n) == nzawsze. Jednak nie dotyczy to dużych liczb:

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

Nie dziwię się, rozumiem, że hash przyjmuje skończony zakres wartości. Co to za zasięg?

Próbowałem użyć wyszukiwania binarnego, aby znaleźć najmniejszą liczbę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

Co jest specjalnego w 2305843009213693951? Zauważam, że to mniej niżsys.maxsize == 9223372036854775807

Edycja: używam Pythona 3. Przeprowadziłem to samo wyszukiwanie binarne w Pythonie 2 i otrzymałem inny wynik 2147483648, który, jak zauważam, to sys.maxint+1

Bawiłem się też, [hash(random.random()) for i in range(10**6)]aby oszacować zakres funkcji skrótu. Maksimum jest konsekwentnie poniżej n powyżej. Porównując min, wydaje się, że hash Pythona 3 jest zawsze pozytywnie oceniany, podczas gdy hash Pythona 2 może przyjmować wartości ujemne.

Colonel Panic
źródło
9
Czy sprawdziłeś binarną reprezentację liczby?
John Dvorak
3
„0b1111111111111111111111111111111111111111111111111111111111111” ciekawy! A więc n+1 == 2**61-1
pułkownik Panic
2
wydaje się zależeć od systemu. W moim Pythonie hash dotyczy ncałego 64-bitowego zakresu int.
Daniel
1
Zwróć uwagę na określony cel wartości skrótu: służą do szybkiego porównywania kluczy słownika podczas wyszukiwania w słowniku. Innymi słowy, zdefiniowane w ramach implementacji i ze względu na to, że są krótsze niż wiele wartości, które mogą mieć wartości skrótu, mogą mieć kolizje nawet w rozsądnych przestrzeniach wejściowych.
użytkownik
2
Hm, nie jest 2147483647równe sys.maxint(nie sys.maxint+1), a jeśli 'n = 0b1111111111111111111111111111111111111111111111111111111111111', to nie jest n+1 == 2**61albo n == 2**61-1(nie n+1 == 2**61-1)?
phoog

Odpowiedzi:

73

Na podstawie dokumentacji Pythona w pyhash.cpliku:

W przypadku typów liczbowych skrót liczby x jest oparty na redukcji x modulo liczby pierwszej P = 2**_PyHASH_BITS - 1. Został zaprojektowany w taki sposób, że hash(x) == hash(y)ilekroć x i y są liczbowo równe, nawet jeśli x i y mają różne typy.

Zatem dla maszyny 64/32 bitowej redukcja wyniosłaby 2 _PyHASH_BITS - 1, ale co to jest _PyHASH_BITS?

Możesz go znaleźć w pyhash.hpliku nagłówkowym, który dla maszyny 64-bitowej został zdefiniowany jako 61 (więcej informacji znajdziesz w pyconfig.hpliku).

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

Więc po pierwsze wszystko jest oparty na platformie, na przykład w moim 64-bitowej platformie Linux redukcja jest 2 61 -1, który jest 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Możesz również użyć math.frexp, aby uzyskać mantysę i wykładnik, sys.maxintktórego dla maszyny 64-bitowej pokazuje, że max int to 2 63 :

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

Różnicę widać po prostym teście:

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

Przeczytaj pełną dokumentację na temat algorytmu haszującego Pythona https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Jak wspomniano w komentarzu, możesz użyć sys.hash_info(w pythonie 3.X), co da ci struct sekwencję parametrów używanych do obliczania skrótów.

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

Oprócz modułu, który opisałem w poprzednich wierszach, można również uzyskać następującą infwartość:

>>> hash(float('inf'))
314159
>>> sys.hash_info.inf
314159
Kasravnd
źródło
3
Byłoby miło wspomnieć sys.hash_info, dla kompletności.
Mark Dickinson,
78

2305843009213693951jest 2^61 - 1. To największa liczba pierwsza Mersenne'a, która mieści się w 64 bitach.

Jeśli musisz utworzyć hash, po prostu pobierając wartość mod pewnej liczby, to duża liczba pierwsza Mersenne'a jest dobrym wyborem - jest łatwa do obliczenia i zapewnia równomierny rozkład możliwości. (Chociaż osobiście nigdy nie zrobiłbym haszyszu w ten sposób)

Szczególnie wygodne jest obliczanie modułu dla liczb zmiennoprzecinkowych. Mają składnik wykładniczy, który mnoży liczbę całkowitą przez 2^x. Ponieważ 2^61 = 1 mod 2^61-1wystarczy wziąć pod uwagę tylko (exponent) mod 61.

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

Matt Timmermans
źródło
8
Mówisz, że nigdy nie zrobiłbyś haszyszu w ten sposób. Czy masz alternatywne sugestie dotyczące tego, jak można to zrobić w sposób, który zapewni rozsądną wydajność obliczania liczb całkowitych, zmiennoprzecinkowych, dziesiętnych, ułamków zwykłych i zapewni, że x == ygwarantują one hash(x) == hash(y)wszystkie typy? (Liczby takie jak Decimal('1e99999999')są szczególnie problematyczne, na przykład: nie chcesz ich rozszerzać do odpowiedniej liczby całkowitej przed haszowaniem.)
Mark Dickinson
@MarkDickinson Podejrzewam, że próbuje odróżnić ten prosty, błyskawicznie szybki skrót od kryptograficznych skrótów, które również dbają o to, by wynik wyglądał losowo.
Mike Ounsworth
4
@MarkDickinson Moduł to dobry początek, ale potem pomieszałbym go jeszcze bardziej, zwłaszcza mieszając niektóre wysokie bity z niskimi. Nierzadko zdarza się widzieć sekwencje liczb całkowitych podzielnych przez potęgi 2. Nierzadko spotyka się również tabele skrótów o pojemnościach, które są potęgami 2. Na przykład w Javie, jeśli masz sekwencję liczb całkowitych podzielnych przez 16, i używasz ich jako kluczy w HashMap, użyjesz tylko 1/16 wiadra (przynajmniej w wersji źródła, na które patrzę)! Myślę, że hashe powinny wyglądać przynajmniej trochę przypadkowo, aby uniknąć tych problemów
Matt Timmermans
Tak, skróty w stylu mieszania bitów są znacznie lepsze niż te inspirowane matematyką. Instrukcje mieszania bitów są tak tanie, że możesz mieć ich wiele za tę samą cenę. Ponadto wydaje się, że dane ze świata rzeczywistego nie zawierają wzorców, które nie działają dobrze przy mieszaniu bitów. Ale są wzory, które są straszne dla modułu.
usr
9
@usr: Jasne, ale hash nieco mieszania jest nieosiągalne tutaj: wymóg, że praca dla hash int, float, Decimaloraz Fractionobiektów i że x == yzakłada hash(x) == hash(y)nawet kiedy xi ymają różne rodzaje nakłada pewne raczej poważne ograniczenia. Gdyby chodziło tylko o napisanie funkcji skrótu dla liczb całkowitych, bez martwienia się o inne typy, byłaby to zupełnie inna sprawa.
Mark Dickinson
9

Funkcja skrótu zwraca zwykły int, co oznacza, że ​​zwracana wartość jest większa -sys.maxinti mniejsza niż sys.maxint, co oznacza, że ​​jeśli sys.maxint + xdo niej przejdziesz , wynik będzie -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

Tymczasem 2**200jest nrazy większa niż sys.maxint- przypuszczam, że hash przejdzie przez zakres -sys.maxint..+sys.maxintn razy, aż zatrzyma się na zwykłej liczbie całkowitej w tym zakresie, jak we fragmentach kodu powyżej.

Generalnie dla każdego n <= sys.maxint :

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

Uwaga: dotyczy to Pythona 2.

Andriy Ivaneyko
źródło
8
Może to być prawdą w przypadku Pythona 2, ale na pewno nie w przypadku Pythona 3 (który nie ma sys.maxint, i który używa innej funkcji skrótu).
interjay
0

Realizacja dla typu int w CPython można znaleźć tutaj.

Po prostu zwraca wartość, z wyjątkiem -1, niż zwraca -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;
}
Jieter
źródło
6
Nie obejmuje to dużych wartości, które są implementowane przez PyLongzamiastPyInt .
interjay