เมื่อใดที่ hash (n) == n ใน Python


100

ฉันได้รับเล่นกับงูหลามของฟังก์ชันแฮช สำหรับจำนวนเต็มขนาดเล็กจะปรากฏhash(n) == nเสมอ อย่างไรก็ตามสิ่งนี้ไม่ได้ครอบคลุมถึงจำนวนมาก:

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

ฉันไม่แปลกใจเลยฉันเข้าใจว่าแฮชมีค่าที่ จำกัด ช่วงนั้นคืออะไร?

ฉันลองใช้การค้นหาแบบไบนารีเพื่อค้นหาจำนวนที่น้อยที่สุด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

มีอะไรพิเศษเกี่ยวกับ 2305843009213693951? ฉันสังเกตว่ามันน้อยกว่าsys.maxsize == 9223372036854775807

แก้ไข: ฉันใช้ Python 3 ฉันใช้การค้นหาไบนารีเดียวกันบน Python 2 และได้ผลลัพธ์ที่แตกต่างกัน 2147483648 ซึ่งฉันสังเกตว่า sys.maxint+1

ฉันยังเล่น[hash(random.random()) for i in range(10**6)]เพื่อประมาณช่วงของฟังก์ชันแฮช ค่าสูงสุดต่ำกว่า n ด้านบนอย่างต่อเนื่อง เมื่อเปรียบเทียบค่าต่ำสุดดูเหมือนว่าแฮชของ Python 3 จะมีมูลค่าเป็นบวกเสมอในขณะที่แฮชของ Python 2 สามารถรับค่าลบได้


9
คุณตรวจสอบการแสดงเลขฐานสองของตัวเลขแล้วหรือยัง?
John Dvorak

3
'0b11111111111111111111111111111111111111111111111111111111111'! ดังนั้น n+1 == 2**61-1
ผู้พันตกใจ

2
ดูเหมือนจะขึ้นอยู่กับระบบ ด้วย python ของฉันแฮชมีnไว้สำหรับช่วง int 64 บิตทั้งหมด
Daniel

1
สังเกตจุดประสงค์ที่ระบุไว้ของค่าแฮช: ใช้เพื่อเปรียบเทียบคีย์พจนานุกรมอย่างรวดเร็วในระหว่างการค้นหาพจนานุกรม กล่าวอีกนัยหนึ่งการกำหนดการนำไปใช้งานและโดยอาศัยการที่สั้นกว่าค่าหลาย ๆ ค่าที่สามารถมีค่าแฮชอาจมีการชนกันได้แม้ในช่องว่างอินพุตที่เหมาะสม
ผู้ใช้

2
อืมไม่2147483647เท่ากับsys.maxint(not sys.maxint+1) และถ้า 'n = 0b1111111111111111111111111111111111111111111111111111111111111' แล้วn+1 == 2**61หรือn == 2**61-1(not n+1 == 2**61-1)?
กลัว

คำตอบ:


73

ขึ้นอยู่กับเอกสาร python ในpyhash.cไฟล์:

ประเภทตัวเลขกัญชาของจำนวน x จะขึ้นอยู่กับการลดลงของ x P = 2**_PyHASH_BITS - 1โมดูโลนายก ออกแบบมาเพื่อให้ hash(x) == hash(y)เมื่อใดก็ตามที่ x และ y มีค่าเท่ากันแม้ว่า x และ y จะมีประเภทต่างกันก็ตาม

ดังนั้นสำหรับเครื่อง 64/32 บิตการลดจะเป็น 2 _PyHASH_BITS - 1 แต่คือ_PyHASH_BITSอะไร?

คุณสามารถค้นหาได้ในpyhash.hไฟล์ส่วนหัวซึ่งสำหรับเครื่อง 64 บิตถูกกำหนดเป็น 61 (คุณสามารถอ่านคำอธิบายเพิ่มเติมได้ในpyconfig.hไฟล์)

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

ก่อนอื่นทั้งหมดขึ้นอยู่กับแพลตฟอร์มของคุณเช่นในแพลตฟอร์ม Linux 64 บิตของฉันการลดลงคือ 2 61 -1 ซึ่งก็คือ2305843009213693951:

>>> 2**61 - 1
2305843009213693951

นอกจากนี้คุณสามารถใช้math.frexpเพื่อรับ mantissa และเลขชี้กำลังsys.maxintซึ่งสำหรับเครื่อง 64 บิตแสดงให้เห็นว่า max int คือ 2 63 :

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

และคุณสามารถเห็นความแตกต่างได้ด้วยการทดสอบง่ายๆ:

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

อ่านเอกสารทั้งหมดเกี่ยวกับอัลกอริธึมการแฮช python https://github.com/python/cpython/blob/master/Python/pyhash.c#L34

ดังที่กล่าวไว้ในความคิดเห็นคุณสามารถใช้sys.hash_info(ใน python 3.X) ซึ่งจะให้ลำดับโครงสร้างของพารามิเตอร์ที่ใช้สำหรับแฮชคอมพิวเตอร์

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

นอกเหนือจากโมดูลัสที่ฉันได้อธิบายไว้ในบรรทัดก่อนหน้านี้คุณยังสามารถรับinfค่าดังต่อไปนี้:

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

3
จะเป็นการดีที่จะกล่าวถึงsys.hash_infoเพื่อความสมบูรณ์
Mark Dickinson

78

2305843009213693951คือ2^61 - 1. มันเป็นไพรม์เมอร์เซนที่ใหญ่ที่สุดที่รวมเป็น 64 บิต

หากคุณต้องสร้างแฮชเพียงแค่ใช้ค่า mod จำนวนหนึ่ง Mersenne prime ขนาดใหญ่ก็เป็นตัวเลือกที่ดี - คำนวณได้ง่ายและรับประกันการกระจายความเป็นไปได้อย่างเท่าเทียมกัน (แม้ว่าส่วนตัวจะไม่เคยแฮชด้วยวิธีนี้ก็ตาม)

สะดวกอย่างยิ่งในการคำนวณโมดูลัสสำหรับตัวเลขทศนิยม 2^xพวกเขามีองค์ประกอบชี้แจงว่าคูณจำนวนทั้งหมดโดย เนื่องจาก2^61 = 1 mod 2^61-1คุณต้องพิจารณาเฉพาะไฟล์(exponent) mod 61.

ดู: https://en.wikipedia.org/wiki/Mersenne_prime


8
คุณบอกว่าคุณจะไม่มีวันทำแฮชด้วยวิธีนี้ คุณมีข้อเสนอแนะอื่น ๆ เกี่ยวกับวิธีการที่จะทำให้การคำนวณ ints, float, Decimals, Fractions มีประสิทธิภาพพอสมควรและรับประกันว่าจะx == yรับประกันhash(x) == hash(y)ข้ามประเภทหรือไม่ (ตัวเลขที่ชอบDecimal('1e99999999')เป็นปัญหาโดยเฉพาะตัวอย่างเช่นคุณไม่ต้องการที่จะต้องขยายให้เป็นจำนวนเต็มที่ตรงกันก่อนที่จะแฮช)
Mark Dickinson

@MarkDickinson ฉันสงสัยว่าเขาพยายามที่จะสร้างความแตกต่างระหว่างแฮชด่วนที่ลดน้ำหนักง่าย ๆ นี้กับแฮชการเข้ารหัสที่ให้ความสำคัญกับการทำให้ผลลัพธ์ดูสุ่ม
Mike Ounsworth

4
@MarkDickinson โมดูลัสเป็นการเริ่มต้นที่ดี แต่ฉันจะผสมมันให้มากขึ้นโดยเฉพาะการผสมบิตที่สูงเข้ากับค่าต่ำ ไม่ใช่เรื่องแปลกที่จะเห็นลำดับของจำนวนเต็มหารด้วยกำลังของ 2 ไม่ใช่เรื่องแปลกที่จะเห็นตารางแฮชที่มีความจุที่เป็นพาวเวอร์ 2 ใน Java เช่นถ้าคุณมีลำดับของจำนวนเต็มที่หารด้วย 16 และ คุณใช้เป็นคีย์ใน HashMap คุณจะใช้เพียง 1/16 ของที่เก็บข้อมูลเท่านั้น (อย่างน้อยก็ในเวอร์ชันของแหล่งข้อมูลที่ฉันกำลังดูอยู่)! ฉันคิดว่าแฮชควรจะสุ่มดูอย่างน้อยเพื่อหลีกเลี่ยงปัญหาเหล่านี้
Matt Timmermans

ใช่แฮชสไตล์การผสมบิตนั้นเหนือกว่าแฮชที่ได้รับแรงบันดาลใจจากคณิตศาสตร์มาก คำแนะนำในการผสมบิตมีราคาถูกมากจนคุณสามารถมีได้มากมายในราคาเดียวกัน นอกจากนี้ข้อมูลในโลกแห่งความเป็นจริงดูเหมือนจะไม่มีรูปแบบที่ไม่ได้ผลกับการผสมบิต แต่มีรูปแบบที่น่ากลัวสำหรับโมดูลัส
usr

9
@usr: แน่นอน แต่บิตผสมกัญชาเป็นไปไม่ได้ที่นี่: ความต้องการที่ทำงานแฮสำหรับint, float, DecimalและFractionวัตถุและที่x == yหมายถึงhash(x) == hash(y)แม้ในขณะที่xและyมีชนิดที่แตกต่างกันมีการเรียกเก็บข้อ จำกัด ค่อนข้างรุนแรงบาง หากเป็นเพียงเรื่องของการเขียนฟังก์ชันแฮชสำหรับจำนวนเต็มโดยไม่ต้องกังวลเกี่ยวกับประเภทอื่น ๆ มันจะเป็นเรื่องที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
Mark Dickinson

9

ฟังก์ชันแฮชจะส่งคืนint ธรรมดาซึ่งหมายความว่าค่าที่ส่งคืนมากกว่า-sys.maxintและต่ำกว่าsys.maxintซึ่งหมายความว่าหากคุณส่งผ่านsys.maxint + xไปผลลัพธ์จะเป็น-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

ในขณะเดียวกัน2**200ก็nมากกว่าหนึ่งเท่าsys.maxint- ฉันเดาว่าแฮชจะไปเกินช่วง-sys.maxint..+sys.maxintn ครั้งจนกว่าจะหยุดเป็นจำนวนเต็มธรรมดาในช่วงนั้นเช่นในส่วนโค้ดด้านบน ..

โดยทั่วไปสำหรับn <= sys.maxint :

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

หมายเหตุ:เป็นจริงสำหรับ python 2


8
สิ่งนี้อาจเป็นจริงสำหรับ Python 2 แต่ไม่ใช่สำหรับ Python 3 (ซึ่งไม่มีsys.maxintและใช้ฟังก์ชันแฮชอื่น)
interjay

0

การใช้งานสำหรับประเภท int ใน cpython สามารถพบได้ที่นี่

เพียงแค่ส่งคืนค่ายกเว้น-1มากกว่าที่จะส่งกลับ-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
นี่ยังไม่รวมค่าขนาดใหญ่ซึ่งจะดำเนินการโดยมากกว่าPyLong PyInt
interjay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.