Попарное пересечение множеств в Python

если у меня есть переменное количество наборов (назовем число n), которые в большинстве m элементы каждый, каков наиболее эффективный способ вычисления попарных пересечений для всех пар множеств? Обратите внимание, что это отличается от пересечения всех n наборы.

например, если у меня есть следующие наборы:

A={"a","b","c"}
B={"c","d","e"}
C={"a","c","e"}

Я хочу иметь возможность найти:

intersect_AB={"c"}
intersect_BC={"c", "e"}
intersect_AC={"a", "c"}

другой приемлемый формат (если это делает вещи проще) будет карта элементов в данном наборе наборов, которые содержат тот же элемент. Например:

intersections_C={"a": {"A", "C"},
                 "c": {"A", "B", "C"}
                 "e": {"B", "C"}}

Я знаю, что один из способов сделать это - создать словарь, отображающий каждое значение в объединении всех n устанавливает список наборов, в которых он происходит, а затем перебирает все эти значения для создания списков, таких как intersections_C выше, но я не уверен, как это Весы как n увеличивает и размеры набора становятся слишком большой.

дополнительная справочная информация:

  1. каждый из наборов примерно одинаковой длины, но также очень большой (достаточно большой, чтобы хранить их все в памяти-реалистичная проблема, и алгоритм, который избегает этого, был бы предпочтительным, хотя и не нужен)
  2. размер пересечений между любыми двумя наборами очень мал по сравнению с размером самих наборов
  3. если это поможет, мы можем предположить что угодно нам нужно о порядке входных наборов.

3 ответов


это должно делать то, что вы хотите

import random as RND
import string
import itertools as IT

издеваться над некоторыми данными

fnx = lambda: set(RND.sample(string.ascii_uppercase, 7))
S = [fnx() for c in range(5)]

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

idx = range(len(S))

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

pairs = IT.combinations(idx, 2)

написать a функция выполняет заданное пересечение

nt = lambda a, b: S[a].intersection(S[b])

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

res = dict([ (t, nt(*t)) for t in pairs ])

результат ниже, отформатированный по первому варианту, прочитанному в OP, является словарем, в котором значения - это множество пересечений двух последовательностей; каждое значение keyed к кортежу, состоящему из двух индексов этих последовательностей

этот решение, действительно просто два строки кода: (i) вычислить перестановки; (ii) затем применить некоторую функцию над каждой перестановкой, сохраняя возвращаемое значение в структурированном контейнере (ключ-значение) контейнер

объем памяти этого решения минимален, но вы можете сделать еще лучше, вернув выражение генератора на последнем шаге, т. е.

res = ( (t, nt(*t)) for t in pairs )

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


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


Как насчет использования метода пересечения множества. См. ниже:

A={"a","b","c"}
B={"c","d","e"}
C={"a","c","e"}

intersect_AB = A.intersection(B)
intersect_BC = B.intersection(C)
intersect_AC = A.intersection(C)

print intersect_AB, intersect_BC, intersect_AC