Python, bu döngünün ne zaman (eğer varsa) biteceği konusunda hiçbir söz vermez. Bir kümeyi yineleme sırasında değiştirmek, atlanan öğelere, tekrarlanan öğelere ve diğer tuhaflıklara yol açabilir. Asla böyle davranışlara güvenmeyin.
Söylemek istediğim her şey, önceden haber verilmeksizin değiştirilebilir, uygulama detaylarıdır. Herhangi birine dayanan bir program yazarsanız, programınız Python uygulaması ve CPython 3.8.2 dışındaki sürümlerin herhangi bir kombinasyonunu bozabilir.
Döngünün 16'da neden sona erdiğine dair kısa açıklama, 16'nın önceki öğeden daha düşük bir karma tablo dizinine yerleştirilen ilk öğe olmasıdır. Tam açıklama aşağıdadır.
Bir Python setinin dahili hash tablosu her zaman 2 boyluk bir güce sahiptir. 2 ^ n büyüklüğünde bir tablo için, herhangi bir çarpışma meydana gelmezse, elemanlar, karma tablosundaki, karma değerlerinin en az önemli bitlerine karşılık gelen konumda saklanır. Bunun uygulandığını görebilirsiniz set_add_entry
:
mask = so->mask;
i = (size_t)hash & mask;
entry = &so->table[i];
if (entry->key == NULL)
goto found_unused;
Çoğu küçük Python ints hash eder; özellikle, test karmasındaki tüm ints kendileri için. Bunun uygulandığını görebilirsiniz long_hash
. Kümeniz asla karmalarında eşit düşük bitli iki öğe içermediğinden, çarpışma olmaz.
Bir Python seti yineleyicisi, basit bir tamsayı indeksine sahip bir kümedeki konumunu kümenin dahili karma tablosuna kaydeder. Sonraki öğe istendiğinde, yineleyici, bu dizinden başlayarak karma tablosundaki doldurulmuş bir girdiyi arar, ardından depolanan dizinini bulunan girdinin hemen sonrasına ayarlar ve girdinin öğesini döndürür. Bunu şurada görebilirsiniz 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;
Kümeniz başlangıçta 8 büyüklüğünde bir karma tablosu ve karma tablosundaki 0
0 dizinindeki int nesnesine bir işaretçi ile başlar . Yineleyici de indeks 0'a yerleştirilir. Yinelediğinizde, her biri bir sonraki dizine hash tablosuna öğeler eklenir, çünkü bunların hash'i koymak için söylediği yer budur ve her zaman yineleyicinin baktığı sonraki endeks budur. Kaldırılan elemanlar, çarpışma çözünürlüğü amacıyla eski konumlarında saklanan bir kukla işaretleyiciye sahiptir. Bunun uygulandığını görebilirsiniz 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;
Kümeye 4
eklendiğinde, kümedeki öğelerin ve mankenlerin sayısı, set_add_entry
bir hash tablosunun yeniden oluşturulmasını tetikleyecek kadar yüksek olur ve şunu çağırır 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
hash tablosundaki 2 olan kalabalık, kukla olmayan girişlerin sayısıdır, bu nedenle set_table_resize
ikinci argüman olarak 8 alır. Buna dayanarak , yeni karma tablo boyutunun 16 olması gerektiğine set_table_resize
karar verir :
/* 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 numaralı karma tabloyu yeniden oluşturur. Tüm öğeler hala yeni karma tablosundaki eski dizinlerinde bulunur, çünkü karma değerlerinde yüksek bitler ayarlanmamıştır.
Döngü devam ettikçe, öğeler yineleyicinin bakacağı bir sonraki dizine yerleştirilmeye devam eder. Başka bir karma tablo yeniden oluşturma tetiklenir, ancak yeni boyut hala 16'dır.
Döngü öğe olarak 16 eklediğinde desen kırılır. Yeni öğenin yerleştirileceği dizin 16 yoktur. 16'nın en düşük 4 biti 0000'dır ve dizin 0'a 16 koyar. Yineleyicinin depolanan dizini bu noktada 16'dır ve döngü yineleyiciden sonraki öğeyi istediğinde, yineleyici bunun sonuna kadar gittiğini görür. karma tablo.
Yineleyici bu noktada döngüyü sonlandırır ve yalnızca 16
kümede kalır.
s.add(i+1)
(ve muhtemelen çağrıs.remove(i)
), kümenin yineleme sırasını değiştirerek for döngüsünün oluşturduğu set yineleyicisinin bir sonraki göreceğini etkileyebilir. Etkin bir yineleyiciniz varken nesneyi değiştirmeyin.