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