Python: 在大量列表中快速提取所有可能2组合的交集

3 投票
3 回答
3084 浏览
提问于 2025-04-15 16:08

我有一个数据集,大约有9000个长度不一的列表(每个列表的元素数量从1到10万不等)。我需要计算这个数据集中所有可能的两个列表组合之间的交集长度。需要注意的是,每个列表中的元素都是独一无二的,所以可以把它们存储为Python中的集合。

在Python中,有什么高效的方法可以做到这一点呢?

编辑 我忘了说明,我需要能够将交集的值与对应的两个列表匹配起来。感谢大家的快速回复,抱歉造成了困惑!

3 个回答

0

试试这个:

_lists = [[1, 2, 3, 7], [1, 3], [1, 2, 3], [1, 3, 4, 7]]
_sets = map( set, _lists )
_intersection = reduce( set.intersection, _sets )

然后获取索引:

_idxs = [ map(_i.index, _intersection ) for _i in _lists ]

谢谢,

何塞·玛丽亚·加西亚

PS:抱歉我误解了问题

2

因为你需要生成一个(N乘N/2)的结果矩阵,也就是O(N平方)的输出,所以无论用什么语言,任何方法都不可能比O(N平方)更快。(在你的问题中,N大约是9000)。所以,我认为没有比(a)创建你需要的N组数据和(b)遍历这些数据来生成输出更简单的方法。换句话说:

def lotsofintersections(manylists):
  manysets = [set(x) for x in manylists]
  moresets = list(manysets)
  for  s in reversed(manysets):
    moresets.pop()
    for z in moresets:
      yield s & z

这段代码已经在尝试进行一些小的优化(例如,避免对列表进行切片或从前面删除元素,这可能会增加其他O(N平方)的复杂度)。

如果你有很多核心和/或节点可用,并且在寻找并行算法,那情况就不同了——如果是这样的话,你能说一下你拥有的集群类型、规模,以及节点和核心之间最佳的通信方式等等吗?

编辑:因为提问者在评论中随意提到他们实际上需要被交集的集合的数字(真的,为什么要省略这么重要的部分呢?!至少编辑一下问题来澄清这些内容……),这只需要将其更改为:

  L = len(manysets)
  for i, s in enumerate(reversed(manysets)):
    moresets.pop()
    for j, z in enumerate(moresets):
      yield L - i, j + 1, s & z

(如果你需要“从1开始计数”以获得逐步标识符——否则就是显而易见的更改)。

但如果这是规格的一部分,你不妨使用更简单的代码——忘掉更多的集合,然后:

  L = len(manysets)
  for i xrange(L):
    s = manysets[i]
    for j in range(i+1, L):
      yield i, j, s & manysets[z]

这次假设你想“从0开始计数”,只是为了变化;-)

3

如果你的集合存储在变量 s 中,比如:

s = [set([1, 2]), set([1, 3]), set([1, 2, 3]), set([2, 4])]

那么你可以使用 itertools.combinations 来两两组合这些集合,并计算它们的交集(注意,正如 Alex 提到的,combinations 从 2.6 版本开始才有)。这里用列表推导式(只是为了举例):

from itertools import combinations
[ i[0] & i[1] for i in combinations(s,2) ]

或者,你也可以用循环,这可能更符合你的需求:

for i in combinations(s, 2):
    inter = i[0] & i[1]
    # processes the intersection set result "inter"

所以,要获取每个集合的长度,这个“处理”过程可以是:

    l = len(inter)

这样做会非常高效,因为它使用迭代器来计算每一种组合,而不是提前准备好所有组合。


补充说明:请注意,使用这种方法,列表 "s" 中的每个集合实际上可以是其他返回集合的东西,比如生成器。如果你内存不够,列表本身也可以是一个生成器。不过,这样可能会慢一些,具体取决于你是如何生成这些元素的,但你不需要同时在内存中存储整个集合列表(在你的情况下,这应该不是问题)。

例如,如果每个集合是由一个函数 gen 生成的:

def gen(parameter):
    while more_sets():
        # ... some code to generate the next set 'x'
        yield x

with open("results", "wt") as f_results:
    for i in combinations(gen("data"), 2):
        inter = i[0] & i[1]
        f_results.write("%d\n" % len(inter))

补充说明 2:如何收集索引(根据 redrat 的评论)。

除了我在评论中提供的快速解决方案,收集集合索引的更有效方法是使用一个 (index, set) 的列表,而不是单纯的 set 列表。

新格式的示例:

s = [(0, set([1, 2])), (1, set([1, 3])), (2, set([1, 2, 3]))]

如果你反正是要构建这个列表来计算组合,那么适应你的新需求应该很简单。主要的循环变成:

with open("results", "wt") as f_results:
    for i in combinations(s, 2):
        inter = i[0][1] & i[1][1]
        f_results.write("length of %d & %d: %d\n" % (i[0][0],i[1][0],len(inter))

在这个循环中,i[0]i[1] 是一个元组 (index, set),所以 i[0][1] 是第一个集合,i[0][0] 是它的索引。

撰写回答