Найкращий спосіб знайти перетин декількох множин?


266

У мене є список наборів:

setlist = [s1,s2,s3...]

Я хочу s1 ∩ s2 ∩ s3 ...

Я можу написати функцію, щоб це виконувати, виконуючи серію попарно s1.intersection(s2)тощо.

Чи є рекомендований, кращий або вбудований спосіб?

Відповіді:


454

З Python версії 2.6 ви можете використовувати декілька аргументів на set.intersection(), наприклад

u = set.intersection(s1, s2, s3)

Якщо набори є у списку, це означає:

u = set.intersection(*setlist)

де *a_listце розширення списку

Зауважте, що set.intersectionце не статичний метод, але для використання перетину першого набору з рештою списку використовується функціональна позначення. Отже, якщо список аргументів порожній, це не вдасться.


65

Станом на 2.6, set.intersectionбере довільно багато ітерабелів.

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s3 = set([2, 4, 6])
>>> s1 & s2 & s3
set([2])
>>> s1.intersection(s2, s3)
set([2])
>>> sets = [s1, s2, s3]
>>> set.intersection(*sets)
set([2])

24

Зрозуміло set.intersection, що ви хочете тут, але у випадку, коли вам колись потрібне узагальнення "взяти суму всіх цих", "візьміть добуток усіх цих", "візьміть за собою все це", те, що ви шукаєте, це reduceфункція:

from operator import and_
from functools import reduce
print(reduce(and_, [{1,2,3},{2,3,4},{3,4,5}])) # = {3}

або

print(reduce((lambda x,y: x&y), [{1,2,3},{2,3,4},{3,4,5}])) # = {3}

12

Якщо у вас немає Python 2.6 або новішої версії, альтернативою є написання явного циклу:

def set_list_intersection(set_list):
  if not set_list:
    return set()
  result = set_list[0]
  for s in set_list[1:]:
    result &= s
  return result

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print set_list_intersection(set_list)
# Output: set([1])

Ви також можете використовувати reduce:

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print reduce(lambda s1, s2: s1 & s2, set_list)
# Output: set([1])

Однак багатьом програмістам Python це не подобається, в тому числі сам Гвідо :

Близько 12 років тому Python отримав лямбда, зменшити (), фільтрувати () та карту (), люб’язно (я вважаю) хакера Lisp, який пропустив їх та подав робочі патчі. Але, незважаючи на цінність PR, я думаю, що ці функції слід вирішити з Python 3000.

Тому тепер зменшіть (). Це насправді той, якого я завжди найбільше ненавиджу, тому що, окрім кількох прикладів, що включають + або *, майже кожного разу, коли я бачу виклик зменшення () з аргументом нетривіальної функції, мені потрібно захопити ручку та папір для Діаграма того, що насправді подається у цю функцію, перш ніж я зрозумію, що слід робити (). Отже, на мій погляд, застосуваність redu () значною мірою обмежена асоціативними операторами, і в усіх інших випадках краще виписати цикл накопичення чітко.


8
Зауважте, що Гвідо каже, що використання reduce"обмежується асоціативними операторами", що застосовується в цьому випадку. reduceЦе дуже часто важко зрозуміти, але &це не так вже й погано.
Майк Грехем


Перевірте python.org/doc/essays/list2str на корисні оптимізації, пов'язані зі зменшенням. Загалом, це може бути досить непогано використане для створення списків, наборів, рядків тощо. Варто також поглянути на github.com/EntilZha/PyFunctional
Андреас

Зауважте, що ви могли оптимізувати, відриваючи цикл, коли resultпорожній.
bfontaine

1

Тут я пропоную загальну функцію для множинного перетину, намагаючись скористатися найкращим доступним методом:

def multiple_set_intersection(*sets):
    """Return multiple set intersection."""
    try:
        return set.intersection(*sets)
    except TypeError: # this is Python < 2.6 or no arguments
        pass

    try: a_set= sets[0]
    except IndexError: # no arguments
        return set() # return empty set

    return reduce(a_set.intersection, sets[1:])

Гвідо може не подобатися reduce, але мені це подобається :)


Ви повинні перевірити довжину, setsзамість того, щоб намагатися отримати доступ sets[0]та ловити IndexError.
bfontaine

Це не звичайна перевірка; a_setвикористовується при остаточному поверненні.
tzot

Ви не можете return reduce(sets[0], sets[1:]) if sets else set()?
bfontaine

Ха так, дякую. Код повинен змінитися, оскільки слід уникати посилань на try/, exceptякщо можна. Це кодовий запах, неефективний і може приховати інші проблеми.
bfontaine

0

Жан-Франсуа Fabre set.intesection (* list_of_sets) відповідь, безумовно, є найбільш піхтонічним і справедливо прийнятою відповіддю.

Для тих, хто хоче скористатися функцією зменшення, також буде працювати наступне:

reduce(set.intersection, list_of_sets)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.