Python ไม่รับประกันว่าห่วงนี้จะจบเมื่อใด (ถ้ามี) การแก้ไขชุดระหว่างการทำซ้ำอาจนำไปสู่องค์ประกอบที่ข้ามองค์ประกอบที่ทำซ้ำและความแปลกประหลาดอื่น ๆ อย่าพึ่งพาพฤติกรรมดังกล่าว
ทุกสิ่งที่ฉันจะพูดคือรายละเอียดการใช้งานอาจมีการเปลี่ยนแปลงโดยไม่ต้องแจ้งให้ทราบล่วงหน้า หากคุณเขียนโปรแกรมที่ใช้โปรแกรมใดโปรแกรมหนึ่งของคุณอาจหยุดการทำงานของ Python และรุ่นอื่นที่ไม่ใช่ CPython 3.8.2
คำอธิบายสั้น ๆ ว่าทำไมลูปสิ้นสุดที่ 16 คือ 16 คือองค์ประกอบแรกที่เกิดขึ้นที่ดัชนีตารางแฮชต่ำกว่าองค์ประกอบก่อนหน้า คำอธิบายแบบเต็มอยู่ด้านล่าง
ตารางแฮชภายในของชุด Python มีขนาดกำลังสองเสมอ สำหรับตารางขนาด 2 ^ n หากไม่มีการชนเกิดขึ้นองค์ประกอบต่างๆจะถูกเก็บไว้ในตำแหน่งในตารางแฮชที่สอดคล้องกับบิตแฮชที่มีนัยสำคัญน้อยที่สุด คุณสามารถเห็นสิ่งนี้นำมาใช้ในset_add_entry
:
mask = so->mask;
i = (size_t)hash & mask;
entry = &so->table[i];
if (entry->key == NULL)
goto found_unused;
Python ขนาดเล็กส่วนใหญ่มีการแฮชกับตัวเอง โดยเฉพาะอย่างยิ่ง ints ทั้งหมดในการทดสอบแฮชของตัวเอง long_hash
คุณสามารถดูนี้ดำเนินการใน เนื่องจากชุดของคุณไม่เคยมีสององค์ประกอบที่มีบิตต่ำเท่ากันในแฮชของพวกเขาจึงไม่เกิดการชนกัน
Python ชุด iterator ติดตามตำแหน่งในชุดที่มีดัชนีจำนวนเต็มอย่างง่ายเข้าไปในตารางแฮชภายในของชุด เมื่อมีการร้องขอองค์ประกอบถัดไปตัววนซ้ำจะค้นหารายการที่มีประชากรในตารางแฮชเริ่มต้นที่ดัชนีนั้นจากนั้นตั้งค่าดัชนีที่เก็บไว้เป็นทันทีหลังจากรายการที่พบและส่งกลับองค์ประกอบของรายการ คุณสามารถเห็นสิ่งนี้ในsetiter_iternext
:
while (i <= mask && (entry[i].key == NULL || entry[i].key == dummy))
i++;
si->si_pos = i+1;
if (i > mask)
goto fail;
si->len--;
key = entry[i].key;
Py_INCREF(key);
return key;
ชุดของคุณเริ่มต้นด้วยตารางแฮชขนาด 8 และตัวชี้ไปยัง0
วัตถุ int ที่ดัชนี 0 ในตารางแฮช ตัววนซ้ำยังอยู่ในตำแหน่งที่ดัชนี 0 เมื่อคุณวนซ้ำองค์ประกอบจะถูกเพิ่มลงในตารางแฮชแต่ละรายการในดัชนีถัดไปเพราะนั่นคือที่ที่แฮชของพวกเขาบอกว่าจะใส่มันและนั่นคือดัชนีถัดไปที่ iterator มอง องค์ประกอบที่ถูกลบออกจะมีเครื่องหมายหุ่นจำลองเก็บไว้ที่ตำแหน่งเดิมเพื่อการแก้ไขปัญหาการชน คุณจะเห็นว่ามีการใช้งานในset_discard_entry
:
entry = set_lookkey(so, key, hash);
if (entry == NULL)
return -1;
if (entry->key == NULL)
return DISCARD_NOTFOUND;
old_key = entry->key;
entry->key = dummy;
entry->hash = -1;
so->used--;
Py_DECREF(old_key);
return DISCARD_FOUND;
เมื่อ4
เพิ่มเข้าไปในชุดจำนวนขององค์ประกอบและหุ่นในชุดนั้นจะสูงพอที่set_add_entry
จะก่อให้เกิดการสร้างตารางแฮชเรียกset_table_resize
:
if ((size_t)so->fill*5 < mask*3)
return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
so->used
คือจำนวนของรายการที่เติมแล้วและไม่จำลองในตารางแฮชซึ่งคือ 2 ดังนั้นset_table_resize
รับ 8 เป็นอาร์กิวเมนต์ที่สอง ขึ้นอยู่กับสิ่งนี้set_table_resize
ตัดสินใจว่าขนาดตารางแฮชใหม่ควรเป็น 16:
/* Find the smallest table size > minused. */
/* XXX speed-up with intrinsics */
size_t newsize = PySet_MINSIZE;
while (newsize <= (size_t)minused) {
newsize <<= 1; // The largest possible value is PY_SSIZE_T_MAX + 1.
}
มันสร้างตารางแฮชใหม่ด้วยขนาด 16 องค์ประกอบทั้งหมดยังคงอยู่ที่ดัชนีเก่าของพวกเขาในตารางแฮชใหม่เนื่องจากพวกเขาไม่มีบิตสูงที่ตั้งไว้ในแฮช
เมื่อวนรอบดำเนินไปเรื่อย ๆ องค์ประกอบต่างๆจะยังคงอยู่ที่ดัชนีถัดไปที่ตัววนซ้ำจะดู การสร้างตารางแฮชอีกอันหนึ่งจะเปิดขึ้นมา แต่ขนาดใหม่ยังคงเป็น 16
รูปแบบแตกเมื่อลูปเพิ่ม 16 เป็นองค์ประกอบ ไม่มีดัชนี 16 ที่จะวางองค์ประกอบใหม่ที่ 4 บิตต่ำสุดของ 16 คือ 0000 ใส่ 16 ที่ดัชนี 0 ดัชนีที่เก็บไว้ของ iterator คือ 16 ณ จุดนี้และเมื่อลูปขอองค์ประกอบถัดไปจาก iterator iterator เห็นว่ามันได้ผ่านจุดสิ้นสุดของ ตารางแฮช
ตัววนซ้ำสิ้นสุดการวนซ้ำ ณ จุดนี้เหลือเฉพาะ16
ในชุดเท่านั้น
s.add(i+1)
(และอาจเป็นไปได้ที่การเรียกs.remove(i)
) สามารถเปลี่ยนลำดับการวนซ้ำของชุดได้ อย่ากลายพันธุ์วัตถุในขณะที่คุณมีตัววนซ้ำที่ใช้งานอยู่