Набір Python проти списків


187

У Python яка структура даних є більш ефективною / швидкою? Якщо припустити, що порядок не важливий для мене, і я все одно перевіряв би на наявність дублікатів, чи налаштований Python повільніше, ніж список Python?

Відповіді:


231

Це залежить від того, що ви маєте намір зробити з цим.

Набори знаходяться значно швидше, коли йдеться про визначення того, чи існує об'єкт у наборі (як у x in s), але вони повільніші, ніж списки, якщо мова йде про ітерацію над їх вмістом.

Ви можете використовувати модуль timeit, щоб побачити, що швидше для вашої ситуації.


4
На вашу думку: "Набори значно швидші", яка основна реалізація робить її швидшою?
переобмін

Мови скриптів люблять приховувати основні реалізації, але ця очевидна простота не завжди є хорошою справою, вам потрібна певна обізнаність про «структуру даних», коли ви розробляєте програмне забезпечення.
Крістоф Руссі

4
Набір не є значно повільнішим за список під час ітерації.
омерфарукдоган

39
Набори і списки мають лінійну ітерацію часу. Сказати, що один "повільніше", ніж інший, помилково керується і заплутав нових програмістів, які читали цю відповідь.
ханабіт

@habnabit, якщо ви говорите, що вони мають лінійну ітерацію часу. Чи означає це, що вони мають однаковий час ітерації? Яка тоді різниця?
Мухаммед Нурелдін

153

Списки трохи швидші, ніж набори, коли ви просто хочете перебрати значення.

Однак набори значно швидші, ніж списки, якщо ви хочете перевірити, чи міститься елемент у ньому. Однак вони можуть містити лише унікальні предмети.

Виявляється, кортежі виконують майже так само, як і списки, за винятком їх непорушності.

Ітераційний

>>> def iter_test(iterable):
...     for i in iterable:
...         pass
...
>>> from timeit import timeit
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = set(range(10000))",
...     number=100000)
12.666952133178711
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = list(range(10000))",
...     number=100000)
9.917098999023438
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = tuple(range(10000))",
...     number=100000)
9.865639209747314

Визначте, чи є об’єкт

>>> def in_test(iterable):
...     for i in range(1000):
...         if i in iterable:
...             pass
...
>>> from timeit import timeit
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = set(range(1000))",
...     number=10000)
0.5591847896575928
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = list(range(1000))",
...     number=10000)
50.18339991569519
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = tuple(range(1000))",
...     number=10000)
51.597304821014404

6
Я виявив, що (ініціалізаційний набір -> 5.5300979614257812) (список ініціалізації -> 1.8846848011016846) (ініціалізаційний кортеж -> 1.8730108737945557) Елементи розміру 10 000 на моєму ядрі Intel i5 quad core з 12 Гб оперативної пам’яті. Це також слід враховувати.
ThePracticalOne

4
Я оновив код, щоб зараз видалити створення об'єкта. Фаза налаштування циклів timeit викликається лише один раз ( docs.python.org/2/library/timeit.html#timeit.Timer.timeit ).
Елліс Персіваль

7

Виконання списку:

>>> import timeit
>>> timeit.timeit(stmt='10**6 in a', setup='a = range(10**6)', number=100000)
0.008128150348026608

Встановити продуктивність:

>>> timeit.timeit(stmt='10**6 in a', setup='a = set(range(10**6))', number=100000)
0.005674857488571661

Ви можете розглянути Tuples подібні до списків, але їх неможливо змінити. Вони займають трохи менше пам’яті і швидше отримують доступ. Вони не такі гнучкі, але ефективніші, ніж списки. Їх звичайне використання - це функція клавіш словника.

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

setза визначенням. [ пітон | wiki ].

>>> x = set([1, 1, 2, 2, 3, 3])
>>> x
{1, 2, 3}

4
Спочатку слід оновити посилання setвбудованого типу ( docs.python.org/2/library/stdtypes.html#set ), а не застарілу setsбібліотеку. По-друге, "Набори - це також структури послідовностей", читайте наступне з посилання вбудованого типу: "Будучи невпорядкованою колекцією, набори не записують положення елементів або порядок вставки. Відповідно, набори не підтримують індексацію, нарізання тощо поведінка як послідовність ".
Seaux

7
rangeне є list. rangeце спеціальний клас із користувацьким __contains__магічним методом.
Райне Ван

@RyneWang це правда, але лише для Python3. У діапазоні Python2 повертається звичайний список (саме тому існують жахливі речі на кшталт xrange)
Manoel Vilela

7

Setвиграш через майже миттєвий чек "містить": https://en.wikipedia.org/wiki/Hash_table

Реалізація списку : зазвичай масив, низький рівень, близький до металу, добре для ітерації та випадкового доступу за індексом елементів.

Встановити реалізацію: https://en.wikipedia.org/wiki/Hash_table , вона не повторює список, але знаходить елемент, обчислюючи хеш з ключа, тому це залежить від характеру ключових елементів та хеша функція. Аналогічно тому, що використовується для диктату. Я підозрюю, що listможе бути швидше, якщо у вас буде дуже мало елементів (<5), чим більший елемент рахується, тим краще setбуде виконати перевірку на вміст. Це також швидко для додавання та видалення елементів. Також завжди пам’ятайте, що побудова комплекту має вартість!

ПРИМІТКА : Якщо listвже відсортовано, пошук listможе бути досить швидким, але для звичайних випадків a setє швидшим і простішим, що містить перевірки.


8
Близький до металу? Що це означає навіть у контексті Python? Наскільки список ближче до металу, ніж набір?
roganjosh

@roganjosh, python все ще працює на машині, і деякі реалізації, такі як список як "масив", ближчі до того, що апаратне забезпечення добре: stackoverflow.com/questions/176011/… , але це завжди залежить від того, що ви хочете досягти, це добре знати трохи про реалізації, а не лише абстракції.
Крістоф Руссі

2

тл; д-р

Структури даних (DS) важливі, оскільки вони використовуються для виконання операцій над даними, що в основному передбачає: взяти деякий вхід , обробити його і повернути результат .

Деякі структури даних є кориснішими, ніж інші, в деяких конкретних випадках. Тому цілком несправедливо запитувати, який (DS) є більш ефективним / швидким. Це як запитати, який інструмент є більш ефективним між ножем та виделкою. Я маю на увазі, все залежить від ситуації.

Списки

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

Набори

Набір об'єктів - це не упорядкована колекція різних об'єктів, що змішуються . Він зазвичай використовується для тестування членства, видалення дублікатів із послідовності та обчислення математичних операцій, таких як перетину, об'єднання, різниці та симетричної різниці.

Використання

З деяких відповідей видно, що список є досить швидшим, ніж набір при повторенні значень. З іншого боку, набір швидше, ніж список, коли перевіряється, чи міститься в ньому елемент. Тому єдине, що ви можете сказати, - це те, що список кращий, ніж набір для деяких конкретних операцій, і навпаки.


2

Мене зацікавили результати при перевірці за допомогою CPython, якщо значення є однією з невеликої кількості літералів. setвиграє в Python 3 проти tuple, listі or:

from timeit import timeit

def in_test1():
  for i in range(1000):
    if i in (314, 628):
      pass

def in_test2():
  for i in range(1000):
    if i in [314, 628]:
      pass

def in_test3():
  for i in range(1000):
    if i in {314, 628}:
      pass

def in_test4():
  for i in range(1000):
    if i == 314 or i == 628:
      pass

print("tuple")
print(timeit("in_test1()", setup="from __main__ import in_test1", number=100000))
print("list")
print(timeit("in_test2()", setup="from __main__ import in_test2", number=100000))
print("set")
print(timeit("in_test3()", setup="from __main__ import in_test3", number=100000))
print("or")
print(timeit("in_test4()", setup="from __main__ import in_test4", number=100000))

Вихід:

tuple
4.735646052286029
list
4.7308746771886945
set
3.5755991376936436
or
4.687681658193469

Для 3 - 5 літералів setвсе-таки виграє з великим відривом і orстає найповільнішим.

У Python 2 setзавжди найповільніший. orє найшвидшим для 2 до 3 літералів tupleі listшвидше з 4 або більше літералами. Я не міг розрізнити швидкість tupleпроти list.

Коли значення для тестування кешувались у глобальній змінній поза функцією, а не створюючи літерал в циклі, setвигравали щоразу навіть у Python 2.

Ці результати стосуються 64-бітного CPython на Core i7.


0

Я рекомендую реалізацію Set, коли випадок використання обмежується посиланням або пошуком існування та реалізацією Tuple, коли випадок використання вимагає від вас ітерації. Список є низьким рівнем реалізації та потребує значних витрат на пам'ять.


1
Дійсно, правильне розмежування між тим, коли використовувати набори та коли використовувати Tuple, дійсно має надзвичайно важливе значення. Я б не хвилювався за пов'язані накладні витрати на пам'ять, сліди, якщо я не будувати сценарії API нижчого рівня.

0
from datetime import datetime
listA = range(10000000)
setA = set(listA)
tupA = tuple(listA)
#Source Code

def calc(data, type):
start = datetime.now()
if data in type:
print ""
end = datetime.now()
print end-start

calc(9999, listA)
calc(9999, tupA)
calc(9999, setA)

Результат після порівняння 10 ітерацій для всіх 3: Порівняння


0

Набори швидше, тим більше, ви отримуєте більше функцій із наборами, наприклад, скажімо, що у вас є два набори:

set1 = {"Harry Potter", "James Bond", "Iron Man"}
set2 = {"Captain America", "Black Widow", "Hulk", "Harry Potter", "James Bond"}

Ми можемо легко приєднати два набори:

set3 = set1.union(set2)

Дізнайтеся, що спільного в обох:

set3 = set1.intersection(set2)

Дізнайтеся, що відрізняється в обох:

set3 = set1.difference(set2)

І набагато більше! Просто спробуйте їх, вони веселі! Крім того, якщо вам доведеться працювати над різними значеннями в 2 списку або загальними значеннями в межах 2 списків, я вважаю за краще перетворити ваші списки в набори, і багато програмістів роблять це таким чином. Сподіваюся, це допоможе вам :-)

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