Коротка відповідь : використовуйте not set(a).isdisjoint(b)
, як правило, це найшвидше.
Існує чотири загальні способи перевірити, чи є два списки a
та b
ділитися якими-небудь предметами. Перший варіант - перетворити обидва набори і перевірити їх перетин, як такий:
bool(set(a) & set(b))
Оскільки набори зберігаються за допомогою хеш-таблиці в Python, їх пошукO(1)
(див. Тут для отримання додаткової інформації про складність операторів в Python). Теоретично це O(n+m)
в середньому для n
та m
об'єктів у списках a
та b
. Але 1) спочатку він повинен створити набори зі списків, які можуть зайняти незначну кількість часу, і 2) він припускає, що хеш-колізії є рідкими серед ваших даних.
Другий спосіб зробити це - використання вираження генератора, що виконує ітерацію у списках, таких як:
any(i in a for i in b)
Це дозволяє здійснювати пошук на місці, тому для посередницьких змінних не виділяється нова пам'ять. Це також підпадає під першу знахідку. Але in
оператор завжди O(n)
в списках (див. Тут ).
Іншим запропонованим варіантом є гібридто ітерація через один зі списку, перетворення іншого в набір і тест на приналежність до цього набору, наприклад:
a = set(a); any(i in a for i in b)
Четвертий підхід полягає у використанні isdisjoint()
методу (заморожених) наборів (див. Тут ), наприклад:
not set(a).isdisjoint(b)
Якщо елементи, які ви шукаєте, знаходяться біля початку масиву (наприклад, він відсортований), вираз генератора надається перевагу, оскільки метод перетину наборів повинен виділяти нову пам'ять для посередницьких змінних:
from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974
Ось графік часу виконання цього прикладу у функції розміру списку:
Зауважте, що обидві осі є логарифмічними. Це є найкращим випадком для вираження генератора. Як видно, isdisjoint()
метод кращий для дуже малих розмірів списку, тоді як вираз генератора краще для більших розмірів списку.
З іншого боку, оскільки пошук починається з початку для гібридного і генераторного вираження, якщо спільний елемент систематично знаходиться в кінці масиву (або обидва списки не поділяють жодних значень), тоді роз'єднані та встановлені переходи перетину тоді Швидше, ніж генераторне вираження та гібридний підхід.
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668
Цікаво зазначити, що експресія генератора набагато повільніше для більших розмірів списку. Це лише для 1000 повторень, а не 100000 для попередньої цифри. Ця настройка також добре наближається, коли жодних елементів не поділяється, і є найкращим випадком для роз'єднаних та встановлених підходів до перетину.
Ось два аналізи з використанням випадкових чисел (замість того, щоб сфальсифікувати налаштування на користь тієї чи іншої техніки):
Висока ймовірність спільного використання: елементи беруться випадковим чином [1, 2*len(a)]
. Низький шанс поділитися: елементи беруться випадковим чином [1, 1000*len(a)]
.
До цього часу цей аналіз передбачає, що обидва списки однакового розміру. Якщо два списки різного розміру, наприклад a
, значно менші, isdisjoint()
завжди швидше:
Переконайтеся, що a
список менший, інакше продуктивність знижується. У цьому експерименті a
розмір списку було встановлено постійним 5
.
Підсумовуючи:
- Якщо списки дуже малі (<10 елементів),
not set(a).isdisjoint(b)
це завжди найшвидше.
- Якщо елементи в списках відсортовані або мають регулярну структуру, якою ви можете скористатися, вираз генератора
any(i in a for i in b)
є найшвидшим на великих розмірах списку;
- Перевірте встановлене перехрестя
not set(a).isdisjoint(b)
, яке завжди швидше, ніж bool(set(a) & set(b))
.
- Гібрид "повторення через список, тест на набір",
a = set(a); any(i in a for i in b)
як правило, повільніше, ніж інші методи.
- Експресія генератора та гібрид набагато повільніше, ніж два інші підходи, коли мова йде про списки без спільного використання елементів.
У більшості випадків використання isdisjoint()
методу є найкращим підходом, оскільки експресія генератора займе набагато більше часу, оскільки це дуже неефективно, коли жодні елементи не поділяються.
len(...) > 0
оскільки цеbool(set([]))
дає помилковий результат. І звичайно, якщо ви зберегли свої списки як набори для початку, ви зберегли б створення набору накладні.