Python: Look up tablosu için Dict ile Liste


169

Bir tür arama tablosuna koymam gereken yaklaşık 10 milyon değerim var, bu yüzden hangisinin daha etkili bir liste veya dikte olacağını merak ediyordum ?

Her ikisi için de böyle bir şey yapabileceğinizi biliyorum:

if something in dict_of_stuff:
    pass

ve

if something in list_of_stuff:
    pass

Benim düşüncem, bu kararın daha hızlı ve daha verimli olacağıdır.

Yardımınız için teşekkürler.

EDIT 1
Ne yapmaya çalıştığım hakkında biraz daha fazla bilgi. Euler Sorunu 92 . Hesaplanan bir değerin tüm hesaplanıp hesaplanmadığını görmek için bir arama tablosu yapıyorum.

EDIT 2 Arama
için verimlilik.

DÜZENLEME 3
Değerle ilişkilendirilmiş hiçbir değer yok ... yani bir set daha iyi olur mu?


1
Ne açısından verimlilik? Ekle? Yukarı Bak? Bellek tüketimi? Saf değer varlığını kontrol ediyor musunuz, ya da onunla ilişkili herhangi bir meta veri var mı?
truppo

Bir yan not olarak, bu özel sorun için 10 milyon listeye veya imaya ihtiyacınız yok, ancak çok daha küçük bir listeye ihtiyacınız var.
sfotiadis

Yanıtlar:


223

hız

Listelerdeki aramalar O (n), sözlüklerdeki aramalar O (1) 'in veri yapısındaki kalem sayısına göre itfa edilmektedir. Değerleri ilişkilendirmeniz gerekmiyorsa, kümeleri kullanın.

Hafıza

Hem sözlükler hem de kümeler karma kullanır ve yalnızca nesne depolamaktan çok daha fazla bellek kullanırlar. Güzel Kodda AM Kuchling'e göre , uygulama karmayı 2/3 dolu tutmaya çalışıyor, bu yüzden biraz bellek harcayabilirsiniz.

Anında yeni girişler eklemezseniz (güncellenmiş sorunuza dayanarak yaparsınız), listeyi sıralamak ve ikili aramayı kullanmak faydalı olabilir. Bu O (log n) 'dir ve dizeler için daha yavaş olabilir, doğal sıralaması olmayan nesneler için imkansızdır.


6
Evet, ancak içerik asla değişmezse tek seferlik bir işlemdir. İkili arama O (log n) şeklindedir.
Torsten Marek

1
@John Fouhy: ints hash tablosunda saklanmaz, sadece işaretçiler, yani hou ints için 40M'ye sahiptir (pek çoğu küçükken gerçekten değil) ve hash tablosu için 60M'dir. Bugünlerde çok fazla sorun olmadığını kabul ediyorum, yine de akılda tutmaya değer.
Torsten Marek

2
Bu eski bir soru, ama amortismanlı O (1) çok büyük setler / zorlamalar için geçerli olmayabilir. Wiki.python.org/moin/TimeComplexity'ye göre en kötü senaryo O (n). Sanırım ortalama zamanın O (1) 'den farklılaştığı ve O (n)' ye yakınlaşmaya başladığı dahili hash uygulamasına bağlıdır. Küresel kümeleri kolayca fark edilebilen bazı özelliklere (ilk basamağın değeri, ardından ikinci, üçüncü vb. En uygun ayar boyutunu elde etmeniz gerektiği sürece) göre daha küçük bölümlere ayırarak arama performansına yardımcı olabilirsiniz . .
Nisan.H

3
@TorstenMarek Bu beni şaşırttı. Gönderen bu sayfayı , liste arama O (1) ve dict arama Söylediklerinizi tersidir O (n) 'dir. Yanlış anlıyor muyum?
temporary_user_name

3
@Aerovistae Sanırım bu sayfadaki bilgileri yanlış okudun. Liste altında "x in s" (arama) için O (n) görüyorum. Ayrıca set ve dik aramayı O (1) ortalama durum olarak gösterir.
Dennis

45

Bir diksiyon bir karma tablodur, bu yüzden anahtarları bulmak gerçekten hızlıdır. Dict ve list arasında, dict daha hızlı olurdu. Ancak ilişkilendirilecek bir değeriniz yoksa, bir küme kullanmak daha da iyidir. Bu "table" kısmı olmayan bir karma tablodur.


EDIT: yeni sorunuz için, EVET, bir set daha iyi olurdu. Sadece 2 set oluşturun, biri 1 ile biten diziler için, diğeri 89 ile biten diziler için. Başarıyla başarıyla bu sorunu setler kullanarak çözdüm.



31

Bazı kıyaslama yaptım ve bu, hem iX CPU'da python 2.7.3'ü çalıştıran hem de listeden daha hızlı olduğu ve büyük veri kümeleri için ayarlandığı ortaya çıktı:

  • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

    10 döngü, döngü başına en iyi 3: 64,2 ms

  • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

    10000000 döngü, döngü başına en iyi 3: 0,0759 usec

  • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

    1000000 döngü, döngü başına en iyi 3: 0,262 usec

Gördüğünüz gibi, diksiyon listeden çok daha hızlı ve ayarlanandan yaklaşık 3 kat daha hızlı. Bazı uygulamalarda yine de güzelliği için set seçmek isteyebilirsiniz. Ve eğer veri setleri gerçekten küçükse (<1000 eleman) listeler oldukça iyi performans gösterir.


Tam tersi olmamalı mı? Liste: 10 * 64.2 * 1000 = 642000 usec, dict: 10000000 * 0.0759 = 759000 usec ve set: 1000000 * 0.262 = 262000 usec ... bu yüzden setler en hızlı, ardından listeden ve örneğinizde son olarak dict ile. Yoksa bir şey mi kaçırıyorum?
andzep

1
... ama burada benim için soru şu: bu zamanlar gerçekten neyi ölçüyor? Belirli bir liste, dikte veya küme için erişim süresi değil, çok daha fazlası, liste oluşturma , dikte etme, ayarlama ve son olarak bir değeri bulma ve erişme zamanı ve döngüler . Peki, bunun soru ile bir ilgisi var mı? ... Yine de ilginç ...
andzep

8
@andzep, yanılıyorsunuz, -sseçenek timeitortamı kurmaktır, yani toplam süreyi saymaz. -sSeçenek yalnızca bir kez çalıştırılır. Python 3.3'te şu sonuçları alıyorum: gen (aralık) -> 0.229 usec, liste -> 157 msn, dikte -> 0.0806 usec, set -> 0.0807 usec. Set ve dikte performansı aynıdır. Ancak Dict'in başlatılması biraz daha uzun sürüyor (toplam süre 13.580s - 11.803s)
sleblanc

1
neden yerleşik set kullanmıyorsunuz? Aslında setlerle Set'Set () çok daha kötü sonuçlar elde ediyorum ()
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest Yerleşik set python 2.4'te tanıtıldı, bu yüzden önerdiğim çözümde neden kullanmadığından emin değilim. python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"Python 3.6.0'ı (10000000 döngü, döngü başına en iyi 3: 0,0608 usec) kullanarak, kabaca dik ölçütüyle aynı şekilde iyi performans elde ediyorum , bu nedenle yorumunuz için teşekkür ederiz.
EriF89

6

Bir dikte istiyorsun.

Python'daki (sıralanmamış) listeler için, "giriş" işlemi O (n) süresi gerektirir - büyük miktarda veri olduğunda iyi değil. Bir dikte, bir karma tablodur, böylece O (1) arama süresi bekleyebilirsiniz.

Diğerlerinin de belirttiği gibi, anahtar / değer çiftleri yerine yalnızca anahtarlarınız varsa, bir küme (özel bir tür dikte) seçebilirsiniz.

İlişkili:

  • Python wiki : Python konteyner işlemlerinin zaman karmaşıklığı hakkında bilgi.
  • SO : Python konteyner çalışma süresi ve bellek karmaşıklıkları

1
Sıralı listeler için bile "in" O (n) 'dir.

2
Bağlantılı bir liste için evet --- ancak Python'daki "listeler", çoğu kişinin O (1) dizinli erişim ve sıralandığında O (günlük n) içinde bir bulma işlemi sağlayan vektörler dediği şeydir.
zweiterlinde

Sıralı inbir listeye uygulanan operatörün sıralanmamış bir listeye uygulandığındakinden daha iyi performans gösterdiğini mi söylüyorsunuz (rastgele bir değer araması için)? (Bunların dahili olarak vektörler veya bağlantılı listedeki düğümler olarak uygulanıp uygulanmadığını düşünmüyorum.)
martineau

4

eğer veri benzersizse set () en verimli, ama iki dikten (aynı zamanda benzersizlik gerektirir, oops :)


% cevabımı gönderdiğimi gördüğümde farkettim)
SilentGhost

2
@SilentGhost cevap yanlışsa, neden silmiyorsunuz? upvotes için çok kötü, ama bu olur (iyi, oldu )
Jean-François Fabre

3

@ EriF89'u göstermek için yeni bir test seti onca yıl sonra hala geçerli:

$ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.84 msec per loop
$ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 573 msec per loop
$ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 587 msec per loop
$ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.88 msec per loop

Burada bazı kullanım durumlarında daha tuplehızlı olduğu bilinen lists(ve daha az bellek kullanan) a'yı karşılaştırıyoruz. Arama tablosu durumunda,tuple daha iyi sonuç vermedi.

Hem dictveset çok iyi performans. Bu, benzersizlik hakkında @SilentGhost yanıtına ilginç bir nokta getiriyor: OP'nin bir veri kümesinde 10M değerleri varsa ve bunların içinde yinelenmeler olup olmadığı bilinmiyorsa, öğelerinin bir kümesini / dikteğini paralel tutmaya değer gerçek veri setiyle ve bu set / dikte varlığını test ederek. 10M veri noktalarının sadece 10 benzersiz değere sahip olması mümkündür, bu da aramak için çok daha küçük bir alandır!

SilentGhost'un dicts hakkındaki hatası aslında aydınlatıcıdır, çünkü biri yinelenen verileri (değerlerde) çoğaltılmamış bir kümeyle (anahtarlar) ilişkilendirmek ve böylece bir veri nesnesini tüm verileri tutmak için tutabilir, ancak yine de bir arama tablosu olarak hızlı olabilir. Örneğin, bir dikte anahtarı aranan değer olabilir ve değer, bu değerin oluştuğu hayali bir listedeki dizinlerin bir listesi olabilir.

Örneğin, aranacak kaynak veri listesi l=[1,2,3,1,2,1,4]olsaydı, bu dikteyle değiştirilerek hem arama hem de bellek için optimize edilebilir:

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> l=[1,2,3,1,2,1,4]
>>> for i, e in enumerate(l):
...     d[e].append(i)
>>> d
defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})

Bu dikte ile kişi şunu bilebilir:

  1. Orijinal veri kümesinde bir değer varsa (yani 2 in ddöndürür True)
  2. Burada değeri ilk veri kümesi (yani d[2]veriler orijinal veri listesinde bulunmuştur indekslerinin listesine verir: [1, 4])

Son paragrafınız için, onu okumak mantıklı olsa da, açıklamaya çalıştığınız gerçek kodu görmek güzel (ve muhtemelen kavraması daha kolay) olacaktır.
kaiser

0

Aslında 10 milyon değeri tabloya kaydetmenize gerek yok, bu yüzden her iki şekilde de önemli değil.

İpucu: İlk kare işlemi toplamından sonra sonucunuzun ne kadar büyük olabileceğini düşünün. Mümkün olan en büyük sonuç 10 milyondan çok daha küçük olacak ...

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.