เหตุใดจึงมีคำสั่งในพจนานุกรมและตั้งค่าเอง?


151

ฉันไม่เข้าใจว่าการวนลูปมากกว่าพจนานุกรมหรือชุดในไพ ธ อนนั้นทำตามคำสั่ง 'ตามอำเภอใจ' อย่างไร

ฉันหมายถึงมันเป็นภาษาการเขียนโปรแกรมดังนั้นทุกอย่างในภาษาจะต้องถูกกำหนด 100% ถูกต้องหรือไม่ Python จะต้องมีอัลกอริทึมบางอย่างที่ตัดสินใจว่าส่วนใดของพจนานุกรมหรือชุดที่เลือกคืออันดับที่หนึ่งวินาทีและอื่น ๆ

ฉันพลาดอะไรไป


1
ใหม่ล่าสุด PyPy สร้าง (2.5 สำหรับ Python 2.7) ทำให้พจนานุกรมรับคำสั่งจากเริ่มต้น
Veedrac

คำตอบ:


236

หมายเหตุ:คำตอบนี้ถูกเขียนขึ้นก่อนการใช้งานdictประเภทจะเปลี่ยนไปใน Python 3.6 รายละเอียดการใช้งานส่วนใหญ่ในคำตอบนี้ยังคงใช้อยู่ แต่ลำดับการแสดงรายการของคีย์ในพจนานุกรมจะไม่ถูกกำหนดโดยค่าแฮชอีกต่อไป การใช้งานชุดยังคงไม่เปลี่ยนแปลง

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

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

รายการเนื้อหา loops กว่าช่องและเพื่อให้ปุ่มมีการระบุไว้ในลำดับที่พวกเขาขณะนี้อาศัยอยู่ในตาราง

ยกตัวอย่างเช่นใช้ปุ่ม'foo'และ'bar'สมมติว่าขนาดของตารางคือ 8 สล็อต ในหลาม 2.7 hash('foo')เป็น-4177197833195190597, คือhash('bar') 327024216814240868Modulo 8 นั่นหมายถึงปุ่มทั้งสองนี้ถูก slotted ในช่อง 3 และ 4 จากนั้น:

>>> hash('foo')
-4177197833195190597
>>> hash('foo') % 8
3
>>> hash('bar')
327024216814240868
>>> hash('bar') % 8
4

นี่เป็นการแจ้งลำดับของรายการของพวกเขา:

>>> {'bar': None, 'foo': None}
{'foo': None, 'bar': None}

ช่องทั้งหมดยกเว้น 3 และ 4 ว่างเปล่าวนรอบตารางแรกแสดงรายการช่อง 3 จากนั้นช่อง 4 จึง'foo'แสดงรายการก่อนหน้า'bar'นี้

barและbazอย่างไรก็ตามมีค่าแฮชที่อยู่ห่างกัน 8 ส่วนดังนั้นจึงจับคู่กับช่องเดียวกัน4:

>>> hash('bar')
327024216814240868
>>> hash('baz')
327024216814240876
>>> hash('bar') % 8
4
>>> hash('baz') % 8
4

ลำดับของพวกเขาตอนนี้ขึ้นอยู่กับคีย์ที่ถูก slotted ก่อน คีย์ที่สองจะต้องถูกย้ายไปยังช่องถัดไป:

>>> {'baz': None, 'bar': None}
{'bar': None, 'baz': None}
>>> {'bar': None, 'baz': None}
{'baz': None, 'bar': None}

ลำดับของตารางแตกต่างกันที่นี่เพราะคีย์หนึ่งหรืออีกอันหนึ่งถูก slotted ก่อน

ชื่อทางเทคนิคสำหรับโครงสร้างพื้นฐานที่ใช้โดย CPython (การดำเนินการของ Python ที่ใช้กันมากที่สุด) คือตารางแฮชหนึ่งที่ใช้เปิดแอดเดรส หากคุณอยากรู้และเข้าใจ C มากพอให้ดูการใช้งาน Cสำหรับรายละเอียดทั้งหมด คุณสามารถดูการนำเสนอ Pycon 2010นี้โดย Brandon Rhodesเกี่ยวกับการdictทำงานของCPython หรือรับสำเนาของBeautiful Codeซึ่งรวมถึงบทเกี่ยวกับการนำไปใช้งานที่เขียนโดย Andrew Kuchling

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

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

CPython 3.6 แนะนำการใช้งานใหม่ dictที่รักษาลำดับการแทรกและเร็วกว่าและมีประสิทธิภาพมากขึ้นในการบูตหน่วยความจำ แทนที่จะเก็บตารางกระจัดกระจายขนาดใหญ่ที่แต่ละแถวอ้างอิงค่าแฮชที่เก็บไว้และวัตถุคีย์และค่าการนำไปใช้ใหม่เพิ่มอาร์เรย์แฮชขนาดเล็กที่อ้างอิงดัชนีในตาราง 'หนาแน่น' แยกต่างหากเท่านั้น (หนึ่งที่ประกอบด้วยแถวจำนวนมากเท่านั้น เนื่องจากมีคู่คีย์ - ค่าจริง) และเป็นตารางที่มีความหนาแน่นสูงซึ่งเกิดขึ้นกับรายการที่มีอยู่ตามลำดับ ดูข้อเสนอที่จะหลาม-Dev สำหรับรายละเอียดเพิ่มเติม โปรดทราบว่าใน Python 3.6 นี้ถือเป็นรายละเอียดการใช้งาน, Python-the-language ไม่ได้ระบุว่าการใช้งานอื่น ๆ จะต้องรักษาความสงบเรียบร้อย นี้มีการเปลี่ยนแปลงในหลาม 3.7 ซึ่งรายละเอียดนี้ได้รับการยกระดับให้เป็นสเปคภาษา ; สำหรับการใช้งานใด ๆ ที่เข้ากันได้กับ Python 3.7 หรือใหม่กว่านั้นจะต้องคัดลอกพฤติกรรมการรักษาคำสั่งซื้อนี้ และเพื่อความชัดเจน: การเปลี่ยนแปลงนี้ไม่สามารถใช้ได้กับชุดเนื่องจากชุดมีโครงสร้างแฮช 'ขนาดเล็ก' อยู่แล้ว

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

หากคุณต้องการชุดที่สั่งคุณสามารถติดตั้งosetแพคเกจ ; มันทำงานได้บน Python 2.5 ขึ้นไป


1
ฉันไม่คิดว่าการใช้งาน Python อื่น ๆ สามารถใช้สิ่งใดก็ตามที่ไม่ใช่ตารางแฮชไม่ทางใดก็ทางหนึ่ง (แม้ว่าจะมีหลายพันล้านวิธีในการใช้ตารางแฮช ความจริงที่ว่าพจนานุกรมใช้__hash__และ__eq__(และไม่มีอะไรอื่น) คือการรับประกันภาษาจริงไม่ใช่รายละเอียดการใช้งาน

1
@delnan: ฉันสงสัยว่าคุณยังคงสามารถใช้ BTree กับ hashes และการทดสอบความเท่าเทียมกันได้หรือไม่ .. ฉันไม่ได้ตัดสินอย่างนั้นในทุกกรณี :-)
Martijn Pieters

1
แน่นอนว่าถูกต้องและฉันยินดีที่ได้รับการพิสูจน์ความผิดพลาดที่เป็นไปได้ แต่ฉันไม่เห็นว่าจะมีวิธีใดที่จะเอาชนะโต๊ะแฮชได้โดยไม่ต้องมีสัญญาที่กว้างขึ้น BTree จะไม่มีประสิทธิภาพของตัวพิมพ์ที่ดีกว่าและไม่ให้ตัวพิมพ์ที่แย่ที่สุด (การแฮชการชนยังหมายถึงการค้นหาเชิงเส้น) ดังนั้นคุณจะได้รับความต้านทานที่ดีขึ้นสำหรับ hash neomg congruent (mod tablesize) และมีวิธีการมากมายที่จะจัดการมัน (บางอันใช้ในdictobject.c) และจบลงด้วยการเปรียบเทียบที่น้อยกว่า BTree จำเป็นต้องหาทางที่ถูกต้อง ทรีย่อย

@delnan: ฉันเห็นด้วยอย่างสมบูรณ์; ฉันส่วนใหญ่ไม่ต้องการถูกทุบเพราะไม่อนุญาตให้มีตัวเลือกการใช้งานอื่น ๆ
Martijn Pieters

37

นี่เป็นการตอบสนองต่อPython 3.41 มากกว่าชุดก่อนที่จะถูกปิดเป็นชุดซ้ำ


อื่น ๆ ถูกต้อง: อย่าพึ่งพาคำสั่งซื้อ อย่าแม้แต่จะแกล้งทำเป็นว่ามีอยู่จริง

ที่กล่าวว่ามีสิ่งหนึ่งที่คุณสามารถพึ่งพาคือ:

list(myset) == list(myset)

นั่นคือการสั่งซื้อที่มีเสถียรภาพ


การทำความเข้าใจว่าทำไมจึงมีคำสั่งที่รับรู้ต้องการความเข้าใจเล็ก ๆ น้อย ๆ :

  • ว่างูหลามใช้ชุดกัญชา ,

  • วิธีการตั้งค่าแฮชของ CPython ในหน่วยความจำและ

  • ตัวเลขจะถูกแฮชอย่างไร

จากด้านบน:

ชุดกัญชาเป็นวิธีการในการจัดเก็บข้อมูลแบบสุ่มกับเวลาจริงๆค้นหาได้อย่างรวดเร็ว

มันมีอาร์เรย์สำรอง:

# A C array; items may be NULL,
# a pointer to an object, or a
# special dummy object
_ _ 4 _ _ 2 _ _ 6

เราจะไม่สนใจวัตถุจำลองพิเศษซึ่งมีอยู่เพียงเพื่อให้การกำจัดง่ายขึ้นเนื่องจากเราจะไม่ถูกลบออกจากชุดเหล่านี้

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

จากนั้นคุณสร้างในดัชนีโดยการมอดุลัสตามความยาวของอาร์เรย์:

hash(4) % len(storage) = index 2

สิ่งนี้ทำให้เข้าถึงองค์ประกอบได้อย่างรวดเร็ว

แฮชเป็นเพียงเรื่องราวส่วนใหญ่เท่านั้นhash(n) % len(storage)และhash(m) % len(storage)อาจส่งผลให้มีจำนวนเท่ากัน ในกรณีดังกล่าวกลยุทธ์ที่แตกต่างกันสามารถลองและแก้ไขข้อขัดแย้งได้ CPython ใช้ "การสำรวจเชิงเส้น" 9 ครั้งก่อนที่จะทำสิ่งที่ซับซ้อนดังนั้นมันจะดูทางด้านซ้ายของช่องใส่ได้ถึง 9 แห่งก่อนที่จะมองไปที่อื่น

ชุดแฮชของ CPython ถูกเก็บไว้ในลักษณะนี้:

  • ชุดกัญชาสามารถไม่เกิน 2/3 หากมี 20 องค์ประกอบและอาร์เรย์สำรองยาว 30 องค์ประกอบหน่วยเก็บข้อมูลสำรองจะปรับขนาดให้ใหญ่ขึ้น นี่เป็นเพราะคุณได้รับการชนบ่อยขึ้นด้วยร้านค้าสำรองขนาดเล็กและการชนกันทำให้ทุกอย่างช้าลง

  • ที่เก็บข้อมูลสำรองปรับขนาดด้วยพลังของ 4 เริ่มต้นที่ 8 ยกเว้นชุดขนาดใหญ่ (องค์ประกอบ 50k) ซึ่งปรับขนาดในพลังของสอง: (8, 32, 128, ... )

ดังนั้นเมื่อคุณสร้างอาร์เรย์ที่เก็บข้อมูลสำรองจะมีความยาว 8 เมื่อเต็ม 5 และคุณเพิ่มองค์ประกอบมันจะมีองค์ประกอบ 6 รายการ 6 > ²⁄₃·8ดังนั้นสิ่งนี้ทริกเกอร์การปรับขนาดและที่เก็บข้อมูลสำรองเพิ่มเป็นสี่เท่าของขนาด 32

ในที่สุดhash(n)ก็แค่ส่งกลับnสำหรับตัวเลข (ยกเว้น-1ที่พิเศษ)


ดังนั้นเรามาดูสิ่งแรก:

v_set = {88,11,1,33,21,3,7,55,37,8}

len(v_set)เท่ากับ 10 ดังนั้นที่เก็บข้อมูลสำรองอย่างน้อย 15 (+1) หลังจากเพิ่มรายการทั้งหมดแล้ว พลังที่เกี่ยวข้องของ 2 คือ 32 ดังนั้นที่เก็บสำรองคือ:

__ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __

เรามี

hash(88) % 32 = 24
hash(11) % 32 = 11
hash(1)  % 32 = 1
hash(33) % 32 = 1
hash(21) % 32 = 21
hash(3)  % 32 = 3
hash(7)  % 32 = 7
hash(55) % 32 = 23
hash(37) % 32 = 5
hash(8)  % 32 = 8

ดังนั้นการแทรกเหล่านี้เป็น:

__  1 __  3 __ 37 __  7  8 __ __ 11 __ __ __ __ __ __ __ __ __ 21 __ 55 88 __ __ __ __ __ __ __
   33 ← Can't also be where 1 is;
        either 1 or 33 has to move

ดังนั้นเราคาดหวังคำสั่งเช่นนั้น

{[1 or 33], 3, 37, 7, 8, 11, 21, 55, 88}

ด้วย 1 หรือ 33 ที่ไม่ได้เป็นจุดเริ่มต้นที่อื่น สิ่งนี้จะใช้การตรวจเชิงเส้นดังนั้นเราจะได้:

       ↓
__  1 33  3 __ 37 __  7  8 __ __ 11 __ __ __ __ __ __ __ __ __ 21 __ 55 88 __ __ __ __ __ __ __

หรือ

       ↓
__ 33  1  3 __ 37 __  7  8 __ __ 11 __ __ __ __ __ __ __ __ __ 21 __ 55 88 __ __ __ __ __ __ __

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

ตอนนี้คุณสามารถดูว่าทำไม

{7,5,11,1,4,13,55,12,2,3,6,20,9,10}

อาจจะอยู่ในลำดับ มี 14 องค์ประกอบดังนั้นที่เก็บข้อมูลสำรองอย่างน้อย 21 + 1 ซึ่งหมายถึง 32:

__ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __

1 ถึง 13 แฮชใน 13 ช่องแรก 20 ไปในสล็อต 20

__  1  2  3  4  5  6  7  8  9 10 11 12 13 __ __ __ __ __ __ 20 __ __ __ __ __ __ __ __ __ __ __

55 ไปในสล็อตhash(55) % 32ซึ่งคือ 23:

__  1  2  3  4  5  6  7  8  9 10 11 12 13 __ __ __ __ __ __ 20 __ __ 55 __ __ __ __ __ __ __ __

หากเราเลือก 50 แทนเราคาดหวัง

__  1  2  3  4  5  6  7  8  9 10 11 12 13 __ __ __ __ 50 __ 20 __ __ __ __ __ __ __ __ __ __ __

และแท้จริงและดูเถิด:

{1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 20, 50}
#>>> {1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 50, 20}

pop มีการใช้งานค่อนข้างง่ายโดยลักษณะของสิ่งต่าง ๆ : มันสำรวจรายการและผุดรายการแรก


นี่คือรายละเอียดการใช้งานทั้งหมด


17

"Arbitrary" ไม่ใช่สิ่งเดียวกับ "ไม่ได้ตั้งใจ"

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

ดังนั้นหากคุณเขียนโปรแกรมที่ขึ้นอยู่กับคุณสมบัติใด ๆของคำสั่งพจนานุกรมทั้งหมดคุณจะ "ทำลายสัญญา" ของการใช้ประเภทพจนานุกรมและนักพัฒนา Python ไม่ได้สัญญาว่าสิ่งนี้จะใช้ได้ตลอดแม้ว่ามันจะใช้งานได้ สำหรับตอนนี้เมื่อคุณทดสอบ โดยพื้นฐานแล้วมันเทียบเท่ากับการพึ่งพา "พฤติกรรมที่ไม่ได้กำหนด" ใน C


3
โปรดทราบว่าส่วนหนึ่งของการทำซ้ำของพจนานุกรมถูกกำหนดไว้อย่างดี: การวนซ้ำคีย์ค่าหรือรายการของพจนานุกรมที่กำหนดจะเกิดขึ้นในลำดับเดียวกันตราบใดที่ไม่มีการเปลี่ยนแปลงใด ๆ ในพจนานุกรมระหว่างนั้น นั่นหมายความว่าเป็นหลักเหมือนกันd.items() zip(d.keys(), d.values())หากมีการเพิ่มรายการใด ๆ ลงในพจนานุกรมการเดิมพันทั้งหมดจะปิด คำสั่งซื้ออาจมีการเปลี่ยนแปลงอย่างสมบูรณ์ (หากจำเป็นต้องปรับขนาดตารางแฮช) แม้ว่าส่วนใหญ่คุณจะพบว่ารายการใหม่เปิดขึ้นในบางจุดตามลำดับ
Blckknght

6

คำตอบอื่น ๆ สำหรับคำถามนี้ยอดเยี่ยมและเขียนได้ดี OP ถามว่า "อย่างไร" ซึ่งฉันตีความว่าเป็น "พวกเขาหนีไปได้อย่างไร" หรือ "ทำไม"

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

ลำดับที่การผูกจะถูกส่งคืนอาจเป็นกฎเกณฑ์

กล่าวอีกนัยหนึ่งนักศึกษาวิทยาศาสตร์คอมพิวเตอร์ไม่สามารถสันนิษฐานได้ว่ามีการจัดเรียงแบบเชื่อมโยง เช่นเดียวกันกับชุดในคณิตศาสตร์

ลำดับที่อิลิเมนต์ของชุดถูกแสดงรายการไม่เกี่ยวข้อง

และวิทยาการคอมพิวเตอร์

ชุดเป็นชนิดข้อมูลนามธรรมที่สามารถเก็บค่าบางอย่างโดยไม่ต้องสั่งใด ๆ

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


1
คุณพื้นขวา แต่มันจะเป็นเพียงเล็กน้อยใกล้ชิด (และให้คำแนะนำที่ดีในเหตุผลของมัน "สั่ง") จะบอกว่ามันเป็นการดำเนินงานของตารางแฮชมากกว่าอาร์เรย์รอง
นักเล่นแร่แปรธาตุ Two-Bit

5

Python ใช้ตารางแฮชเพื่อจัดเก็บพจนานุกรมดังนั้นจึงไม่มีคำสั่งในพจนานุกรมหรือวัตถุที่ซ้ำได้อื่น ๆ ที่ใช้ตารางแฮช

แต่เกี่ยวกับดัชนีของรายการในวัตถุแฮชหลามคำนวณดัชนีตามรหัสต่อไปนี้ภายในhashtable.c :

key_hash = ht->hash_func(key);
index = key_hash & (ht->num_buckets - 1);

ดังนั้นเมื่อค่าแฮชของจำนวนเต็มเป็นจำนวนเต็ม*ดัชนีจะขึ้นอยู่กับจำนวน ( ht->num_buckets - 1เป็นค่าคงที่) ดังนั้นดัชนีที่คำนวณโดยBitwise - และระหว่าง(ht->num_buckets - 1)และจำนวนตัวมันเอง* (คาดหวังสำหรับ -1 ซึ่งมันคือ -2 ) และสำหรับวัตถุอื่นที่มีค่าแฮช

พิจารณาตัวอย่างต่อไปนี้ด้วยการsetใช้ hash-table:

>>> set([0,1919,2000,3,45,33,333,5])
set([0, 33, 3, 5, 45, 333, 2000, 1919])

สำหรับจำนวนที่33เรามี:

33 & (ht->num_buckets - 1) = 1

ที่จริงมันคือ:

'0b100001' & '0b111'= '0b1' # 1 the index of 33

หมายเหตุในกรณีนี้(ht->num_buckets - 1)เป็นหรือ8-1=70b111

และสำหรับ1919:

'0b11101111111' & '0b111' = '0b111' # 7 the index of 1919

และสำหรับ333:

'0b101001101' & '0b111' = '0b101' # 5 the index of 333

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

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

>>> map(hash, (0, 1, 2, 3))
  [0, 1, 2, 3]
>>> map(hash, ("namea", "nameb", "namec", "named"))
  [-1658398457, -1658398460, -1658398459, -1658398462]

มันไม่ได้เลวร้ายเสมอไป! ในทางกลับกันในตารางที่มีขนาด 2 ** i การรับบิตที่มีลำดับต่ำในขณะที่ดัชนีตารางเริ่มต้นนั้นเร็วมากและไม่มีการชนกันเลยสำหรับการทำดัชนีโดยช่วง ints ที่ต่อเนื่องกัน เช่นเดียวกับประมาณเมื่อคีย์เป็นสตริง "ต่อเนื่อง" ดังนั้นสิ่งนี้ให้พฤติกรรมที่ดีกว่าการสุ่มในกรณีทั่วไปและเป็นที่พึงปรารถนามาก

OTOH เมื่อการชนเกิดขึ้นแนวโน้มที่จะเติมชิ้นส่วนที่ต่อเนื่องกันของตารางแฮชทำให้กลยุทธ์การแก้ปัญหาการชนที่ดีเป็นสิ่งสำคัญ การรับเฉพาะรหัสบิตสุดท้ายของรหัสแฮชก็มีจุดอ่อนเช่นกันตัวอย่างเช่นให้ถือว่ารายการ[i << 16 for i in range(20000)]เป็นชุดของคีย์ เนื่องจาก ints เป็นรหัสแฮชของตัวเองและสิ่งนี้เหมาะกับขนาด 2 ** 15 ดังนั้น 15 บิตสุดท้ายของรหัสแฮชทั้งหมดคือ 0 ทั้งหมด: พวกมันทั้งหมดแมปกับดัชนีตารางเดียวกัน

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


* ฟังก์ชั่นแฮชสำหรับชั้นเรียนint:

class int:
    def __hash__(self):
        value = self
        if value == -1:
            value = -2
        return value


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