【问题标题】:Prune a list of combinations based on sets修剪基于集合的组合列表
【发布时间】:2016-06-13 17:17:17
【问题描述】:

虽然这个问题是使用 Python 编程语言制定的,但我相信它更多的是一个编程逻辑问题。

我有一个所有可能组合的列表,即:n 选择 k

我可以使用

来准备这样的列表
import itertools
bits_list = list(itertools.combinations(range(n), k))

如果“n”为 100,“k”为 5,则“bits_list”的长度为 75287520。

现在,我想修剪此列表,使数字分组显示,或者不分组。让我们以以下集合为例:

第 1 组:[0, 1, 2]
第 2 组:[57, 58]
第 3 组:[10、15、20、25]
第 4 组:[10、11、12、13、14、15、16、17、18、19]

这里每个集合都需要一起出现在 bits_list 的任何成员中,或者根本不出现。

到目前为止,我只能想到一个蛮力的 if-else 方法来解决这个问题,但是这样 if-else 条件的数量会非常多。

这是我所拥有的:

bits_list = [x for x in list(itertools.combinations(range(n), k)) 
             if all(y in x for y in [0, 1, 2]) or
             all(y not in x for y in [0, 1, 2])]

现在,这仅涵盖第 1 组。我想为许多组执行此操作。如果集合的长度大于 k 的值,我们可以忽略该集合(例如,k = 5 和集合 4)。

请注意,最终目标是让“k”在一个范围内迭代,比如 [5:25] 并处理附加列表。列表的大小在这里呈指数增长,从计算上讲,非常昂贵!

如果 'k' 为 10,python 解释器会在任何具有 16 GB RAM 的普通笔记本电脑上完成之前中断进程。我需要找到一个适合相对现代服务器(不是集群或服务器场)内存的解决方案。

非常感谢任何帮助!

P.S.:直觉上,将这个问题想象成为登上公共巴士或火车系统的人生成所有可能的案例。通常,您会登上整个团队,或者您不登上任何人。


更新:
  1. 对于上述给定的集合,如果 k = 5,则 bits_list 的有效成员将是 [0, 1, 2, 57, 58],即:Set1 和 Set2 的组合。如果 k = 10,那么我们可以构建 Set1 + Set2 + Set3 + NoSetElement 作为可能的成员。 @DonkeyKong 的解决方案让我意识到我没有在我的问题中明确提到这一点。

  2. 我有很多套;我打算使用足够的集合来修剪完整的组合列表,以便 bits_list 最终适合内存。

  3. @9000 的建议在这里完全有效,即在每次迭代期间,我可以将组合保存为实际位。

【问题讨论】:

  • 这个问题可能更适合计算机科学堆栈交流
  • 您是在火车上接人还是在上车?
  • 所以你肯定需要一个实际的列表,在内存中,有这么多项目?因为这就是你的瓶颈所在。
  • 您是否尝试计算有效或输出它们的组合?
  • @DaveGalvin,我正在使用这些组合对我选择的项目执行进一步的操作。

标签: python algorithm


【解决方案1】:

这仍然会在某个时间点(大约 n=90,k =5),但它比您当前的实现要快得多。对于n=80k=5,我的基本基准测试让我的解决方案在 2.6 秒左右,而你的解决方案在 52 秒左右。

我们的想法是分别构建过滤器的不相交部分和子集部分。不相交的部分是微不足道的,子集部分是通过取所有长度为k - set_len 的不相交组合的itertools.product 和集合中的各个元素来计算的。

from itertools import combinations, product, chain
n = 80
k = 5
set1 = {0,1,2}

nots = set(range(n)) - set1
disj_part = list(combinations(nots, k))
subs_part = [tuple(chain(x, els)) for x, *els in 
              product(combinations(nots, k - len(set1)), *([e] for e in set1))]
full_l = disj_part + subs_part

【讨论】:

  • 我正在研究基于此的解决方案。谢谢你的精彩回答。有足够的集合使用,内存错误就不会发生。这个解决方案的优点是我们不是在构建列表然后修剪,而是从集合中构建列表! P.S.:请参阅有关使用集合组合的问题的更新。
  • @RahulMurmuria 是的,确切地说,从集合中构建可以极大地降低成本。别客气。我不完全确定你对组合组合的更新是什么意思,你能试着澄清一下吗?您只是为您的“全部或全部”案例组合集合吗?
  • 如果您认为使用我给出的直观示例,公交车上有 1 个家庭并不意味着公交车上的其余人必须是单身。您可以在公共汽车上拥有任意数量的家庭。同样,如果 k = 10,则 bits_list 的有效成员将是 [0, 1, 2, 57, 58, 10, 15, 20, 25, 33]
  • 如果我们正在修剪,我们就不需要考虑这种情况。这些组合成员不会从 bits_list 中删除。但是,如果我们从集合开始构建列表,那么我们需要明确处理这种情况。
  • @RahulMurmuria 干杯,明天再来这里看看,如果有时间,我可能会为您提供更新的答案。
【解决方案2】:

如果您实际上将您的位表示为位,即整数 n 位长的二进制表示中的 0/1 值,并且恰好设置了 k 位,那么您存储数据所需的 RAM 量将大大增加更小。

此外,您还可以使用位操作来检查 mask 中的所有位是否实际已设置 (value & mask == mask),或全部未设置 (value | ~mask == value)。

蛮力可能会比您花在考虑更聪明的算法上的时间更短,因此一次性过滤完全没问题。

如果您必须经常快速地执行此操作,并且您的 n 很少或更少,我宁愿使用 cython 来有效地描述蛮力算法,而不是查看算法改进。现代 CPU 可以有效地处理 64 位数字;不比较数字的一部分,你不会有太多好处。

OTOH,如果您的 n 真的很大,并且要比较的集合的数量也很大,您可以对您的位进行分区以进行有效比较。

假设您可以有效地比较一块 64 位,并且您的位列表包含例如每个100块。然后你可以对字符串做同样的事情:逐块比较,如果其中一个块不匹配,则不要比较其余的块。

【讨论】:

  • 事实上,我稍后在我的程序中将 bits_list 的每个成员都表示为 bitarray 以表示不相关的内容。相反,在准备这个 bits_list 时将它们表示为 bitarray 绝对是好的设计。感谢您的建议!
【解决方案3】:

一个更快的实现是替换 if 和 all() 语句:

bits_list = [x for x in list(itertools.combinations(range(n), k)) 
             if all(y in x for y in [0, 1, 2]) or
             all(y not in x for y in [0, 1, 2])]

使用 python 的集合操作 isdisjoint()issubset() 操作。

bits_generator = (set(x) for x in itertools.combinations(range(n), k))
first_set = set([0,1,2])
filter_bits = (x for x in bits_generator 
             if x.issubset(first_set) or
             x.isdisjoint(first_set))
answer_for_first_set = list(filter_bits)

我可以继续使用生成器,使用生成器不会耗尽内存,但你会等待并加速宇宙的热寂。不是因为 python 的运行时或其他实现细节,而是因为如果您选择较大的 N 和 K 值,即使在计算机时间内也存在一些问题。

【讨论】:

    【解决方案4】:

    根据@Mitch's answer 的想法,我创建了一个与问题中最初提出的想法略有不同的解决方案。我没有创建所有组合的列表 (bits_list),然后修剪那些与列出的集合不匹配的组合,而是从集合中构建 bits_list

    import itertools
    all_sets = [[0, 1, 2], [3, 4, 5], [6, 7], [8], [9, 19, 29], [10, 20, 30], 
                [11, 21, 31], [12, 22, 32], ...[57, 58], ... [95], [96], [97]]
    bits_list = [list(itertools.chain.from_iterable(x)) for y in [1, 2, 3, 4, 5] 
                 for x in itertools.combinations(all_sets, y)]
    

    在这里,我没有找到 n 选择 k,然后循环查找所有 k,并找到与集合匹配的组合,而是从集合开始,甚至将各个成员自己包含为集合,因此无需2 个组件 - 不相交部分和子集部分 - 在 @Mitch's answer 中讨论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-09
      • 1970-01-01
      • 1970-01-01
      • 2019-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-26
      相关资源
      最近更新 更多