【问题标题】:Find in python combinations of mutually exclusive sets from a list's elements从列表的元素中查找互斥集的python组合
【发布时间】:2017-01-20 19:53:09
【问题描述】:

在我目前正在进行的一个项目中,我已经实现了大约 80% 的我希望我的程序执行的操作,我对结果非常满意。

在剩下的 20% 中,我遇到了一个让我有点不知道如何解决的问题。 这里是:

我提出了一个列表列表,其中包含多个数字(任意长度) 例如:

listElement[0] = [1, 2, 3]
listElement[1] = [3, 6, 8]
listElement[2] = [4, 9]
listElement[4] = [6, 11]
listElement[n] = [x, y, z...]

其中 n 可以达到 40,000 左右。

假设每个列表元素是一组数字(在数学意义上),我想做的是推导出互斥集合的所有组合;也就是说,类似于上述列表元素的幂集,但排除了所有非不相交集元素。

因此,为了继续 n=4 的示例,我想提出一个包含以下组合的列表:

newlistElement[0] = [1, 2, 3]
newlistElement[1] = [3, 6, 8]
newlistElement[2] = [4, 9]
newlistElement[4] = [6, 11] 
newlistElement[5] = [[1, 2, 3], [4, 9]]
newlistElement[6] = [[1, 2, 3], [6, 11]]
newlistElement[7] = [[1, 2, 3], [4, 9], [6, 11]]
newlistElement[8] = [[3, 6, 8], [4, 9]]
newlistElement[9] = [[4, 9], [6, 11]

一个无效的情况,例如组合 [[1, 2, 3], [3, 6, 8]] 因为 3 在两个元素中是常见的。 有什么优雅的方法可以做到这一点吗?如有任何反馈,我将不胜感激。

我还必须说明我不想执行 powerset 函数,因为初始列表可能有相当多的元素(正如我所说的 n 可以达到 40000),并且使用这么多元素的 powerset永远不会完成。

【问题讨论】:

  • 你试过什么?另外,请注意,这种组合问题经常出现。
  • docs.python.org/2/library/stdtypes.html#frozenset - 将是我挖掘文档的第一站
  • 各个元素中可能的数字范围是多少?
  • Marcin:我还没有尝试过具体的东西,但我一直在尝试在脑海中对问题进行理论化。一直在寻求递归解决方案,但我不确定。
  • kreativitea:典型情况下的数字范围是 1 到 10 个数字,但在某些(稀有)项目中,它可以达到 30 个左右……如果我没记错的话跨度>

标签: python algorithm


【解决方案1】:

我会使用生成器:

import itertools

def comb(seq):
   for n in range(1, len(seq)):
      for c in itertools.combinations(seq, n): # all combinations of length n
         if len(set.union(*map(set, c))) == sum(len(s) for s in c): # pairwise disjoint?
            yield list(c)

for c in comb([[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]):
   print c

这会产生:

[[1, 2, 3]]
[[3, 6, 8]]
[[4, 9]]
[[6, 11]]
[[1, 2, 3], [4, 9]]
[[1, 2, 3], [6, 11]]
[[3, 6, 8], [4, 9]]
[[4, 9], [6, 11]]
[[1, 2, 3], [4, 9], [6, 11]]

如果您需要将结果存储在单个列表中:

print list(comb([[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]))

【讨论】:

  • python中的集合有内置函数isdisjoint:set(A).isdisjoint(set(B))
  • @MaxLi:对。但是有没有一种内置的方法可以将它应用于一系列集合(如果集合是成对不相交的,它应该返回True)?
  • 谢谢。这在快速测试中似乎工作正常。我现在将尝试使用我拥有的实际数据值。如果必须对 40,000 个初始列表元素执行此操作,它会起作用吗?我不确定我问的是否正确,但它的复杂性是什么?
  • @NPE: 类似于 reduce(lambda(x,y): set(x).isdisjoint(set(y)),seq) ?
  • @concept303:它查看所有子集,其中有2**n
【解决方案2】:

以下是递归生成器:

def comb(input, lst = [], lset = set()):
   if lst:
      yield lst
   for i, el in enumerate(input):
      if lset.isdisjoint(el):
         for out in comb(input[i+1:], lst + [el], lset | set(el)):
            yield out

for c in comb([[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]):
   print c

在许多集合具有共同元素的情况下,这可能比其他解决方案更有效(当然在最坏的情况下,它仍然必须遍历 powerset 的 2**n 元素)。

【讨论】:

  • 这与之前的输入相同,从 63 秒变为 0.0009 秒。绝对应该是递归!太感谢了。所有成对不相交集的最坏情况几乎从未在我的数据中发生过,如果发生了,那是针对初始元素非常少(1 到 3)的情况。
  • @concept303:是的,这应该更有效,因为它避免了查看很多死胡同。不过,40K 列表可能仍然有点太多了。
  • 40k 是我必须处理的最极端情况。典型情况是 1000 左右。我希望它能处理。我需要加载列表并尝试。会报告的!
  • 在 125 秒内处理了 13095 个包裹。将放一个更大的列表,让它运行一晚,看看明天早上是否有结果。再次非常感谢你。很棒的代码!如果可能的话,我认为这个答案应该以某种方式移到顶部。
  • @concept303:没错。可以想出一个非常短得多的示例,该代码将花费很长时间(由于其指数级的最坏情况复杂性)。
【解决方案3】:

下面程序中使用的方法类似于之前的几个答案,排除了不相交的集合,因此通常不会测试所有组合。它与以前的答案不同,它尽可能早地贪婪地排除所有集合。这使其运行速度比 NPE 的解决方案快几倍。这是两种方法的时间比较,使用的输入数据有 200、400、... 1000 个大小为 6 的集合,其元素范围为 0 到 20:

Set size =   6,  Number max =  20   NPE method
  0.042s  Sizes: [200, 1534, 67]
  0.281s  Sizes: [400, 6257, 618]
  0.890s  Sizes: [600, 13908, 2043]
  2.097s  Sizes: [800, 24589, 4620]
  4.387s  Sizes: [1000, 39035, 9689]

Set size =   6,  Number max =  20   jwpat7 method
  0.041s  Sizes: [200, 1534, 67]
  0.077s  Sizes: [400, 6257, 618]
  0.167s  Sizes: [600, 13908, 2043]
  0.330s  Sizes: [800, 24589, 4620]
  0.590s  Sizes: [1000, 39035, 9689]

在上面的数据中,左列显示了以秒为单位的执行时间。数字列表显示发生了多少个单联、双联或三联。程序中的常量指定数据集的大小和特征。

#!/usr/bin/python
from random import sample, seed
import time
nsets,   ndelta,  ncount, setsize  = 200, 200, 5, 6
topnum, ranSeed, shoSets, shoUnion = 20, 1234, 0, 0
seed(ranSeed)
print 'Set size = {:3d},  Number max = {:3d}'.format(setsize, topnum)

for casenumber in range(ncount):
    t0 = time.time()
    sets, sizes, ssum = [], [0]*nsets, [0]*(nsets+1);
    for i in range(nsets):
        sets.append(set(sample(xrange(topnum), setsize)))

    if shoSets:
        print 'sets = {},  setSize = {},  top# = {},  seed = {}'.format(
            nsets, setsize, topnum, ranSeed)
        print 'Sets:'
        for s in sets: print s

    # Method by jwpat7
    def accrue(u, bset, csets):
        for i, c in enumerate(csets):
            y = u + [c]
            yield y
            boc = bset|c
            ts = [s for s in csets[i+1:] if boc.isdisjoint(s)]
            for v in accrue (y, boc, ts):
                yield v

    # Method by NPE
    def comb(input, lst = [], lset = set()):
        if lst:
            yield lst
        for i, el in enumerate(input):
            if lset.isdisjoint(el):
                for out in comb(input[i+1:], lst + [el], lset | set(el)):
                    yield out

    # Uncomment one of the following 2 lines to select method
    #for u in comb (sets):
    for u in accrue ([], set(), sets):
        sizes[len(u)-1] += 1
        if shoUnion: print u
    t1 = time.time()
    for t in range(nsets-1, -1, -1):
        ssum[t] = sizes[t] + ssum[t+1]
    print '{:7.3f}s  Sizes:'.format(t1-t0), [s for (s,t) in zip(sizes, ssum) if t>0]
    nsets += ndelta

编辑:在函数accrue中,参数(u, bset, csets)使用如下:
• u = 当前集合并集中的集合列表
• bset = "big set" = u 的平面值 = 已使用的元素
• csets = 候选集 = 符合条件的集列表
注意如果accrue的第一行被替换为
def accrue(csets, u=[], bset=set()):
第七行 by
for v in accrue (ts, y, boc):
(即,如果重新排序参数并为 u 和 bset 提供默认值)然后可以通过 [accrue(listofsets)] 调用 accrue 以生成其兼容联合列表。

关于评论中提到的ValueError: zero length field name in format错误是在使用Python 2.6时发生的,请尝试以下操作。

# change:
    print "Set size = {:3d}, Number max = {:3d}".format(setsize, topnum)
# to:
    print "Set size = {0:3d}, Number max = {1:3d}".format(setsize, topnum)

程序中的其他格式可能需要进行类似的更改(添加适当的字段编号)。请注意,what's new in 2.6 页面显示“对 str.format() 方法的支持已向后移植到 Python 2.6”。虽然它没有说明是否需要字段名称或数字,但它没有显示没有它们的示例。相比之下,任何一种方式都适用于 2.7.3。

【讨论】:

  • 另外,这段代码对我不起作用。它在打印命令“print”Set size = {:3d}, Number max = {:3d}“.format(setsize, topnum) ValueError: zero length field name in format:. 我正在运行 python 2.6
  • @jpwat7:我不知道你的函数中的三个参数 u、bset 和 cset 是什么。
  • concept303,我在这些问题的答案末尾添加了 2 个注释。顺便说一句,如果您比当前接受的答案更喜欢这个答案,请随意选择它! :)
  • @jpwat7:如果它比当前选择的运行速度更快,我会这样做,因为这是公平的做法。但我需要先测试一下。我自己已经更正了打印错误,但我现在正在尝试将我的数据加载到其中并运行它(正如我所说,新手请耐心等待)。试图让它使用我的列表...
  • [x for x in accrue(map(set, scenarios))] 产生[[set([1, 2])], [set([1, 2]), set([3, 4])], [set([2, 3])], [set([3, 4])]] 时,语句[map(list,x) for x in accrue(map(set, scenarios))] 产生[[[1, 2]], [[1, 2], [3, 4]], [[2, 3]], [[3, 4]]] 即如果x 是集合列表,只需说map(list,x) 即可转换为列表列表.
【解决方案4】:

使用itertools.combinationsset.intersectionfor-else 循环:

from itertools import *
lis=[[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]
def func(lis):
    for i in range(1,len(lis)+1):
       for x in combinations(lis,i):
          s=set(x[0])
          for y in x[1:]:
              if len(s & set(y)) != 0:
                  break
              else:
                  s.update(y)    
          else:
              yield x


for item in func(lis):
    print item

输出:

([1, 2, 3],)
([3, 6, 8],)
([4, 9],)
([6, 11],)
([1, 2, 3], [4, 9])
([1, 2, 3], [6, 11])
([3, 6, 8], [4, 9])
([4, 9], [6, 11])
([1, 2, 3], [4, 9], [6, 11])

【讨论】:

  • 这有点类似于我开始处理它的方式,但你把它放在我的工作代码中。谢谢!
  • @concept303 更新解决方案,使用for-else 循环和生成器函数简化代码。
  • 这似乎比第一个答案工作得更快。这可能吗?对于 23 个元素,我用第一种方法得到 1 分钟,用你的方法得到 15 秒。嗯...我添加了另外 2 个元素使其变为 25,时间变成了一分钟。所以这个也必然有指数级的时间增长。
【解决方案5】:

类似于NPE's solution,但它没有递归,它返回一个列表:

def disjoint_combinations(seqs):
    disjoint = []
    for seq in seqs:
        disjoint.extend([(each + [seq], items.union(seq))
                            for each, items in disjoint
                                if items.isdisjoint(seq)])
        disjoint.append(([seq], set(seq)))
    return [each for each, _ in disjoint]

for each in disjoint_combinations([[1, 2, 3], [3, 6, 8], [4, 9], [6, 11]]):
    print each

结果:

[[1, 2, 3]]
[[3, 6, 8]]
[[1, 2, 3], [4, 9]]
[[3, 6, 8], [4, 9]]
[[4, 9]]
[[1, 2, 3], [6, 11]]
[[1, 2, 3], [4, 9], [6, 11]]
[[4, 9], [6, 11]]
[[6, 11]]

【讨论】:

    【解决方案6】:

    不使用 itertools 包的单行程序。 这是您的数据:

    lE={}
    lE[0]=[1, 2, 3]
    lE[1] = [3, 6, 8]
    lE[2] = [4, 9]
    lE[4] = [6, 11]
    

    这是单行:

    results=[(lE[v1],lE[v2]) for v1 in lE for v2  in lE if (set(lE[v1]).isdisjoint(set(lE[v2])) and v1>v2)]
    

    【讨论】:

    • 但这会根据 OP 的要求产生[[1, 2, 3], [4, 9], [6, 11]] 吗?
    • 它只构建 (x,y) 元素,但正如我现在看到的那样,需要 powerset,抱歉我错过了重点
    • 我第一次阅读问题时犯了完全相同的错误:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    • 2019-04-15
    • 2012-04-11
    • 1970-01-01
    相关资源
    最近更新 更多