Короткий ответ : пользуйтесь 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([]))
дает False. И, конечно, если вы сохраните свои списки в виде наборов, вы сэкономите накладные расходы на создание наборов.