【问题标题】:Python: Fast extraction of intersections among all possible 2-combinations in a large number of listsPython:快速提取大量列表中所有可能的 2 组合之间的交集
【发布时间】:2010-12-17 23:31:08
【问题描述】:

我有一个 ca 的数据集。 9K 可变长度列表(1 到 100K 个元素)。我需要计算此数据集中所有可能的 2 列表组合 的交集的长度。请注意,每个列表中的元素都是唯一的,因此可以将它们作为集合存储在 python 中。

在 python 中执行此操作的最有效方法是什么?

编辑我忘了指定我需要能够将交集值与相应的列表对匹配。感谢大家的及时回复,并对造成的困惑表示歉意!

【问题讨论】:

  • 我尝试了 RedGlyph 的解决方案(#2),它的工作原理就像一个魅力(也相当快)。所以我会坚持这一点,感谢大家的及时回复。

标签: python list set intersection combinations


【解决方案1】:

如果你的集合存储在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] 是它的索引。

【讨论】:

  • > 取决于您生成这些元素的方式 这些集合是从纯文本文件导入的,很遗憾无法即时生成。
  • 它们可能,但这会很慢(在缓存的位置跳转,阅读它们......确实有点令人生畏)。在这种情况下,我会坚持第一种方法,它无论如何都不会增加内存消耗 - 只要您可以将原始集合列表保存在内存中就可以了。 Edit 部分的想法更多是为了展示灵活性。
  • @RedGlyph,使用组合和上述方法,我可以得到任意两个项目之间的交点和交点的长度。但是我知道我将无法访问被比较的两个项目在 s 中的位置,对吗?我的最终输出需要是一系列包含两组 id 的三元组(例如,如果它们是在字典中定义的,或者至少是它们在 s 中的位置)和它们的交集长度。您基于 itertools 的解决方案可以用于此目的吗?
  • @redrat:如果您还需要所有长度的索引,您可以使用索引组合而不是集合组合:for x,y in combinations(xrange(0, n), 2),然后可以通过它们访问集合:@ 987654340@。如果您只需要几个索引,例如最大长度,您可以通过以下方式找到它:j = s.index(imax)imax 是迭代循环中的一个集合。它不在您的描述中,因此如果您需要更多帮助,则必须准确说明您需要什么。
  • @redrat,这个 (paste.ubuntu.com/322146) 是 RedGlyph 想法的延续,除了使用 enumerate 来收集索引。由于enumerate 返回一个迭代器,它可以很好地以菊花链方式连接到 itertools.combinations。我还没有测试过,但我认为这比通过索引访问s 的元素要快:s[x]
【解决方案2】:

由于您需要生成 (N x N/2) 的结果矩阵,即 O(N 平方) 输出,因此没有任何方法可以小于 O(N 平方) - 当然,在任何语言中。 (在您的问题中,N 是“大约 9K”)。所以,我认为没有什么比(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 平方) 因素)。

如果您有许多可用的内核和/或节点并且正在寻找并行算法,那当然是另一种情况 - 如果是这种情况,您能否提及您拥有的集群类型、它的大小、节点和内核的方式能最好地沟通,等等?

编辑:正如 OP 在评论(!)中随便提到的那样,他们实际上需要相交的集合的数量(真的,为什么要省略规范的这些关键部分?!至少编辑问题以澄清它们...),这只需要将其更改为:

  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 开始计数”,只是为了多样化;-)

【讨论】:

  • @Alex Martelli:你能解释一下为什么这段代码比 itertools.combination 更快吗?我的 timeit 测试表明情况确实如此,但我发现这令人不安,因为在这种情况下,“一种明显的方法”(itertools.combination)似乎不是最快的方法。应该始终使用您的代码而不是组合?
  • 谢谢亚历克斯,我不打算在集群上运行它,所以它可能确实要求很高。我也有兴趣了解不同方法之间已知的性能差异。
  • @Alex:你不需要用if s is not z: yield s & z排除相同的集合吗?
  • @unutbu:无论列表和/或集合大小如何,我都进行了一些测量,并且都产生了非常接近的时间(在 0.1% 内进行一分钟的测试)。你有没有观察到不同的东西?
  • @RedGlyph:我必须撤回我之前的评论。即使使用if s is not z: yield s & z,我测试的两种算法也不会返回相同的结果。所以比较时间是没有意义的。这可能是我以某种方式引入的错误,但到目前为止我还没有找到它。你可以在这里查看我的代码:paste.ubuntu.com/321906
【解决方案3】:

试试这个:

_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:对不起,我误解了这个问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多