Проверьте, имеют ли списки общие элементы в python



Я хочу проверить, если любой предметы в одном списке присутствуют в другом списке. Я могу сделать это просто с помощью кода ниже, но я подозреваю, что для этого может быть функция библиотеки. Если нет, есть ли более подходящие для Python способ достижения того же результата.



In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
....: for i in a:
....: if i in b:
....: return True
....: return False
....:

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
....: return len(set(a).intersection(set(b))) > 0
....:
664   9  

9 ответов:

короткий ответ: используйте 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

вот график времени выполнения для этого примера в функции размера списка:

Element sharing test execution time when shared at the beginning

обратите внимание, что обе оси являются логарифмическими. Это представляет собой лучший случай для выражение генератора. Как видно, то 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

Element sharing test execution time when shared at the end

интересно отметить, что выражение генератора намного медленнее для больших размеров списка. Это только для 1000 повторений, вместо 100000 для предыдущего рисунка. Эта настройка также хорошо аппроксимируется, когда нет общих элементов, и является лучшим случаем для непересекающихся и заданных подходов к пересечению.

вот два анализа с использованием случайных чисел (вместо фальсификации установки в пользу той или иной техники):

Element sharing test execution time for randomly generated data with high chance of sharing Element sharing test execution time for randomly generated data with high chance of sharing

высокая вероятность обмена: элементы случайным образом берутся из [1, 2*len(a)]. Низкий шанс обмена: элементы случайным образом берутся из [1, 1000*len(a)].

до сих пор этот анализ предполагал, что оба списка имеют одинаковый размер. В случае двух списков разных размеров, например a гораздо меньше, isdisjoint() всегда быстрее:

Element sharing test execution time on two differently sized lists when shared at the beginning Element sharing test execution time on two differently sized lists when shared at the end

убедитесь, что a список меньше, в противном случае производительность снижается. В этом эксперименте a размер списка был установлен постоянный к 5.

в итоге:

  • если списки очень малы (not set(a).isdisjoint(b) всегда самый быстрый.
  • если элементы в списках отсортированы или имеют регулярную структуру, которую вы можно воспользоваться, генератором выражения any(i in a for i in b) является самым быстрым на больших размерах списка;
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

Примечание: предполагается, что логический ответ. Если все, что вам нужно, это выражение для использования в if заявление, просто использовать if set(a) & set(b):

def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

это асимптотически оптимально (худший случай O (n + m)), и может быть лучше, чем подход пересечения из-за any's короткое замыкание.

например:

lists_overlap([3,4,5], [1,2,3])

вернет True, как только он доберется до 3 in sb

EDIT: еще одна вариация (благодаря Дэйву Кирби):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

это зависит от imapитератор, который реализован в C, а не понимание генератора. Он также использует sb.__contains__ как сопоставление функция. Я не знаю, насколько это влияет на производительность. Это все равно будет короткое замыкание.

вы также можете использовать any с пониманием списка:

any([item in a for item in b])

в Python 2.6 или более поздней версии вы можете сделать:

return not frozenset(a).isdisjoint(frozenset(b))

вы можете использовать любую встроенную функцию / w выражение генератора:

def list_overlap(a,b): 
     return any(i for i in a if i in b)

как указывали Джон И ли, это дает неправильные результаты, когда для каждого я разделял два списка bool(i) == False. Это должно быть:

return any(i in b for i in a)

этот вопрос довольно старый, но я заметил, что в то время как люди спорили наборы против списков, никто не думал использовать их вместе. Следуя примеру Соравукса,

худший случай для списков:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

и лучший случай для списков:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

таким образом, даже быстрее, чем итерация по двум спискам, повторяется, хотя список, чтобы увидеть, находится ли он в наборе, что имеет смысл, поскольку проверка того, находится ли число в наборе, занимает постоянное время при проверке повторение списка занимает время, пропорциональное длине списка.

таким образом, мой вывод заключается в том, что повторите список и проверьте, находится ли он в наборе.

Если вам все равно, что перекрывающийся элемент может быть, вы можете просто проверить len Объединенного списка против списков, объединенных в набор. Если есть перекрывающиеся элементы, то набор будет короче:

len(set(a+b+c))==len(a+b+c) возвращает True, если нет перекрытия.

Я брошу еще один с функциональным стилем программирования:

any(map(lambda x: x in a, b))

объяснение:

map(lambda x: x in a, b)

возвращает список булевых значений, где элементы b находятся в a. Затем этот список передается в any, который просто возвращает True если какие-либо элементы True.

Comments

    Ничего не найдено.