Quand'è hash (n) == n in Python?


100

Ho giocato con la funzione hash di Python . Per i piccoli numeri interi, appare hash(n) == nsempre. Tuttavia questo non si estende a grandi numeri:

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

Non sono sorpreso, capisco che l'hash abbia una gamma finita di valori. Qual è questo intervallo?

Ho provato a utilizzare la ricerca binaria per trovare il numero più piccolohash(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

Cos'ha di speciale 2305843009213693951? Noto che è meno disys.maxsize == 9223372036854775807

Modifica: sto usando Python 3. Ho eseguito la stessa ricerca binaria su Python 2 e ho ottenuto un risultato diverso 2147483648, che noto è sys.maxint+1

Ho anche giocato con [hash(random.random()) for i in range(10**6)]per stimare la gamma della funzione hash. Il massimo è costantemente inferiore a n sopra. Confrontando il min, sembra che l'hash di Python 3 sia sempre valutato positivamente, mentre l'hash di Python 2 può assumere valori negativi.


9
Hai controllato la rappresentazione binaria del numero?
John Dvorak

3
'0b1111111111111111111111111111111111111111111111111111111111111' curioso! Quindi n+1 == 2**61-1
Colonel Panic

2
sembra dipendere dal sistema. Con il mio python, l'hash è nper l'intero intervallo int a 64 bit.
Daniel

1
Nota lo scopo dichiarato del valore hash: vengono utilizzati per confrontare rapidamente le chiavi del dizionario durante una ricerca nel dizionario. In altre parole, definito dall'implementazione e in virtù del fatto di essere più breve di molti valori che possono avere valori hash, possono benissimo avere collisioni anche in spazi di input ragionevoli.
un CVn

2
Ehm, non è 2147483647uguale a sys.maxint(non sys.maxint+1) e se "n = 0b1111111111111111111111111111111111111111111111111111111111111" non è n+1 == 2**61o n == 2**61-1(no n+1 == 2**61-1)?
phoog

Risposte:


73

Basato sulla documentazione di Python nel pyhash.cfile:

Per i tipi numerici, l'hash di un numero x si basa sulla riduzione di x modulo primo P = 2**_PyHASH_BITS - 1. È progettato in modo che hash(x) == hash(y)ogni volta che xey sono numericamente uguali, anche se xey hanno tipi diversi.

Quindi per una macchina a 64/32 bit, la riduzione sarebbe 2 _PyHASH_BITS - 1, ma qual è _PyHASH_BITS?

Puoi trovarlo in pyhash.h file di intestazione che per una macchina a 64 bit è stato definito come 61 (puoi leggere ulteriori spiegazioni nel pyconfig.hfile).

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

Quindi prima di tutto si basa sulla tua piattaforma, ad esempio nella mia piattaforma Linux a 64 bit la riduzione è di 2 61 -1, che è 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Inoltre puoi usare math.frexpper ottenere la mantissa e l'esponente di sys.maxintcui per una macchina a 64 bit mostra che il massimo int è 2 63 :

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

E puoi vedere la differenza con un semplice test:

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

Leggi la documentazione completa sull'algoritmo di hashing di Python https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Come accennato nel commento puoi usare sys.hash_info (in python 3.X) che ti darà una sequenza di parametri usati per calcolare gli hash.

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

Oltre al modulo che ho descritto nelle righe precedenti, puoi anche ottenere il infvalore come segue:

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

3
Sarebbe bello menzionarlo sys.hash_info, per completezza.
Mark Dickinson

78

2305843009213693951è 2^61 - 1. È il numero primo di Mersenne più grande che si adatta a 64 bit.

Se devi creare un hash semplicemente prendendo il valore mod un certo numero, allora un grande Mersenne primo è una buona scelta: è facile da calcolare e garantisce una distribuzione uniforme delle possibilità. (Anche se personalmente non farei mai un hashish in questo modo)

È particolarmente conveniente calcolare il modulo per i numeri in virgola mobile. Hanno una componente esponenziale che moltiplica il numero intero per 2^x. Da allora 2^61 = 1 mod 2^61-1, devi solo considerare il file (exponent) mod 61.

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


8
Dici che non faresti mai un hashish in questo modo. Hai suggerimenti alternativi su come potrebbe essere fatto in un modo che renda ragionevolmente efficiente il calcolo per interi, float, decimali, frazioni e assicuri che x == ygarantisca hash(x) == hash(y)tra i tipi? (Numeri come Decimal('1e99999999')sono particolarmente problematici, ad esempio: non vuoi doverli espandere al numero intero corrispondente prima dell'hashing.)
Mark Dickinson,

@MarkDickinson Sospetto che stia cercando di tracciare una distinzione tra questo semplice hash alleggerito e gli hash crittografici che si preoccupano anche di rendere l'output casuale.
Mike Ounsworth

4
@MarkDickinson Il modulo è un buon inizio, ma poi lo mescolerei ancora, specialmente mescolando alcuni dei bit alti con quelli bassi. Non è raro vedere sequenze di numeri interi divisibili per potenze di 2. Non è raro inoltre vedere tabelle hash con capacità che sono potenze di 2. In Java, ad esempio, se hai una sequenza di numeri interi divisibili per 16, e li usi come chiavi in ​​una HashMap, utilizzerai solo 1/16 dei bucket (almeno nella versione del sorgente che sto guardando)! Penso che gli hash dovrebbero essere almeno un po 'casuali per evitare questi problemi
Matt Timmermans,

Sì, gli hash in stile bit-mixing sono di gran lunga superiori a quelli ispirati alla matematica. Le istruzioni per la miscelazione dei bit sono così economiche che puoi averne molte allo stesso costo. Inoltre, i dati del mondo reale sembrano non avere schemi che non funzionano bene con il bit mixing. Ma ci sono modelli che sono orribili per il modulo.
usr

9
@usr: Certo, ma un po 'hash-miscelazione è fattibile qui: il requisito che il lavoro hash per int, float, Decimale Fractiongli oggetti e che x == yimplica hash(x) == hash(y)anche quando xe yavere tipi diversi impone alcuni piuttosto gravi vincoli. Se fosse solo questione di scrivere una funzione hash per interi, senza preoccuparsi degli altri tipi, sarebbe una questione completamente diversa.
Mark Dickinson

9

La funzione hash restituisce un int semplice che significa che il valore restituito è maggiore -sys.maxinte minore di sys.maxint, il che significa che se ci passi il sys.maxint + xrisultato sarebbe -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

Nel frattempo 2**200è una nvolta maggiore di sys.maxint- la mia ipotesi è che l'hash sarebbe andato oltre il range-sys.maxint..+sys.maxint n volte fino a quando non si ferma su un intero normale in quell'intervallo, come negli snippet di codice sopra ..

Quindi, in generale, per ogni n <= sys.maxint :

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

Nota: questo è vero per python 2.


8
Questo potrebbe essere vero per Python 2, ma sicuramente non per Python 3 (che non ha sys.maxinte che utilizza una funzione hash diversa).
Interjay

0

L' implementazione per il tipo int in cpython può essere trovata qui.

Restituisce solo il valore, eccetto -1, che restituisce -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
Questo non include valori grandi, che vengono implementati da PyLongpiuttosto che PyInt.
Interjay
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.