Python: Список проти Dict для пошуку таблиці


169

У мене є приблизно 10 мільйонів значень, які мені потрібно помістити в якийсь тип таблиці пошуку, тому мені було цікаво, який би був більш ефективний список чи диктант ?

Я знаю, що ви можете зробити щось подібне для обох:

if something in dict_of_stuff:
    pass

і

if something in list_of_stuff:
    pass

Думаю, що диктат буде швидшим та ефективнішим.

Спасибі за вашу допомогу.

EDIT 1
Трохи більше інформації про те, що я намагаюся зробити. Проблема Ейлера 92 . Я складаю таблицю перегляду, щоб побачити, чи було обчислено все готове значення.

EDIT 2
Ефективність пошуку.

EDIT 3
Немає ніяких значень, пов'язаних зі значенням ... так чи буде набір кращим?


1
Ефективність з точки зору чого? Вставити? Пошук? Споживання пам'яті? Ви перевіряєте наявність чистих значень чи є з цим пов’язані якісь метадані?
truppo

Як бічна примітка, для цієї конкретної проблеми вам не потрібен 10-мільйонний перелік чи диктант, але набагато менший.
sfotiadis

Відповіді:


222

Швидкість

Пошук у списках є O (n), пошук у словниках амортизується O (1), щодо кількості елементів у структурі даних. Якщо вам не потрібно асоціювати значення, використовуйте набори.

Пам'ять

І словники, і набори використовують хешування, і вони використовують набагато більше пам'яті, ніж лише для зберігання об'єктів. За словами А.М. Кухлінга в Beautiful Code , реалізація намагається зберегти хеш 2/3 повноцінним, щоб ви могли витратити досить багато пам’яті.

Якщо ви не додаєте нових записів під час польоту (що ви робите, виходячи з оновленого запитання), можливо, варто буде сортувати список та використовувати двійковий пошук. Це O (log n), і, ймовірно, буде повільніше для рядків, неможливо для об'єктів, які не мають природного впорядкування.


6
Так, але це разова операція, якщо вміст ніколи не змінюється. Двійковий пошук - це O (log n).
Торстен Марек

1
@John Fouhy: ints не зберігаються в хеш-таблиці, лише вказівники, тобто HO мають 40M для ints (ну, не дуже, коли їх багато мало) та 60M для хеш-таблиці. Я погоджуюся, що це не так вже й велика проблема в наші дні, все ж варто пам’ятати про це.
Торстен Марек

2
Це давнє запитання, але я думаю, що амортизований O (1) може не відповідати дуже великим наборам / диктам. Найгірший сценарій згідно wiki.python.org/moin/TimeComplexity - це O (n). Я думаю, це залежить від внутрішньої реалізації хешування, в який момент середній час відходить від O (1) і починає сходитися на O (n). Ви можете допомогти виконувати пошук, поділивши глобальні набори на більш дрібні розділи на основі деякого легко помітного атрибута (наприклад, значення першої цифри, потім другої, третьої тощо, протягом тих пір, поки вам потрібно отримати оптимальний заданий розмір) .
Nisan.H

3
@TorstenMarek Це мене бентежить. На цій сторінці пошук списку дорівнює O (1), а пошук диктату - O (n), що є протилежним тому, що ви сказали. Я нерозумію?
тимчасове_користувач

3
@Aerovistae Я думаю, що ви неправильно прочитали інформацію на цій сторінці. Під списком я бачу O (n) для "x in s" (пошук). Він також показує пошук заданих і диктованих як середній випадок O (1).
Денніс

45

Диктант - хеш-таблиця, тому дуже швидко знайти ключі. Тому між dict і list, dict буде швидше. Але якщо у вас немає значення для асоціації, ще краще використовувати набір. Це хеш-таблиця, без частини "таблиці".


EDIT: для вашого нового питання, ТАК, набір буде кращим. Просто створіть 2 набори, один для послідовностей закінчився в 1, а інший для послідовностей закінчився в 89. Я успішно вирішив цю проблему, використовуючи набори.



31

Я зробив деякий бенчмаркінг, і виявляється, що dict швидше, ніж обидва списки та встановлені для великих наборів даних, запускаючи python 2.7.3 на процесорі i7 в Linux:

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

    10 петель, найкраще 3: 64,2 мсек за петлю

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

    10000000 петель, найкраще 3: 0,0759 Usec на петлю

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

    1000000 петель, найкраще 3: 0,262 Usec на цикл

Як бачите, dict значно швидше, ніж список, і приблизно в 3 рази швидше встановленого. У деяких додатках ви все ще можете вибрати набір для краси його. І якщо набори даних дійсно невеликі (<1000 елементів), списки працюють досить добре.


Чи не повинно бути саме навпаки? Список: 10 * 64,2 * 1000 = 642000 usec, dict: 10000000 * 0,0759 = 759000 usec та встановити: 1000000 * 0,262 = 262000 usec ... тому набори найшвидші, за ними перелік та диктант як останній у вашому прикладі. Або я щось пропускаю?
andzep

1
... але питання для мене тут таке: що насправді вимірює цей час? Не час доступу до заданого списку, диктату чи набору, але набагато більше, час та петлі для створення списку, диктування, встановлення та нарешті для пошуку та доступу до одного значення. Отже, чи взагалі це стосується питання? ... Цікаво, хоча ...
andzep

8
@andzep, ви помиляєтеся, -sопція - це налаштування timeitсередовища, тобто воно не враховується за загальний час. -sОпція виконується тільки один раз. На Python 3.3 я отримую ці результати: gen (діапазон) -> 0,229 usec, список -> 157 мсек, dict -> 0,0806 usec, set -> 0,0807 usec. Виставлення та диктант однакові. Проте Dict займає трохи більше часу, ніж ініціалізація (загальний час 13.580s проти 11.803s)
sleblanc

1
чому б не використовувати вбудований набір? Насправді я отримую набагато гірші результати за допомогою set.Set (), ніж із вбудованим набором ()
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest Вбудований набір був введений у python 2.4, тому я не впевнений, чому я не використав його у запропонованому рішенні. Я отримую хороші показники роботи з python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"використанням Python 3.6.0 (10000000 циклів, найкраще 3: 0,0608 usec на цикл), приблизно такий же, як і орієнтир dict, тому дякую за ваш коментар.
EriF89

6

Ви хочете диктувати.

Для (несортованих) списків в Python, для операції "in" потрібен O ​​(n) час --- не добре, коли у вас є велика кількість даних. Диктант, з іншого боку, є хеш-таблицею, тому ви можете очікувати часу пошуку O (1).

Як зазначали інші, ви можете вибрати набір (спеціальний тип диктату), якщо у вас є лише ключі, а не пари ключів / значень.

Пов'язані:

  • Вікі Python : інформація про складність часу операцій з контейнерами Python.
  • ТАК : Час роботи контейнера Python та складність пам’яті

1
Навіть для відсортованих списків "в" є O (n).

2
Для пов'язаного списку так, але "списки" в Python - це те, що більшість людей називали б векторами, які забезпечують індексований доступ в O (1) та операцію пошуку в O (log n) при сортуванні.
zweiterlinde

Ви хочете сказати, що inоператор, застосований до відсортованого списку, працює краще, ніж коли застосовується до несортованого (для пошуку випадкового значення)? (Я не думаю, чи вони реалізовані внутрішньо як вектори чи як вузли у зв’язаному списку).
martineau

4

якщо дані унікальні, набір () буде найефективнішим, але з двох диктовок (що також вимагає унікальності, ой :)


Я зрозумів, коли побачив, що моя відповідь розміщено%)
SilentGhost

2
@SilentGhost, якщо відповідь неправильна, чому б не видалити її? занадто погано для оновлених, але це трапляється (ну, трапилось )
Жан-Франсуа Фабре

3

Як новий набір тестів, які показують @ EriF89, все ще вірно після всіх цих років:

$ 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

Тут ми також порівнюємо а tuple, які, як відомо, є швидшими, ніж lists(і використовують менше пам'яті) в деяких випадках використання. Що стосується таблиці пошуку, то tupleслабкіше не краще.

І те, dictі setвиконано дуже добре. Це викликає цікавий момент, пов’язаний з відповіддю @SilentGhost про унікальність: якщо ОП має 10M значень у наборі даних, і невідомо, чи є в них дублікати, то варто було б паралельно зберігати набір / диктування його елементів. з фактичним набором даних та тестуванням на наявність у цьому наборі / dict. Можливо, що в 10M точках даних є лише 10 унікальних значень, що є значно меншим простором для пошуку!

Помилка SilentGhost щодо диктів насправді висвітлює, тому що можна використовувати диктант для співвіднесення дублюваних даних (у значеннях) у недуплений набір (ключі) і, таким чином, зберігати один об’єкт даних, щоб зберігати всі дані, але все ще бути швидким як таблиця пошуку. Наприклад, ключем dict може бути значення, яке шукається, а значення може бути переліком індексів у уявному списку, де це значення відбулося.

Наприклад, якщо список вихідних даних, який слід шукати, був l=[1,2,3,1,2,1,4]оптимізований як для пошуку, так і для пам'яті, замінивши його цим диктом:

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

З цього диктату можна знати:

  1. Якщо значення було в початковому наборі даних (тобто 2 in dповернення True)
  2. Якщо значення було у вихідному наборі даних (тобто d[2]повертає список індексів , де дані були знайдені в первинному списку даних: [1, 4])

Для вашого останнього абзацу, хоча це має сенс його читати, було б непогано (і, мабуть, легше зрозуміти), побачити фактичний код, який ви намагаєтесь пояснити.
кайзер

0

Насправді вам не потрібно зберігати 10 мільйонів значень у таблиці, так що це не є великою справою.

Підказка: подумайте, наскільки великим може бути ваш результат після першої суми операцій з квадратами. Найбільший можливий результат буде набагато меншим, ніж 10 мільйонів ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.