Python: Daftar vs Diktik untuk mencari tabel

169

Saya memiliki sekitar 10 juta nilai yang harus saya masukkan ke dalam beberapa jenis tabel pencarian, jadi saya bertanya-tanya mana yang akan lebih efisien daftar atau dikt ?

Saya tahu Anda dapat melakukan sesuatu seperti ini untuk keduanya:

if something in dict_of_stuff:
    pass

dan

if something in list_of_stuff:
    pass

Pikiran saya adalah dikt akan lebih cepat dan lebih efisien.

Terima kasih atas bantuan Anda.

Sunting 1
Sedikit info lagi tentang apa yang saya coba lakukan. Masalah Euler 92 . Saya sedang membuat tabel pencarian untuk melihat apakah nilai yang dihitung sudah siap dihitung.

EDIT 2
Efisiensi untuk mencari.

EDIT 3
Tidak ada nilai yang diasosiasikan dengan nilai ... jadi apakah himpunan akan lebih baik?

Nggak
sumber
1
Efisiensi dalam hal apa? Memasukkan? Menengadah? Konsumsi memori? Apakah Anda memeriksa keberadaan nilai murni, atau adakah metadata yang terkait dengannya?
truppo
Sebagai catatan tambahan, Anda tidak perlu daftar 10 juta atau dikt untuk masalah khusus tetapi yang jauh lebih kecil.
sfotiadis

Jawaban:

222

Mempercepat

Pencarian dalam daftar adalah O (n), pencarian dalam kamus diamortisasi O (1), berkenaan dengan jumlah item dalam struktur data. Jika Anda tidak perlu mengaitkan nilai, gunakan set.

Penyimpanan

Baik kamus dan set menggunakan hashing dan mereka menggunakan lebih banyak memori daripada hanya untuk penyimpanan objek. Menurut AM Kuchling dalam Kode Indah , implementasi mencoba untuk menjaga hash 2/3 penuh, sehingga Anda mungkin membuang cukup banyak memori.

Jika Anda tidak menambahkan entri baru dengan cepat (yang Anda lakukan, berdasarkan pertanyaan Anda yang diperbarui), mungkin ada baiknya menyortir daftar dan menggunakan pencarian biner. Ini adalah O (log n), dan cenderung lebih lambat untuk string, tidak mungkin untuk objek yang tidak memiliki urutan alami.

Torsten Marek
sumber
6
Ya, tapi ini operasi sekali saja jika isinya tidak pernah berubah. Pencarian biner adalah O (log n).
Torsten Marek
1
@ John Fouhy: int tidak disimpan dalam tabel hash, hanya pointer, yaitu Anda memiliki 40M untuk ints (well, tidak benar-benar ketika banyak dari mereka kecil) dan 60M untuk tabel hash. Saya setuju bahwa ini bukan masalah besar saat ini, masih ada baiknya untuk diingat.
Torsten Marek
2
Ini adalah pertanyaan lama, tetapi saya pikir amortisasi O (1) mungkin tidak berlaku untuk set / dikte yang sangat besar. Skenario kasus terburuk menurut wiki.python.org/moin/TimeComplexity adalah O (n). Saya kira itu tergantung pada implementasi hashing internal pada titik waktu rata-rata menyimpang dari O (1) dan mulai konvergen pada O (n). Anda dapat membantu kinerja pencarian dengan mengelompokkan kumpulan global ke dalam bagian yang lebih kecil berdasarkan pada beberapa atribut yang mudah dilihat (seperti nilai digit pertama, lalu kedua, ketiga, dll., Selama Anda perlu mendapatkan ukuran set yang optimal) .
Nisan.H
3
@ TorstenMarek Ini membingungkan saya. Dari halaman ini , daftar pencarian adalah O (1) dan pencarian dict adalah O (n), yang merupakan kebalikan dari apa yang Anda katakan. Apakah saya salah paham?
temporary_user_name
3
@Aerovistae Saya pikir Anda salah membaca info di halaman itu. Di bawah daftar, saya melihat O (n) untuk "x in s" (pencarian). Ini juga menunjukkan set dan dict lookup sebagai O (1) kasus rata-rata.
Dennis
45

Dict adalah tabel hash, jadi sangat cepat untuk menemukan kunci. Jadi antara dict dan daftar, dict akan lebih cepat. Tetapi jika Anda tidak memiliki nilai untuk dikaitkan, lebih baik menggunakan satu set. Ini adalah tabel hash, tanpa bagian "table".


EDIT: untuk pertanyaan baru Anda, YA, satu set akan lebih baik. Cukup buat 2 set, satu untuk urutan berakhir dengan 1 dan lainnya untuk urutan berakhir pada 89. Saya telah berhasil memecahkan masalah ini menggunakan set.

nosklo
sumber
35

set()persis apa yang Anda inginkan. O (1) pencarian, dan lebih kecil dari dict.

rekursif
sumber
31

Saya melakukan benchmarking dan ternyata dict lebih cepat daripada daftar dan ditetapkan untuk set data besar, menjalankan python 2.7.3 pada CPU i7 di linux:

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

    10 loop, terbaik 3: 64,2 msec per loop

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

    10000000 loop, terbaik 3: 0,0759 usec per loop

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

    10.00000 loop, terbaik 3: 0,262 usec per loop

Seperti yang Anda lihat, dict jauh lebih cepat dari daftar dan sekitar 3 kali lebih cepat dari yang ditetapkan. Dalam beberapa aplikasi Anda mungkin masih ingin memilih set untuk keindahannya. Dan jika set data benar-benar kecil (<1000 elemen) daftar berkinerja cukup baik.

EriF89
sumber
Bukankah seharusnya sebaliknya? Daftar: 10 * 64.2 * 1000 = 642000 usec, dict: 10000000 * 0,0759 = 759000 usec dan set: 1000000 * 0,262 = 262000 usec ... jadi set adalah yang tercepat, diikuti oleh daftar dan dengan dict sebagai terakhir pada contoh Anda. Atau apakah saya melewatkan sesuatu?
andzep
1
... tapi pertanyaan untuk saya di sini adalah: apa yang sebenarnya diukur kali ini? Bukan waktu akses untuk daftar tertentu, dict atau set, tetapi lebih dari itu, waktu dan loop untuk membuat daftar, dict, set dan akhirnya untuk menemukan dan mengakses satu nilai. Jadi, apakah ini ada hubungannya dengan pertanyaan itu? ... Sangat menarik ...
andzep
8
@ danzep, Anda salah, -sopsinya adalah mengatur timeitlingkungan, yaitu tidak dihitung dalam total waktu. The -sopsi dijalankan hanya sekali. Pada Python 3.3, saya mendapatkan hasil ini: gen (range) -> 0,229 usec, daftar -> 157 msec, dict -> 0,0806 usec, set -> 0,0807 usec. Mengatur dan mendiktekan kinerja adalah sama. Namun Dict membutuhkan waktu lebih lama untuk diinisialisasi daripada yang ditetapkan (total waktu 13,580s v. 11,803s)
sleblanc
1
mengapa tidak menggunakan set builtin? Saya benar-benar mendapatkan hasil yang lebih buruk dengan sets.Set () daripada dengan set builtin ()
Thomas Guyot-Sionnest
2
@ ThomasGuyot-Sionnest Set bawaan dibangun dengan python 2.4 jadi saya tidak yakin mengapa saya tidak menggunakannya dalam solusi yang saya usulkan. Saya mendapatkan kinerja yang baik dengan python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"menggunakan Python 3.6.0 (10.000000 loop, terbaik 3: 0,0608 usec per loop), kira-kira sama dengan benchmark dict jadi terima kasih atas komentar Anda.
EriF89
6

Anda menginginkan sebuah dikt.

Untuk daftar (tidak disortir) dalam Python, operasi "dalam" memerlukan waktu O (n) --- tidak baik ketika Anda memiliki sejumlah besar data. Dict, di sisi lain, adalah tabel hash, sehingga Anda dapat mengharapkan O (1) waktu pencarian.

Seperti yang telah dicatat orang lain, Anda mungkin memilih satu set (jenis dict khusus) sebagai gantinya, jika Anda hanya memiliki kunci daripada pasangan kunci / nilai.

Terkait:

  • Python wiki : informasi tentang kompleksitas waktu operasi wadah Python.
  • SO : Waktu operasi wadah python dan kompleksitas memori
zweiterlinde
sumber
1
Bahkan untuk daftar yang diurutkan, "in" adalah O (n).
2
Untuk daftar tertaut, ya --- tetapi "daftar" dengan Python adalah apa yang kebanyakan orang sebut vektor, yang menyediakan akses terindeks dalam O (1) dan operasi pencarian di O (log n), ketika diurutkan.
zweiterlinde
Apakah Anda mengatakan bahwa inoperator yang diterapkan ke daftar yang disortir berkinerja lebih baik daripada ketika diterapkan pada yang tidak disortir (untuk pencarian nilai acak)? (Saya tidak berpikir apakah mereka diimplementasikan secara internal sebagai vektor atau sebagai node dalam daftar tertaut adalah relevan.)
martineau
4

jika data adalah set unik () akan menjadi yang paling efisien, tetapi dari dua - dict (yang juga membutuhkan keunikan, oops :)

SilentGhost
sumber
Saya telah menyadari ketika saya melihat jawaban saya diposting%)
SilentGhost
2
@ SilentGhost jika jawabannya salah, mengapa tidak menghapusnya? Sayang sekali untuk upvotes, tapi itu terjadi (well, terjadi )
Jean-François Fabre
3

Sebagai rangkaian tes baru untuk menunjukkan @ EriF89 masih tepat setelah bertahun-tahun:

$ 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

Di sini kami juga membandingkan a tuple, yang diketahui lebih cepat daripada lists(dan menggunakan lebih sedikit memori) dalam beberapa kasus penggunaan. Dalam hal tabel pencarian, tuplefaired tidak lebih baik.

Baik itu dictdan setdilakukan dengan sangat baik. Ini memunculkan poin menarik yang mengaitkan jawaban @ SilentGhost tentang keunikan: jika OP memiliki nilai 10M dalam kumpulan data, dan tidak diketahui jika ada duplikat di dalamnya, maka ada baiknya menjaga set / dikt elemen-elemennya secara paralel. dengan set data aktual, dan pengujian untuk keberadaan dalam set / dikt itu. Mungkin saja 10M titik data hanya memiliki 10 nilai unik, yang merupakan ruang yang jauh lebih kecil untuk mencari!

Kesalahan SilentGhost tentang dicts sebenarnya menerangi karena seseorang dapat menggunakan dict untuk mengkorelasikan data duplikat (dalam nilai) ke dalam set (kunci) yang tidak ter-duplikasi, dan dengan demikian menyimpan satu objek data untuk menyimpan semua data, namun masih secepat tabel pencarian. Misalnya, kunci dict dapat berupa nilai yang dicari, dan nilai tersebut dapat berupa daftar indeks dalam daftar imajiner tempat nilai tersebut terjadi.

Misalnya, jika daftar data sumber yang akan dicari adalah l=[1,2,3,1,2,1,4], itu bisa dioptimalkan untuk pencarian dan memori dengan menggantinya dengan dict ini:

>>> 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]})

Dengan dikt ini, orang bisa tahu:

  1. Jika suatu nilai berada dalam dataset asli (yaitu 2 in dpengembalian True)
  2. Di mana nilai itu dalam dataset asli (yaitu d[2]mengembalikan daftar indeks dimana data ditemukan dalam daftar data asli: [1, 4])
hamx0r
sumber
Untuk paragraf terakhir Anda, meskipun masuk akal membacanya, alangkah baiknya (dan mungkin lebih mudah dipahami) untuk melihat kode aktual yang Anda coba jelaskan.
kaiser
0

Anda sebenarnya tidak perlu menyimpan 10 juta nilai dalam tabel, jadi itu bukan masalah besar.

Petunjuk: pikirkan seberapa besar hasil Anda setelah operasi jumlah kotak pertama. Hasil terbesar yang mungkin akan jauh lebih kecil dari 10 juta ...

Kiv
sumber