Краткий ответ : используйте 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 элементов), <code>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()
является лучшим подходом, так какВыражение генератора будет выполняться намного дольше, так как оно очень неэффективно, когда нет общих элементов.