ทำไมฟังก์ชันแฮชควรใช้โมดูลัสของจำนวนเฉพาะ?


335

เมื่อนานมาแล้วฉันซื้อโครงสร้างข้อมูลที่จองไว้ที่ราคาต่อรอง $ 1.25 ในนั้นคำอธิบายสำหรับฟังก์ชั่นคร่ำเครียดบอกว่ามันควรจะปรับเปลี่ยนในที่สุดโดยจำนวนเฉพาะเพราะ "ธรรมชาติของคณิตศาสตร์"

คุณคาดหวังอะไรจากหนังสือราคา $ 1.25

อย่างไรก็ตามฉันมีเวลาหลายปีที่จะคิดเกี่ยวกับธรรมชาติของคณิตศาสตร์และยังไม่สามารถเข้าใจได้

การกระจายของตัวเลขเป็นจริงมากขึ้นแม้ว่าจะมีถังจำนวนมากหรือ หรือนี่เป็นเรื่องโปรแกรมเมอร์เก่าที่ทุกคนยอมรับเพราะทุกคนอื่นยอมรับมันได้หรือไม่


1
คำถามที่สมเหตุผลสมบูรณ์แบบ: ทำไมต้องมีถังจำนวนมาก?
Draemon

1
คำถามนี้ดูเหมือนจะปิดหัวข้อเพราะกว่าจะเป็นในด้านวิทยาศาสตร์คอมพิวเตอร์
การแข่งขัน Lightness ใน Orbit

2
cs.stackexchange.com/a/64191/64222อีกคำอธิบายที่ถกเถียงกันอยู่
Green Tree


ต่อไปนี้เป็นคำอธิบายที่ดีสำหรับคำถามที่เกี่ยวข้องกับตัวเลขหลักฐานที่น่าตกใจบางประการ - quora.com/…
AnBisw

คำตอบ:


242

โดยปกติแล้วฟังก์ชันแฮชแบบง่ายจะทำงานโดยการใช้ "ส่วนประกอบส่วนต่าง ๆ " ของอินพุต (อักขระในกรณีของสตริง) และคูณด้วยอำนาจของค่าคงที่และเพิ่มเข้าด้วยกันในรูปแบบจำนวนเต็ม ดังนั้นตัวอย่างเช่นแฮชทั่วไป (แม้ว่าจะไม่ค่อยดี) ของสตริงอาจเป็น:

(first char) + k * (second char) + k^2 * (third char) + ...

จากนั้นถ้ากลุ่มของสตริงทั้งหมดที่มีอักขระตัวแรกเหมือนกันถูกป้อนเข้าผลลัพธ์จะเป็นโมดูโล k เดียวกันอย่างน้อยที่สุดจนกว่าชนิดจำนวนเต็มจะล้น

[เป็นตัวอย่างสตริง hashCode ของ Java นั้นคล้ายกันมากกับสิ่งนี้ - มันใช้อักขระเรียงลำดับย้อนกลับโดยมี k = 31 ดังนั้นคุณจะได้รับความสัมพันธ์ที่โดดเด่น modulo 31 ระหว่างสตริงที่จบในลักษณะเดียวกันและความสัมพันธ์ที่น่าประทับใจแบบโมดูโล 2 ^ 32 ระหว่างสตริงที่เหมือนกันยกเว้นใกล้ถึงจุดสิ้นสุด สิ่งนี้จะไม่ทำให้พฤติกรรมแฮชโต๊ะยุ่งเหยิงอย่างจริงจัง]

hashtable ทำงานโดยใช้โมดูลัสของแฮชกับจำนวนของที่เก็บข้อมูล

สิ่งสำคัญใน hashtable ไม่ควรสร้างการชนในกรณีที่เป็นไปได้เนื่องจากการชนจะลดประสิทธิภาพของ hashtable

ตอนนี้สมมติว่ามีบางคนใส่ค่าทั้งหมดไว้ใน hashtable ที่มีความสัมพันธ์ระหว่างรายการเช่นเดียวกับที่มีอักขระตัวแรก นี่เป็นรูปแบบการใช้งานที่คาดเดาได้ฉันจะพูดดังนั้นเราจึงไม่ต้องการให้เกิดการชนกันมากเกินไป

ปรากฎว่า "เพราะธรรมชาติของคณิตศาสตร์" ถ้าค่าคงที่ที่ใช้ในแฮชและจำนวนของถังเป็นcoprimeการชนจะลดลงในบางกรณีที่พบบ่อย หากพวกเขาไม่ใช่coprimeดังนั้นจึงมีความสัมพันธ์ที่ค่อนข้างง่ายระหว่างอินพุตที่ไม่ได้ลดการชน แฮชทั้งหมดออกมาเป็นโมดูโลเท่า ๆ กันซึ่งเป็นปัจจัยทั่วไปซึ่งหมายความว่าพวกมันทั้งหมดจะตกอยู่ใน 1 / n ของถังที่มีค่านั้นโมดูโลเป็นปัจจัยร่วม คุณได้รับการชนหลายครั้งโดยที่ n เป็นปัจจัยทั่วไป ตั้งแต่ n อย่างน้อย 2 ฉันจะบอกว่ามันไม่เป็นที่ยอมรับสำหรับกรณีการใช้งานที่ค่อนข้างง่ายในการสร้างการชนกันอย่างน้อยสองเท่าตามปกติ หากผู้ใช้บางคนจะแบ่งการกระจายของเราเป็นที่เก็บข้อมูลเราต้องการให้มันเป็นอุบัติเหตุประหลาดไม่ใช่การใช้งานที่คาดเดาได้ง่าย

ตอนนี้การใช้งาน hashtable เห็นได้ชัดว่าไม่มีการควบคุมรายการที่ใส่ไว้ในรายการ พวกเขาไม่สามารถป้องกันพวกเขาที่เกี่ยวข้อง ดังนั้นสิ่งที่ต้องทำคือทำให้แน่ใจว่าค่าคงที่และจำนวนถังเป็น coprime ด้วยวิธีนี้คุณไม่ต้องพึ่งพาส่วนประกอบ "สุดท้าย" เพียงอย่างเดียวเพื่อกำหนดโมดูลัสของถังด้วยความเคารพต่อปัจจัยทั่วไปเล็กน้อย เท่าที่ฉันรู้ว่าพวกเขาไม่จำเป็นต้องเป็นคนสำคัญในการทำสิ่งนี้

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

ในทางปฏิบัติฉันคิดว่ามันค่อนข้างปกติที่จะใช้กำลัง 2 เป็นจำนวนของถัง สะดวกและประหยัดไม่ต้องค้นหารอบ ๆ หรือเลือกจำนวนเฉพาะที่มีขนาดสำคัญ ดังนั้นคุณต้องพึ่งพาฟังก์ชั่นแฮชที่จะไม่ใช้ตัวคูณแม้กระทั่งซึ่งโดยทั่วไปจะเป็นสมมติฐานที่ปลอดภัย แต่คุณยังสามารถรับพฤติกรรมการแฮ็กที่ไม่ดีเป็นครั้งคราวโดยใช้ฟังก์ชันแฮชเช่นเดียวกับข้างบนและการนับจำนวนเฉพาะกลุ่มอาจช่วยเพิ่มเติมได้

เกี่ยวกับหลักการที่ว่า "ทุกอย่างจะต้องมีความสำคัญ" เท่าที่ฉันรู้เพียงพอ แต่ไม่ใช่เงื่อนไขที่จำเป็นสำหรับการกระจายที่ดีในแฮชเทเบิล ช่วยให้ทุกคนทำงานร่วมกันโดยไม่จำเป็นต้องคิดว่าคนอื่นปฏิบัติตามกฎเดียวกัน

[แก้ไข: มีอีกเหตุผลที่พิเศษกว่าคือการใช้ถังจำนวนมากซึ่งก็คือถ้าคุณจัดการการชนด้วยการตรวจวัดเชิงเส้น จากนั้นคุณคำนวณความก้าวหน้าจาก hashcode และหากความก้าวหน้านั้นเป็นปัจจัยของการนับ bucket คุณสามารถทำได้เฉพาะโพรบ (bucket_count / stride) ก่อนที่คุณจะกลับมาเริ่มต้นใหม่ กรณีที่คุณต้องการหลีกเลี่ยงมากที่สุดคือ stride = 0 ซึ่งต้องเป็นแบบพิเศษ แต่เพื่อหลีกเลี่ยงการใส่ปลอกพิเศษ bucket_count / stride เท่ากับจำนวนเต็มเล็ก ๆ คุณสามารถทำให้ bucket_count เป็นไพร์มและไม่สนใจว่า การก้าวเดินให้ไม่ได้ 0]


เช่นเดียวกับการบันทึกด้านข้าง: การสนทนาสำหรับตัวเลือกที่สมเหตุสมผลของปัจจัย k สำหรับ hashCodes อยู่ที่นี่: stackoverflow.com/q/1835976/21499
Hans-Peter Störr

9
นี่เป็นคำตอบที่ยอดเยี่ยม คุณช่วยอธิบายเพิ่มเติมนี้ได้มั้ย "ดังนั้นคุณจะได้รับความสัมพันธ์ที่น่าประทับใจ modulo 31 ระหว่างสตริงที่ลงท้ายด้วยวิธีเดียวกันและความสัมพันธ์ที่น่าประทับใจ modulo 2 ^ 32 ระหว่างสตริงที่เหมือนกันยกเว้นใกล้จบซึ่งจะไม่ทำให้พฤติกรรมยุ่งเหยิงอย่างจริงจัง " โดยเฉพาะอย่างยิ่งผมไม่เข้าใจ 2 ^ 32 ส่วน
สามัญ

2
หมายเหตุเพิ่มเติมเพื่อทำให้สิ่งต่าง ๆ ชัดเจนยิ่งขึ้นเกี่ยวกับสิ่งนี้: "แฮชทั้งหมดจะออกมาเป็นโมดูโลเท่า ๆ กันซึ่งเป็นปัจจัยร่วม" -> นี่เป็นเพราะถ้าคุณพิจารณาตัวอย่างฟังก์ชันแฮชฟังก์ชันแฮช = 1st ถ่าน + ถ่าน 2 * k + ... และ ใช้สตริงที่มีอักขระตัวแรกเหมือนกันแฮ%% k จะเหมือนกันสำหรับสตริงเหล่านี้ ถ้า M คือขนาดของ hashtable และ g คือ gcd ของ M และ k ดังนั้น (hash% k)% g เท่ากับ hash% g (ตั้งแต่ g หาร k) และ hash% g ก็จะเหมือนกันสำหรับสตริงเหล่านี้ ตอนนี้ให้พิจารณา (hash% M)% g นี่เท่ากับแฮช% g (ตั้งแต่ g หาร M) ดังนั้น (hash% M)% g เท่ากับสตริงเหล่านี้ทั้งหมด
Quark

1
@DanielMcLaury Joshua Bloch อธิบายว่าทำไม Java - แนะนำให้ใช้ในหนังสือยอดนิยมสองเล่ม (K&R, Dragon book) และทำงานได้ดีโดยมีการชนกันน้อยในพจนานุกรมภาษาอังกฤษ มันเร็ว (ใช้วิธีของ Horner ) เห็นได้ชัดว่า K&R จำไม่ได้ว่ามาจากไหน ฟังก์ชั่นที่คล้ายกันคือRabin fingerprintจากRabin-Karp algorithm (1981) แต่ K&R (1978) ถือกำเนิดขึ้นมา
bain

1
@SteveJessop คุณช่วยอธิบาย "ความสัมพันธ์ที่น่าประทับใจโมดูโล 2 ^ 32 ระหว่างสตริงที่เหมือนกันยกเว้นใกล้จะจบ" ขอบคุณ
Khanna111

29

สิ่งแรกที่คุณทำเมื่อทำการแทรก / เรียกคืนจากตารางแฮชคือการคำนวณ hashCode สำหรับคีย์ที่กำหนดจากนั้นค้นหาที่ฝากข้อมูลที่ถูกต้องโดยการตัด hashCode ให้มีขนาดของ hashTable โดยทำ hashCode% table_length ต่อไปนี้เป็น 'งบ' 2 ตัวที่คุณอ่านมากที่สุด

  1. หากคุณใช้กำลัง 2 สำหรับ table_length การค้นหา (hashCode (คีย์)% 2 ^ n) นั้นง่ายและรวดเร็วเท่ากับ (hashCode (คีย์) & (2 ^ n -1)) แต่ถ้าฟังก์ชันของคุณในการคำนวณ hashCode สำหรับคีย์ที่ระบุนั้นไม่ดีคุณจะต้องประสบกับการรวมกลุ่มของคีย์จำนวนมากในที่เก็บแฮชไม่กี่ตัว
  2. แต่ถ้าคุณใช้หมายเลขเฉพาะสำหรับ table_length การคำนวณ hashCodes สามารถแมปไปยังที่เก็บแฮชที่แตกต่างกันแม้ว่าคุณจะมีฟังก์ชั่น hashCode ที่โง่เล็กน้อย

และนี่คือข้อพิสูจน์

ถ้าสมมติว่าฟังก์ชั่น hashCode ของคุณส่งผลให้ hashCodes ต่อไปนี้อยู่ในกลุ่มอื่น ๆ {x, 2x, 3x, 4x, 5x, 6x ... } จากนั้นทั้งหมดเหล่านี้จะถูกจัดกลุ่มในถังจำนวน m เท่านั้นโดยที่ m = table_length / GreatestCommonFactor (table_length, x) (เป็นเรื่องเล็กน้อยที่จะตรวจสอบ / สืบเนื่องจากสิ่งนี้) ตอนนี้คุณสามารถเลือกทำอย่างใดอย่างหนึ่งต่อไปนี้เพื่อหลีกเลี่ยงการทำคลัสเตอร์

ตรวจสอบให้แน่ใจว่าคุณไม่ได้สร้าง hashCodes มากเกินไปซึ่งเป็นทวีคูณของ hashCode อื่นเช่นใน {x, 2x, 3x, 4x, 5x, 6x ... } แต่นี่อาจเป็นเรื่องยากถ้า hashTable ของคุณควรจะมี ล้านรายการ หรือเพียงแค่ทำให้ m เท่ากับ table_length โดยการทำให้ GreatestCommonFactor (table_length, x) เท่ากับ 1 คือการสร้าง table_length coprime ด้วย x และถ้า x สามารถเป็นจำนวนเท่าใดก็ได้ให้แน่ใจว่า table_length เป็นจำนวนเฉพาะ

จาก - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html


11

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

คำอธิบายที่ค่อนข้างชัดเจนพร้อมรูปภาพด้วย

แก้ไข: โดยสรุปมีการใช้ช่วงเวลาเนื่องจากคุณมีโอกาสที่ดีที่สุดในการรับค่าที่ไม่ซ้ำกันเมื่อทำการคูณค่าด้วยหมายเลขเฉพาะที่เลือกและเพิ่มขึ้นทั้งหมด ตัวอย่างเช่นกำหนดสตริงการคูณค่าตัวอักษรแต่ละตัวด้วยจำนวนเฉพาะจากนั้นเพิ่มค่าทั้งหมดขึ้นมาจะให้ค่าแฮชของคุณ

คำถามที่ดีกว่าก็คือทำไมจึงเป็นข้อที่ 31


5
แม้ว่าฉันคิดว่าบทสรุปจะมีประโยชน์ในกรณีที่ไซต์นั้นเคยตายไปเนื้อหาที่เหลือของบางส่วนจะถูกบันทึกไว้ที่นี่ใน SO
Thomas Owens

2
บทความไม่ได้อธิบายว่าทำไม แต่พูดว่า "นักวิจัยพบว่าการใช้งานนายกของวันที่ 31 ให้การกระจายที่ดีขึ้นกับกุญแจและน้อยกว่าไม่มีการชนกันไม่มีใครรู้ว่าทำไม ... " ตลกถามคำถามเดียวกันกับฉัน .
theschmitzer

> คำถามที่ดีกว่าคือทำไมทำไมต้องเป็นหมายเลข 31 หากคุณหมายถึงเหตุใดจึงใช้หมายเลข 31 บทความที่คุณชี้จะบอกคุณว่าเพราะเหตุใดเพราะการทดสอบแบบหลายรายการโดยเร็วและการทดสอบ cos แสดงว่าเป็นสิ่งที่ดีที่สุดที่จะใช้ ตัวคูณที่ได้รับความนิยมอื่น ๆ ที่ฉันเคยเห็นคือ 33 ซึ่งให้น้ำหนักกับทฤษฎีว่าปัญหาความเร็วเป็นปัจจัยสำคัญ (อย่างน้อยเริ่มแรก) ถ้าคุณหมายถึงอะไรประมาณ 31 ที่ทำให้การทดสอบดีขึ้นฉันกลัวว่าไม่รู้
sgmoore

ดังนั้นเหตุผลเดียวที่ทำให้มันสามารถใช้เป็นตัวคูณได้ก็เพราะมันง่ายต่อการคูณด้วย (เมื่อฉันบอกว่าฉันเคยเห็น 33 ใช้เป็นทวีคูณฉันไม่ได้หมายความว่าเมื่อเร็ว ๆ นี้นี่อาจเป็นทศวรรษที่ผ่านมาและเป็นไปได้ก่อนที่การวิเคราะห์จำนวนมากจะเกิดขึ้นเมื่อ hashing)
sgmoore

3
@SteveJessop หมายเลข 31 ได้รับการปรับให้เหมาะสมโดย CPU ในการดำเนินการ (x * 32) -1 ซึ่ง*32เป็นการเลื่อนบิตอย่างง่ายหรือดียิ่งขึ้นในการปรับขนาดของที่อยู่ (เช่นlea eax,eax*8; leax, eax,eax*4บน x86 / x64) ดังนั้น*31ผู้สมัครที่ดีสำหรับการคูณจำนวนเฉพาะ นี่เป็นเรื่องจริงเมื่อหลายปีก่อน - ตอนนี้สถาปัตยกรรมซีพียูล่าสุดมีการคูณทันที - การหารช้าลงเสมอ ...
Arnaud Bouchez

10

TL; DR

index[hash(input)%2]จะส่งผลให้เกิดการชนกันของครึ่งหนึ่งของแฮชที่เป็นไปได้ทั้งหมดและช่วงของค่า index[hash(input)%prime]ส่งผลให้เกิดการชนกันของ <2 ของแฮชที่เป็นไปได้ทั้งหมด การแก้ไขตัวหารเป็นขนาดตารางยังช่วยให้มั่นใจว่าตัวเลขไม่สามารถมากกว่าตารางได้


1
2 คือเพื่อนหมายเลขเฉพาะ
Ganesh Chowdhary Sadanala

8

ใช้ primes เนื่องจากคุณมีโอกาสที่จะได้รับค่าที่ไม่ซ้ำกันสำหรับฟังก์ชันแฮชทั่วไปซึ่งใช้ polynomials modulo P. สมมติว่าคุณใช้ฟังก์ชัน hash สำหรับสตริงที่มีความยาว <= N และคุณมีการชนกัน นั่นหมายความว่าพหุนามต่างกัน 2 ตัวสร้างโมดูโลแบบค่าเดียวกันความแตกต่างของพหุนามนั้นเป็นพหุนามที่มีระดับ N เท่ากัน (หรือน้อยกว่า) มันมีรากไม่เกิน N (นี่คือธรรมชาติของคณิตศาสตร์แสดงตัวเองเนื่องจากการอ้างสิทธิ์นี้เป็นจริงสำหรับพหุนามเหนือฟิลด์ => จำนวนเฉพาะ) ดังนั้นถ้า N น้อยกว่า P คุณจะไม่เกิดการชนกัน หลังจากนั้นการทดสอบอาจแสดงให้เห็นว่า 37 มีขนาดใหญ่พอที่จะหลีกเลี่ยงการชนสำหรับตารางแฮชที่มีความยาว 5-10 และมีขนาดเล็กพอที่จะใช้สำหรับการคำนวณ


1
ในขณะที่คำอธิบายดูเหมือนชัดเจนตอนนี้ฉันถึงได้อ่านหนังสือโดย A.Shen "การเขียนโปรแกรม: ทฤษฎีและปัญหา" (ในรัสเซีย), ดูการอภิปรายเกี่ยวกับอัลกอริทึมของราบิน ไม่แน่ใจว่ามีการแปลภาษาอังกฤษหรือไม่
TT_

5

เพียงแค่ให้มุมมองทางเลือกมีเว็บไซต์นี้:

http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth

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


ถังจำนวนมากขึ้นหมายถึงการชนน้อยลง: ดูหลักการของนกพิราบ
ไม่ทราบ

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

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

1
@ ไม่ทราบว่าคุณพลาดการชนนั้นขึ้นอยู่กับฟังก์ชันแฮชด้วย ดังนั้นหากฟังก์ชั่นการใช้งานไม่ดีจริง ๆ ไม่ว่าคุณจะเพิ่มขนาดใหญ่แค่ไหนก็ยังอาจมีการชนกันจำนวนมาก
Suraj Chandran

ดูเหมือนว่าบทความต้นฉบับจะหายไป แต่มีความคิดเห็นที่ลึกซึ้งบางอย่างที่นี่รวมถึงการสนทนากับผู้เขียนต้นฉบับ news.ycombinator.com/item?id=650487
Adrian McCarthy

3

จำนวนเฉพาะเป็นตัวเลขที่ไม่ซ้ำกัน พวกเขาไม่เหมือนใครผลิตภัณฑ์ของนายกกับหมายเลขอื่น ๆ มีโอกาสที่ดีที่สุดในการเป็นเอกลักษณ์ (ไม่ใช่เฉพาะของนายกเองแน่นอน -) เนื่องจากความจริงที่ว่านายกใช้แต่งมัน คุณสมบัตินี้ใช้ในฟังก์ชันการแปลงแป้นพิมพ์

รับสตริง“ ซามูเอล” คุณสามารถสร้างแฮชที่ไม่ซ้ำกันโดยการคูณตัวเลขหรือตัวอักษรแต่ละตัวด้วยจำนวนเฉพาะและเพิ่มขึ้น นี่คือเหตุผลที่ใช้เฉพาะช่วงเวลา

อย่างไรก็ตามการใช้ primes เป็นเทคนิคเก่า กุญแจสำคัญในที่นี้เพื่อทำความเข้าใจว่าตราบใดที่คุณสามารถสร้างคีย์ที่ไม่ซ้ำกันเพียงพอคุณสามารถย้ายไปยังเทคนิคการแฮชอื่น ๆ ได้เช่นกัน ไปที่นี่สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้เกี่ยวกับ http://www.azillionmonkeys.com/qed/hash.html

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/


1
ฮ่าฮ่าฮ่า .... ที่จริงแล้วผลิตภัณฑ์ 2 primes ไม่ได้มีโอกาสที่จะเป็น 'ที่ไม่เหมือนใคร' มากกว่าผลิตภัณฑ์ของหมายเลขเฉพาะและหมายเลขอื่น ๆ ?
HasaniH

@Beska ที่นี่ "เอกลักษณ์" หมายซ้ำดังนั้นผมเชื่อว่า "ไม่เป็นเอกลักษณ์" ควรจะกำหนดในลักษณะเดียวกัน :)
TT_

3

มันขึ้นอยู่กับทางเลือกของฟังก์ชั่นแฮช

ฟังก์ชันแฮชจำนวนมากรวมองค์ประกอบต่าง ๆ ในข้อมูลโดยการคูณด้วยปัจจัยบางอย่างโมดูโลกำลังของสองที่สอดคล้องกับขนาดคำของเครื่อง (โมดูลัสนั้นฟรีโดยเพียงแค่ให้การคำนวณมากเกินไป)

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

ในทางกลับกันปัจจัยเหล่านั้นมักจะเกิดขึ้นจากช่วงเวลาที่แปลกดังนั้นคุณควรจะปลอดภัยโดยใช้กำลังสองสำหรับตารางแฮชของคุณ (เช่น Eclipse ใช้ 31 เมื่อสร้างเมธอด Java hashCode ()


2

สมมติว่าขนาดตารางของคุณ (หรือตัวเลขสำหรับโมดูโล) คือ T = (B * C) ตอนนี้ถ้าแฮชสำหรับอินพุตของคุณเป็นเหมือน (N * A * B) โดยที่ N สามารถเป็นจำนวนเต็มใด ๆ แล้วผลลัพธ์ของคุณจะไม่ถูกกระจายออกไป เนื่องจากทุกครั้งที่ n กลายเป็น C, 2C, 3C ฯลฯ ผลลัพธ์ของคุณจะเริ่มทำซ้ำ เช่นผลลัพธ์ของคุณจะถูกกระจายในตำแหน่ง C เท่านั้น โปรดทราบว่า C ที่นี่คือ (T / HCF (ขนาดตารางแฮช))

ปัญหานี้สามารถขจัดได้ด้วยการทำ HCF 1. หมายเลขเฉพาะนั้นดีมากสำหรับเรื่องนั้น

สิ่งที่น่าสนใจอีกอย่างคือเมื่อ T คือ 2 ^ N สิ่งเหล่านี้จะให้ผลลัพธ์เหมือนกับ N บิตต่ำทั้งหมดของอินพุตแฮช เนื่องจากทุกหมายเลขสามารถแทนค่ากำลังของ 2 เมื่อเราใช้โมดูโลของหมายเลขใด ๆ ด้วย T เราจะลบพาวเวอร์ทั้งหมดของหมายเลขแบบฟอร์ม 2 ซึ่งคือ> = N ดังนั้นจึงให้จำนวนรูปแบบเฉพาะเสมอขึ้นอยู่กับอินพุต . นี่ก็เป็นตัวเลือกที่แย่

ในทำนองเดียวกัน T เป็น 10 ^ N ก็ไม่ดีเช่นกันเนื่องจากเหตุผลที่คล้ายกัน (รูปแบบในการแสดงทศนิยมของตัวเลขแทนไบนารี)

ดังนั้นจำนวนเฉพาะมักจะให้ผลลัพธ์ที่กระจายได้ดีกว่าดังนั้นจึงเป็นตัวเลือกที่ดีสำหรับขนาดตาราง


2

คัดลอกจากคำตอบอื่น ๆ ของฉันhttps://stackoverflow.com/a/43126969/917428 ดูรายละเอียดเพิ่มเติมและตัวอย่าง

ฉันเชื่อว่ามันเกี่ยวข้องกับความจริงที่ว่าคอมพิวเตอร์ทำงานร่วมกับฐาน 2 แค่คิดว่ามันทำงานแบบเดียวกันกับฐาน 10 ได้อย่างไร:

  • 8% 10 = 8
  • 18% 10 = 8
  • 87865378% 10 = 8

ไม่สำคัญว่าจะเป็นเลขใด: ตราบใดที่ลงท้ายด้วย 8, โมดูโล 10 จะเท่ากับ 8

การเลือกตัวเลขที่ใหญ่และไม่ได้กำลังสองพอจะทำให้แน่ใจว่าฟังก์ชันแฮชเป็นฟังก์ชันของบิตอินพุตทั้งหมดแทนที่จะเป็นชุดย่อยของพวกเขา


1

ฉันต้องการเพิ่มบางสิ่งสำหรับคำตอบของ Steve Jessop (ฉันไม่สามารถแสดงความคิดเห็นได้เนื่องจากฉันไม่มีชื่อเสียงเพียงพอ) แต่ฉันพบเนื้อหาที่มีประโยชน์ คำตอบของเขาช่วยได้มาก แต่เขาทำผิด: ขนาดถังไม่ควรมีพลังเท่ากับ 2 ฉันจะอ้างอิงจากหนังสือ "Introduction to Algorithm" โดย Thomas Cormen, Charles Leisersen, et al ในหน้า 263:

เมื่อใช้วิธีการหารเรามักจะหลีกเลี่ยงค่าบางอย่างของ m ตัวอย่างเช่น m ไม่ควรเป็นกำลัง 2 เนื่องจากถ้า m = 2 ^ p ดังนั้น h (k) เป็นเพียงบิตลำดับต่ำสุดของ k นอกจากว่าเรารู้ว่ารูปแบบ p-bit ที่มีลำดับต่ำมีแนวโน้มเท่ากันเราจะดีกว่าที่จะออกแบบฟังก์ชั่นแฮชเพื่อพึ่งพาบิตทั้งหมดของคีย์ ตามแบบฝึกหัด 11.3-3 ขอให้คุณแสดงให้เลือก m = 2 ^ p-1 เมื่อ k เป็นสตริงอักขระที่ตีความใน radix 2 ^ p อาจเป็นทางเลือกที่ไม่ดีเนื่องจากการอนุญาตให้อักขระ k ไม่เปลี่ยนค่าแฮช

หวังว่ามันจะช่วย


0

สำหรับฟังก์ชั่นแฮชไม่เพียง แต่สำคัญในการลดขนาดของคอลเล็คชั่นโดยทั่วไปเท่านั้น

สมมติว่าคุณมีสมการ: (x + y*z) % key = xด้วยและ0<x<key 0<z<keyถ้า key เป็น primenumber n * y = key เป็นจริงสำหรับทุก ๆ n ใน N และ false สำหรับหมายเลขอื่น ๆ

ตัวอย่างที่สำคัญไม่ใช่ตัวอย่างสำคัญ: x = 1, z = 2 และ key = 8 เนื่องจาก key / z = 4 ยังคงเป็นจำนวนธรรมชาติ 4 กลายเป็นคำตอบสำหรับสมการของเราและในกรณีนี้ (n / 2) * y = สำคัญเป็นจริงสำหรับทุก ๆ n ใน N จำนวนของการแก้ปัญหาสำหรับสมการนั้นเพิ่มขึ้นเป็นสองเท่าเนื่องจาก 8 ไม่ได้เป็นจำนวนเฉพาะ

หากผู้โจมตีของเรารู้อยู่แล้วว่า 8 เป็นวิธีแก้ปัญหาที่เป็นไปได้สำหรับสมการเขาสามารถเปลี่ยนไฟล์จากการผลิต 8 เป็น 4 และยังคงได้รับแฮชเดียวกัน


0

ฉันได้อ่านเว็บไซต์เวิร์ดเพรสยอดนิยมที่เชื่อมโยงกับคำตอบที่ได้รับความนิยมด้านบนที่ด้านบนแล้ว จากสิ่งที่ฉันเข้าใจฉันต้องการแบ่งปันการสังเกตง่ายๆที่ฉันทำ

คุณสามารถค้นหารายละเอียดทั้งหมดในบทความได้ที่นี่แต่ถือว่าสิ่งต่อไปนี้เป็นจริง:

  • การใช้หมายเลขเฉพาะทำให้เรามี "โอกาสที่ดีที่สุด" ของค่าที่ไม่ซ้ำใคร

การใช้ hashmap ทั่วไปต้องการ 2 สิ่งที่ไม่ซ้ำกัน

  • รหัสแฮชที่ไม่ซ้ำกันสำหรับคีย์
  • ดัชนีที่ไม่ซ้ำเพื่อเก็บค่าจริง

วิธีที่เราจะได้รับดัชนีที่ไม่ซ้ำ? ด้วยการทำให้ขนาดเริ่มต้นของคอนเทนเนอร์ภายในเป็นสิ่งที่ดีเช่นกัน ดังนั้นโดยทั่วไปแล้วไพรม์มีส่วนเกี่ยวข้องเพราะมันมีคุณสมบัติที่เป็นเอกลักษณ์ในการสร้างตัวเลขที่ไม่เหมือนใครซึ่งเราจะนำไปใช้กับวัตถุ ID และค้นหาดัชนีภายในคอนเทนเนอร์ภายใน

ตัวอย่าง:

key = "key"

value = "value" uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"

แผนที่ไปยังid ที่ไม่ซ้ำกัน

ตอนนี้เราต้องการที่ตั้งที่ไม่เหมือนใครสำหรับความคุ้มค่าของเรา - ดังนั้นเรา

uniqueId % internalContainerSize == uniqueLocationForValueสมมติว่าinternalContainerSizeยังเป็นนายก

ฉันรู้ว่ามันง่าย แต่ฉันหวังว่าจะได้ความคิดทั่วไป


0

"ธรรมชาติของคณิตศาสตร์" เกี่ยวกับโมดูลพลังงานที่สำคัญคือพวกเขาเป็นหนึ่งในการสร้างบล็อกของฟิลด์ จำกัด Building Block อีกสองส่วนเป็นการดำเนินการเพิ่มเติมและการคูณ คุณสมบัติพิเศษของตัวดัดแปลงที่สำคัญคือพวกมันสร้างฟิลด์ จำกัด ด้วยการเพิ่มและการคูณแบบ "ปกติ" ที่เพิ่งนำไปโมดูลัส นี่หมายถึงการคูณทุกการแมปกับจำนวนเต็มโมดูโลที่แตกต่างกันดังนั้นจะเพิ่มทุกครั้ง

Prime moduli มีประโยชน์เนื่องจาก:

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

อย่างไรก็ตามพวกเขามีข้อเสียใหญ่พวกเขาต้องการส่วนจำนวนเต็มซึ่งใช้เวลา (~ 15-40) รอบจำนวนมากแม้ใน CPU ที่ทันสมัย ด้วยการคำนวณครึ่งหนึ่งสามารถมั่นใจได้ว่าแฮชผสมกันได้ดีมาก การคูณสองครั้งและการดำเนินการ xorshift จะผสมผสานกันได้ดีกว่า moudulus ที่สำคัญ จากนั้นเราสามารถใช้ขนาดตารางแฮชใด ๆ และการลดแฮชเร็วที่สุดโดยให้การดำเนินการทั้งหมด 7 รายการสำหรับกำลังของขนาดตาราง 2 และการดำเนินการประมาณ 9 รายการสำหรับขนาดที่กำหนดเอง

ฉันเพิ่งดูหลาย ๆ การใช้งานตารางแฮชที่เร็วที่สุดและส่วนใหญ่ไม่ได้ใช้ตัวดัดแปลงที่สำคัญ


0

คำถามนี้ถูกรวมเข้ากับคำถามที่เหมาะสมกว่าเหตุใดตารางแฮชควรใช้อาร์เรย์ขนาดใหญ่และไม่ใช่กำลัง 2 สำหรับฟังก์ชันแฮชนั้นมีคำตอบที่ดีมากมาย แต่สำหรับคำถามที่เกี่ยวข้องทำไมตารางแฮชที่สำคัญด้านความปลอดภัย เช่นเดียวกับ glibc ให้ใช้อาร์เรย์ขนาดใหญ่ แต่ก็ยังไม่มี

โดยทั่วไปพลังของ 2 ตารางจะเร็วกว่ามาก มีค่าใช้จ่ายแพงh % n => h & bitmaskซึ่ง bitmask สามารถคำนวณได้ผ่านทางclz("ศูนย์นำหน้านับ") ของขนาด n ความต้องการฟังก์ชั่นแบบโมดูโลจะทำแบ่งจำนวนเต็มซึ่งเป็นเรื่องเกี่ยวกับ 50x andช้ากว่าตรรกะ มีเทคนิคบางอย่างเพื่อหลีกเลี่ยง modulo เช่นการใช้https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/แต่โดยทั่วไปแล้วตารางแฮชที่รวดเร็วใช้พลังงาน ของ 2 และตารางแฮชที่ปลอดภัยใช้เฉพาะช่วงเวลา

ทำไมเป็นเช่นนั้น

ความปลอดภัยในกรณีนี้ถูกกำหนดโดยการโจมตีในกลยุทธ์การแก้ปัญหาการชนซึ่งมีตารางแฮชส่วนใหญ่เพียงแค่ค้นหาเชิงเส้นในรายการการชนที่เชื่อมโยง หรือด้วยการค้นหาเชิงเส้นแบบเปิดตารางที่เร็วขึ้นในตารางโดยตรง ดังนั้นด้วยพลังของ 2 ตารางและความรู้ภายในของตารางบางอย่างเช่นขนาดหรือลำดับของรายการของคีย์ที่จัดเตรียมโดยอินเตอร์เฟส JSON บางตัวคุณจะได้รับจำนวนบิตที่ถูกต้อง จำนวนของสิ่งที่อยู่บน bitmask โดยทั่วไปแล้วจะต่ำกว่า 10 บิต และสำหรับ 5-10 บิตมันเป็นเรื่องไม่สำคัญที่จะทำให้เกิดการชนอย่างรุนแรงถึงแม้จะมีฟังก์ชันแฮชที่แข็งแกร่งและช้าที่สุดก็ตาม คุณไม่ได้รับความปลอดภัยเต็มรูปแบบของฟังก์ชั่นแฮชขนาด 32 บิตหรือ 64 บิตอีกต่อไป และประเด็นก็คือการใช้ฟังก์ชั่นแฮชขนาดเล็กอย่างรวดเร็วไม่ใช่มอนสเตอร์เช่นบ่นหรือแม้กระทั่งกาลักน้ำ

ดังนั้นหากคุณมีอินเทอร์เฟซภายนอกกับตารางแฮชของคุณเช่นตัวแก้ไข DNS, ภาษาการเขียนโปรแกรม, ... คุณต้องการที่จะดูแลผู้ใช้ที่ชอบใช้บริการเช่น DOS โดยปกติแล้วคนกลุ่มนี้จะปิดบริการสาธารณะของคุณด้วยวิธีที่ง่ายกว่า แต่ก็เกิดขึ้นได้ง่ายกว่า ดังนั้นคนดูแล

ดังนั้นตัวเลือกที่ดีที่สุดในการป้องกันการโจมตีจากการชนนั้นก็เป็นเช่นนั้น

1) เพื่อใช้ตารางที่สำคัญเพราะแล้ว

  • 32 หรือ 64 บิตทั้งหมดมีความเกี่ยวข้องเพื่อค้นหาที่ฝากข้อมูลไม่ใช่เพียงบางส่วนเท่านั้น
  • ฟังก์ชั่นการปรับขนาดตารางแฮชเป็นธรรมชาติมากกว่าเพียงแค่สองเท่า ฟังก์ชันการเติบโตที่ดีที่สุดคือลำดับฟีโบนักชีและจำนวนเฉพาะเข้ามาใกล้กว่านั้นเป็นสองเท่า

2) ใช้มาตรการที่ดีกว่ากับการโจมตีที่เกิดขึ้นจริงพร้อมกับพลังที่รวดเร็วขนาด 2

  • นับการชนและยกเลิกหรือนอนในการโจมตีที่ตรวจพบซึ่งเป็นหมายเลขการชนที่มีความน่าจะเป็น <1% เช่น 100 กับตารางแฮช 32 บิต นี่คือสิ่งที่ตัวแก้ไข DNS ของ djb ทำ
  • แปลงรายการที่เชื่อมโยงของการชนกันเป็นต้นไม้ด้วยการค้นหา O (บันทึก n) ไม่ใช่ O (n) เมื่อตรวจพบการโจมตีการปะทะ นี่คือสิ่งที่เช่นจาวาทำ

มีเรื่องเล่ามากมายที่ฟังก์ชั่นแฮชที่ปลอดภัยมากขึ้นช่วยป้องกันการโจมตีดังกล่าวซึ่งเป็นสิ่งที่ผิดตามที่ฉันอธิบาย ไม่มีการรักษาความปลอดภัยที่มีบิตต่ำเท่านั้น สิ่งนี้จะใช้ได้กับตารางขนาดใหญ่เท่านั้น แต่จะใช้ทั้งสองวิธีที่ช้าที่สุดคือ hash ช้าและ modulo ที่ช้าที่สุด

ฟังก์ชั่นแฮชสำหรับตารางแฮชเป็นหลักจะต้องมีขนาดเล็ก (จะ inlinable) และรวดเร็ว ความปลอดภัยอาจมาจากการป้องกันการค้นหาเชิงเส้นในการชน และไม่ควรใช้ฟังก์ชั่นแฮชที่ไม่ดีเล็กน้อยเช่นค่าที่ไม่ไวต่อค่าบางค่า (เช่น \ 0 เมื่อใช้การคูณ)

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


-1
function eratosthenes(n) {

    function getPrime(x) {
        var middle = (x-(x%2))/2;
        var arr_rest = [];
        for(var j=2 ; j<=middle;j++){
            arr_rest.push(x%j);
        }

        if(arr_rest.indexOf(0) == -1) {
            return true
        }else {
            return false
        }

    }
    if(n<2)  {
        return []
    }else if(n==2){
        return [2]
    }else {
        var arr = [2]
        for(var i=3;i<n;i++) {
            if(getPrime(i)){
                arr.push(i)
            }
        }
    }

    return arr;
}

2
คุณสามารถเพิ่มความคิดเห็นเพื่ออธิบายการแก้ปัญหาของคุณได้ไหม
pom421
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.