Bawiłem się funkcją skrótu Pythona . W przypadku małych liczb całkowitych pojawia się hash(n) == n
zawsze. 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.
źródło
n+1 == 2**61-1
n
całego 64-bitowego zakresu int.2147483647
równesys.maxint
(niesys.maxint+1
), a jeśli 'n = 0b1111111111111111111111111111111111111111111111111111111111111', to nie jestn+1 == 2**61
albon == 2**61-1
(nien+1 == 2**61-1
)?Odpowiedzi:
Na podstawie dokumentacji Pythona w
pyhash.c
pliku: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.h
pliku nagłówkowym, który dla maszyny 64-bitowej został zdefiniowany jako 61 (więcej informacji znajdziesz wpyconfig.h
pliku).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
:Możesz również użyć
math.frexp
, aby uzyskać mantysę i wykładnik,sys.maxint
którego dla maszyny 64-bitowej pokazuje, że max int to 2 63 :Różnicę widać po prostym teście:
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.Oprócz modułu, który opisałem w poprzednich wierszach, można również uzyskać następującą
inf
wartość:źródło
sys.hash_info
, dla kompletności.2305843009213693951
jest2^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-1
wystarczy wziąć pod uwagę tylko(exponent) mod 61
.Zobacz: https://en.wikipedia.org/wiki/Mersenne_prime
źródło
x == y
gwarantują onehash(x) == hash(y)
wszystkie typy? (Liczby takie jakDecimal('1e99999999')
są szczególnie problematyczne, na przykład: nie chcesz ich rozszerzać do odpowiedniej liczby całkowitej przed haszowaniem.)int
,float
,Decimal
orazFraction
obiektów i żex == y
zakładahash(x) == hash(y)
nawet kiedyx
iy
mają 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.Funkcja skrótu zwraca zwykły int, co oznacza, że zwracana wartość jest większa
-sys.maxint
i mniejsza niżsys.maxint
, co oznacza, że jeślisys.maxint + x
do niej przejdziesz , wynik będzie-sys.maxint + (x - 2)
.Tymczasem
2**200
jestn
razy większa niżsys.maxint
- przypuszczam, że hash przejdzie przez zakres-sys.maxint..+sys.maxint
n 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 :
Uwaga: dotyczy to Pythona 2.
źródło
sys.maxint
, i który używa innej funkcji skrótu).Realizacja dla typu int w CPython można znaleźć tutaj.
Po prostu zwraca wartość, z wyjątkiem
-1
, niż zwraca-2
:źródło
PyLong
zamiastPyInt
.