Python'da hash (n) == n ne zaman?


100

Python'un hash fonksiyonu ile oynuyorum . Küçük tamsayılar için hash(n) == nher zaman görünür . Ancak bu, büyük sayıları kapsamaz:

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

Şaşırmadım, hash'in sınırlı bir değer aralığı aldığını anlıyorum. Bu aralık nedir?

En küçük sayıyı bulmak için ikili aramayı kullanmayı denedimhash(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'i özel kılan nedir? Daha az olduğunu not ediyorumsys.maxsize == 9223372036854775807

Düzenleme: Python 3 kullanıyorum. Aynı ikili aramayı Python 2'de çalıştırdım ve 2147483648 farklı bir sonuç aldım, ki sys.maxint+1

[hash(random.random()) for i in range(10**6)]Hash fonksiyonunun aralığını tahmin etmek için de oynadım . Maksimum, sürekli olarak yukarıdaki n'nin altındadır. Min ile karşılaştırıldığında, Python 3'ün hash değeri her zaman pozitif olarak değerlendirilirken, Python 2'nin hash değeri negatif değerler alabilir.


9
Sayının ikili gösterimini kontrol ettiniz mi?
John Dvorak

3
'0b11111111111111111111111111111111111111111111111111111111111' meraklı! So n+1 == 2**61-1
Albay Panic

2
sisteme bağlı görünüyor. Benim python'umda hash ntüm 64bit int aralığı içindir.
Daniel

1
Karma değerinin belirtilen amacına dikkat edin: Bir sözlük araması sırasında sözlük anahtarlarını hızlı bir şekilde karşılaştırmak için kullanılırlar. Başka bir deyişle, uygulama tanımlı ve karma değerlere sahip olabilen birçok değerden daha kısa olması nedeniyle, makul girdi alanlarında bile çok iyi çakışmalar olabilir.
kullanıcı

2
Um, 2147483647eşit değildir sys.maxint(not sys.maxint+1) ve eğer 'n = 0b1111111111111111111111111111111111111111111111111111111111111' ise eşit değil n+1 == 2**61mi n == 2**61-1(değil n+1 == 2**61-1)?
phoog

Yanıtlar:


73

pyhash.cDosyadaki python belgelerine göre :

Sayısal türler için, bir x sayısının karması, üssü x modulo'nun azaltılmasına dayanır P = 2**_PyHASH_BITS - 1. hash(x) == hash(y)X ve y sayısal olarak eşit olduğunda, x ve y'nin farklı türleri olsa bile, öyle tasarlanmıştır .

Yani 64/32 bitlik bir makine için azalma 2 _PyHASH_BITS - 1 olacaktır, ancak nedir _PyHASH_BITS?

pyhash.h64 bitlik bir makine için 61 olarak tanımlanan başlık dosyasında bulabilirsiniz (daha fazla açıklamayı pyconfig.hdosyadan okuyabilirsiniz ).

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

Öncelikle, sizin platformunuza dayalıdır, örneğin 64bit Linux platformumda azalma 2 61 -1'dir, yani 2305843009213693951:

>>> 2**61 - 1
2305843009213693951

Ayrıca 64 bitlik bir makine için max int'in 2 63 olduğunu gösteren math.frexpmantis ve üssünü elde etmek için de kullanabilirsiniz :sys.maxint

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

Ve farkı basit bir testle görebilirsiniz:

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

Python hashing algoritmasıyla ilgili eksiksiz belgeleri okuyun https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

Yorumda belirtildiği gibi sys.hash_info(python 3.X'te) kullanabilirsiniz, bu size hash'leri hesaplamak için kullanılan bir yapısal parametre dizisi verir.

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

Önceki satırlarda anlattığım modülün yanı sıra, infdeğeri aşağıdaki gibi de alabilirsiniz :

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

3
sys.hash_infoEksiksizlik adına bahsetmek güzel olurdu .
Mark Dickinson

78

2305843009213693951olduğunu 2^61 - 1. 64 bite uyan en büyük Mersenne prime.

Sadece bir sayıdaki değer modunu alarak bir hash yapmanız gerekiyorsa, büyük bir Mersenne asalı iyi bir seçimdir - hesaplaması kolaydır ve olasılıkların eşit dağılımını sağlar. (Şahsen asla bu şekilde bir hash yapmamama rağmen)

Kayan nokta sayıları için modülü hesaplamak özellikle uygundur. Tam sayıyı ile çarpan üstel bir bileşeni vardır 2^x. O zamandan beri 2^61 = 1 mod 2^61-1, yalnızca (exponent) mod 61.

Bakınız: https://en.wikipedia.org/wiki/Mersenne_prime


8
Bu şekilde asla esrar yapmayacağını söylüyorsun. Tam sayılar, yüzer sayılar, Ondalık Sayılar, Kesirler için hesaplamayı makul ölçüde verimli kılacak ve türler arasında x == ygarantiler olmasını sağlayacak şekilde nasıl yapılabileceğine dair alternatif önerileriniz var hash(x) == hash(y)mı? (Gibi sayılar Decimal('1e99999999')özellikle sorunludur, örneğin: hashing işleminden önce onları karşılık gelen tam sayıya genişletmek zorunda kalmak istemezsiniz.)
Mark Dickinson

@MarkDickinson Bu basit yıldırım hızlı hash ile çıktının rastgele görünmesini de önemseyen kriptografik hash'ler arasında bir ayrım yapmaya çalıştığından şüpheleniyorum.
Mike Ounsworth

4
@MarkDickinson Modülüs iyi bir başlangıç, ancak daha sonra onu biraz daha karıştırırdım, özellikle bazı yüksek bitleri düşük seviyeye karıştırırdım. 2'nin katlarına bölünebilen tamsayı dizilerinin görülmesi alışılmadık bir durum değildir. 2'nin üsleri olan kapasitelere sahip karma tablolar görmek de alışılmadık bir durum değildir. Java'da, örneğin, 16'ya bölünebilen bir tamsayı dizisine sahipseniz ve Bunları bir HashMap'te anahtar olarak kullanırsanız, kovaların yalnızca 1 / 16'sını kullanırsınız (en azından baktığım kaynağın sürümünde)! Bu sorunlardan kaçınmak için hash'lerin en azından biraz rastgele görünmesi gerektiğini düşünüyorum
Matt Timmermans

Evet, bit karıştırma tarzı hash'ler matematikten esinlenenlerden çok daha üstündür. Bit karıştırma talimatları o kadar ucuzdur ki, aynı maliyete birçok kişiye sahip olabilirsiniz. Ayrıca, gerçek dünya verileri bit karıştırmayla iyi çalışmayan modellere sahip görünmüyor . Ancak modül için korkunç olan modeller var.
usr

9
@usr: Elbette, ama biraz karıştırma karma burada olanaksız olduğu: gereklilik için karma çalışma olduğunu int, float, Decimalve Fractionnesneler ve bu x == yima hash(x) == hash(y)dahi xve ybazı oldukça ciddi kısıtlamalar getirir farklı türleri vardır. Eğer mesele tamsayılar için bir hash fonksiyonu yazmak olsaydı, diğer tipler hakkında endişelenmeden, tamamen farklı bir mesele olurdu.
Mark Dickinson

9

Hash işlevi, düz int döndürür , bu, döndürülen değerin değerinden daha büyük -sys.maxintve daha küçük sys.maxintolduğu anlamına gelir sys.maxint + x; bu, ona geçerseniz sonuç olacaktır -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

Bu arada 2**200, bundan bir nkat daha büyük sys.maxint- benim tahminim, hash -sys.maxint..+sys.maxint, yukarıdaki kod parçacıkları gibi, bu aralıktaki düz tamsayı üzerinde durana kadar aralık n kez aralık dışına çıkacaktı ..

Genel olarak, herhangi bir n <= sys.maxint için :

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

Not: Bu, python 2 için geçerlidir.


8
Bu Python 2 için doğru olabilir, ancak kesinlikle Python 3 için geçerli değildir (sahip olmayan sys.maxintve farklı bir hash işlevi kullanan).
interjay

0

CPython içinde int türü için uygulama burada bulunabilir.

Yalnızca değeri döndürür, bunun dışında -1döndürür -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
Bu, PyLongyerine uygulanan büyük değerleri içermez PyInt.
interjay
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.