【问题标题】:Pairwise Set Intersection in PythonPython中的成对集合交集
【发布时间】:2015-02-06 18:43:30
【问题描述】:

如果我有可变数量的集合(我们称之为数字 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. 如果有帮助,我们可以假设我们需要对输入集的排序进行任何处理。

【问题讨论】:

  • 你知道行之有效的方法你试过了吗?
  • 我建议如下:遍历所有集合并通过跟踪找到每个元素的位置来构建地图。这是 O(NlogN)(假设字典增加了对数开销),其中 N 是元素的总数。
  • 我已经尝试过我在小样本上描述的方法,但问题是我将使用的很多数据都是用户提供的。理想情况下,我希望能够支持更大的用例,所以我想知道是否有比我描述的幼稚方法更常见/更有效的方法来做到这一点。
  • @nickie 你的想法是遍历集合并为所有 n 集合独立制作字典,每次迭代只制作大小为 m 的字典而不是 nm* 来存储所有可能的元素?
  • 我认为这可以使用哈希表在线性时间内完成,与集合的大小成线性关系:O(N + M + N * c),其中 c 是一个常数,表示访问哈希表中的条目的成本,此常量将与您设置的字符串的长度成正比。

标签: python set set-intersection


【解决方案1】:

这应该做你想做的事

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)

写一个函数来执行集合交集

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

将此函数折叠在对上并将每个函数调用的结果键入其参数

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

下面的结果,根据 OP 中引用的第一个选项格式化,是一个字典,其中 values 是两个序列的集合交集;每个值键入到一个由这些序列的两个索引组成的元组

这个解决方案实际上只是 行代码: (i) 计算排列; (ii) 然后对每个排列应用一些函数,将返回的值存储在结构化容器(键值)容器中

此解决方案的内存占用很小,但您可以通过在最后一步返回生成器表达式来做得更好,即

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

请注意,使用这种方法,对序列和相应的交集都没有写入内存——即,pairsres 都是迭代器。

【讨论】:

  • 这需要 O(n*n*m) 时间,如果你有 n 组大小为 m.
  • 两个集合x和y相交的时间复杂度是O(len(x) * len(y));不利的时间复杂度是这个问题所固有的,所以你能做的最好的就是不要让它变得更糟,而只是解决恒定的时间因素(例如,不要重新实现底层的 C 外观,而是使用优化的 python 函数,如列表推导
  • 这绝对可以完成工作,但是如果有一种方法可以使用这种简单的语法以及像tzaman described 这样的内存效率更高的方法,那就太棒了。谢谢!
  • 您是否查看过此解决方案的内存配置文件?这里没有写在内存中。对对象是一个迭代器(python3),只有集合索引而不是集合本身;同样,请参阅我修改后的答案,最后一行可以稍微更改以返回生成器表达式,包含元组而不是 k:v, expression (vs. dict)
  • 啊,太好了!我注意到的另一件事是,我们可以通过使用组合而不是集合的排列将交集计算的数量减少一半
【解决方案2】:

如果我们可以假设输入集是有序的,那么伪合并排序方法似乎很有希望。将每个集合视为已排序的流,并行推进流,始终只推进所有当前迭代器中值最低的流。每次推进迭代器时,将每个当前值与新的最小值进行比较,并将匹配项转储到相同项目的集合中。

【讨论】:

  • 这是我正在考虑的下一件事——字典的想法(字典以所有 n 集的联合为关键字)似乎更容易概念化和实现,但是我觉得直觉上这会节省一些时间和内存消耗。知道如何量化这种方法相对于字典方法的节省吗?
  • 流式方法的主要优点是您一次只需要在内存中保存每个集合中的一个项目。字典方法使用的远不止这些:~O(唯一元素的数量 * 平均成员资格)。如有必要,您甚至可以将交集本身写到一组文件中。
【解决方案3】:

如何使用集合的交集方法。见下文:

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

【讨论】:

  • 我给出的示例旨在成为一个通用示例(我将有很多不仅仅是集合 A、B 和 C),并且我想尽可能避免重做工作,因为大小我的套装可能是巨大的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-12
  • 1970-01-01
相关资源
最近更新 更多