Set () nasıl uygulanır?


152

İnsanların setpython'daki nesnelerin O (1) üyelik kontrolüne sahip olduğunu söylediğini gördüm . Buna izin vermek için dahili olarak nasıl uygulanırlar? Ne tür bir veri yapısı kullanıyor? Bu uygulamanın başka etkileri nelerdir?

Buradaki her cevap gerçekten aydınlatıcıydı, ama sadece bir tane kabul edebilirim, bu yüzden orijinal soruma en yakın cevabı vereceğim. Bilgi için hepinize teşekkürler!

Yanıtlar:


139

Bu konuya göre :

Gerçekten de, CPython'un setleri, bu değer eksikliğinden yararlanan bazı optimizasyonlarla, kukla değerlere sahip (anahtarlar kümenin üyesi olan) sözlükler gibi bir şey olarak uygulanır.

Yani temelde bir setveri, altta yatan veri yapısı olarak bir karma kullanılabilir Bu, O (1) üyelik kontrolünü açıklar, çünkü bir hashtable içindeki bir öğeyi aramak ortalama bir O (1) işlemidir.

Çok eğimliyseniz , Achim Domma'ya göre çoğunlukla uygulamadan kes ve yapıştır olan set için CPython kaynak koduna bile göz atabilirsiniz .dict


18
IIRC, orijinal setuygulama aslında idi dict model değerlerle ve daha sonra optimize var.
dan04

1
Büyük O en kötü senaryo değil mi? Eğer zamanın O (n) olduğu bir örnek bulabilirseniz o zaman O (n) .. Şu anda tüm bu öğreticilerden hiçbir şey anlamıyorum.
Claudiu Creanga

4
Hayır, ortalama durum O (1), ancak en kötü durum karma tablo araması için O (N).
Justin Ethier

4
@ClaudiuCreanga bu eski bir yorum, ama sadece açıklığa kavuşturmak için: big-O notasyonu, şeylerin büyüme oranı üzerinde üst sınırlar söyler, ancak ortalama vaka performansının büyümesini üst sınırda tutabilir ve en kötü durumun büyümesini ayrı olarak üst sınırlandırabilirsiniz verim.
Kirk Boyer

79

İnsanlar setlerin O (1) üyelik kontrolüne sahip olduğunu söylediklerinde, ortalama durumdan bahsediyorlar . En kötü durumda (tüm karma değerler çarpıştığında) üyelik denetimi O (n) 'dir. Zaman karmaşıklığı konusunda Python wiki'sine bakın .

Wikipedia makalesi diyor en iyi durumda boyutlandırma olduğunu olmayan bir karma tablo için zaman karmaşıklığı O(1 + k/n). Python kümeleri yeniden boyutlandırılan bir karma tablo kullandığından bu sonuç doğrudan Python kümeleri için geçerli değildir.

Wikipedia makalesinde biraz ilerisinde, için söylüyor ortalama durumda ve zaman karmaşıklığı basit bir üniforma karma işlevi varsayarak O(1/(1-k/n)), k/nbir sabit tarafından sınırlandırılmış olabilir c<1.

Big-O sadece n → ∞ gibi asimtotik davranışı ifade eder. K / n , n'den bağımsız bir sabit ile c <1 ,

O(1/(1-k/n))= O(1/(1-c))ile eşit olandan daha büyük değildir .O(constant)O(1)

Yani tekdüze basit karma varsayım, ortalama olarak , Python setleri için üyelik kontrolüdür O(1).


14

Bence bu yaygın bir hata, setarama (ya da bu konuda hashtable) O (1) değildir.
Wikipedia'dan

En basit modelde, karma işlevi tamamen belirtilmez ve tablo yeniden boyutlandırılmaz. Karma işlevinin mümkün olan en iyi seçimi için, açık adreslemeli n boyutundaki bir tablonun çarpışması olmaz ve başarılı arama için tek bir karşılaştırma ile n öğeye kadar tutar ve zincirleme ve k tuşlarına sahip n boyutundaki bir tablo minimum maks. (0, kn) çarpışmalar ve O (1 + k / n) araması için karşılaştırmalar. Karma işlevinin en kötü seçimi için, her ekleme bir çarpışmaya neden olur ve karma tablolar doğrusal aramada dejenere olur, ekleme başına Ω (k) amortismanlı karşılaştırmalar ve başarılı bir arama için k'ye kadar karşılaştırmalar.

İlgili: Java hashmap gerçekten O (1) mi?


4
Ancak öğeleri aramak için sürekli zaman alırlar: python -m timeit -s "s = set (aralık (10))" "in in" 10000000 döngüler, en iyisi 3: 0,0642 usec döngü başına <--> python - m timeit -s "s = set (aralık (10000000))" "5 in s" 10000000 döngü, döngü başına 3: 0,0634 usec en iyisi ... ve bu MemoryErrors atmayan en büyük set
Jochen Ritzel

2
@ THC4k Tüm kanıtladığınız şey X'e bakmanın sabit zamanda yapıldığı, ancak X + Y'ye bakmak için zamanın aynı miktarda zaman alacağı anlamına gelmiyor, bu O (1) 'in tamamen ilgili olduğu.
Shay Erlichmen

3
@intuited: Öyle, ama yukarıdaki test çalıştırması aynı zamanda "5" e bakabileceğinizi "485398" ya da korkunç bir çarpışma alanında olabilecek başka bir numaraya bakabileceğinizi kanıtlamaz. Bu, aynı öğeyi farklı boyutta bir karma içinde aynı anda aramakla ilgili değildir (aslında, bu hiç gerekli değildir), daha ziyade her bir girişe mevcut tabloda aynı miktarda erişip erişemeyeceğinizle ilgilidir - genellikle her zaman çarpışma olacağından, hash tablolarının başarması imkansız olan bir şey.
Nick Bastin

3
Başka bir deyişle, arama yapma süresi saklanan değerlerin sayısına bağlıdır, çünkü bu çarpışma olasılığını artırır.
intuited

3
@ intuited: hayır, bu yanlış. Kayıtlı değerlerin sayısı arttığında, Python otomatik olarak karma değerinin boyutunu artırır ve çarpışma oranı kabaca sabit kalır. Eşit olarak dağıtılmış bir O (1) karma algoritması varsayarsak, karma arama O (1) olarak itfa edilir . "The Mighty Dictionary" adlı video sunumunu izlemek isteyebilirsiniz python.mirocommunity.org/video/1591/…
Lie Ryan

13

Hepimiz , önceki yorumun söylediği kaynağa kolay erişebiliriz set_lookkey():

/* set object implementation
 Written and maintained by Raymond D. Hettinger <python@rcn.com>
 Derived from Lib/sets.py and Objects/dictobject.c.
 The basic lookup function used by all operations.
 This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
 The initial probe index is computed as hash mod the table size.
 Subsequent probe indices are computed as explained in Objects/dictobject.c.
 To improve cache locality, each probe inspects a series of consecutive
 nearby entries before moving on to probes elsewhere in memory.  This leaves
 us with a hybrid of linear probing and open addressing.  The linear probing
 reduces the cost of hash collisions because consecutive memory accesses
 tend to be much cheaper than scattered probes.  After LINEAR_PROBES steps,
 we then use open addressing with the upper bits from the hash value.  This
 helps break-up long chains of collisions.
 All arithmetic on hash should ignore overflow.
 Unlike the dictionary implementation, the lookkey function can return
 NULL if the rich comparison returns an error.
*/


...
#ifndef LINEAR_PROBES
#define LINEAR_PROBES 9
#endif

/* This must be >= 1 */
#define PERTURB_SHIFT 5

static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)  
{
...

2
Bu cevap C sözdizimi vurgulamasından yararlanır . Yorumun Python sözdizimi vurgulaması gerçekten kötü görünüyor.
user202729

"Bu bizi lineer problama ve açık adresleme meleziyle bırakıyor" yorumuna gelince, lineer problama en.wikipedia.org/wiki/Open_addressing adresinde açıklandığı gibi açık adreslemede bir tür çarpışma çözünürlüğünü araştırmıyor mu? Bu nedenle, doğrusal problama açık adreslemenin bir alt türüdür ve yorumun anlamı yoktur.
Alan Evangelista

2

Biraz arasında daha fazla fark vurgulamak için set'sve dict'sburada, bir alıntıdır setobject.cdicts karşı kümesi en temel fark en netleştirmek açıklama bölümleri.

Setler için kullanım örnekleri, aranan anahtarların bulunma olasılığının yüksek olduğu sözlüklerden oldukça farklıdır. Buna karşılık, kümeler öncelikle bir öğenin varlığının önceden bilinmediği üyelik testleri ile ilgilidir. Buna göre, ayarlanan uygulamanın hem bulunan hem de bulunmayan durum için optimize edilmesi gerekir.

github kaynağı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.