Набір 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.