【问题标题】:Python, all combinations of element addition between two lists, with constraintPython,两个列表之间元素添加的所有组合,有约束
【发布时间】:2019-07-26 00:40:47
【问题描述】:

我有两个列表:

list1 = [1, 2, 3]
list2 = [0.5, 1]

任务是通过将第二个列表中的变量添加到其元素中,从原始列表中创建所有可能的组合:

list1 = [1+0.5, 2, 3]
list2 = [1, 2+0.5, 3]
list3 = [1, 2, 3+0.5]
list4 = [1+0.5, 2+0.5, 3]
list5 = [1, 2+0.5, 3+0.5]
list6 = [1+0.5, 2, 3+0.5]
list7 = [1+0.5, 2+0.5, 3+0.5]

list8 = [1+1, 2, 3]
list9 = [1,2+1,3]
list10 = [1,2,3+1]
list 11 = [1+1,2+1,3]
...
list = [1+0.5, 2+1, 3]
list = [1+0.5, 2, 3+1]
...

增加问题的复杂性,我想执行上述操作,但有一个限制:例如,新列表中所有元素的总和应小于 8。

有什么好办法吗?可能递归地考虑到我的初始列表将有大约 30 个元素,而我的第二个列表大约有 5 个?

谢谢。

【问题讨论】:

  • 这是一个组合的噩梦。您有 5 个选择 1-5 组列表,每个列表必须选择 30 个选择 1-30 个索引来更改,每个选择的索引由 1-5 个不同值之一调整(取决于选择的值的数量该列表组)。最差的一组,有 30 个选择 15 个指数进行调整,每个指数有五个值可供选择,其本身将涉及超过 4.7 个 quintillion 列表。做不到。
  • 有没有办法使用手头的约束来修剪可能的列表选项?
  • 没有更详细的限制,没有。任何不能有效地让您预先修剪绝大多数选项的限制都是不可行的。
  • 明白,谢谢。你有没有机会告诉我你是如何为上面的例子做的?

标签: python list loops recursion itertools


【解决方案1】:

您可以使用递归与生成器,提供一个条件来检查累加和的运行值:

使用演示字符串表示的解决方案:

def pairs(a, b, _l, _c = []):
  if len(_c) == _l:
    yield _c
  else:
     if a:
       for i in b:
         yield from pairs(a[1:], b, _l, _c=_c+[f'{a[0]}+{i}'])
         yield from pairs(a[1:], b, _l, _c= _c+[a[0]])

print(list(pairs([1, 2, 3], [0.5, 1], 3)))

输出:

[['1+0.5', '2+0.5', '3+0.5'], ['1+0.5', '2+0.5', 3], ['1+0.5', '2+0.5', '3+1'], ['1+0.5', '2+0.5', 3], ['1+0.5', 2, '3+0.5'], ['1+0.5', 2, 3], ['1+0.5', 2, '3+1'], ['1+0.5', 2, 3], ['1+0.5', '2+1', '3+0.5'], ['1+0.5', '2+1', 3], ['1+0.5', '2+1', '3+1'], ['1+0.5', '2+1', 3], ['1+0.5', 2, '3+0.5'], ['1+0.5', 2, 3], ['1+0.5', 2, '3+1'], ['1+0.5', 2, 3], [1, '2+0.5', '3+0.5'], [1, '2+0.5', 3], [1, '2+0.5', '3+1'], [1, '2+0.5', 3], [1, 2, '3+0.5'], [1, 2, 3], [1, 2, '3+1'], [1, 2, 3], [1, '2+1', '3+0.5'], [1, '2+1', 3], [1, '2+1', '3+1'], [1, '2+1', 3], [1, 2, '3+0.5'], [1, 2, 3], [1, 2, '3+1'], [1, 2, 3], ['1+1', '2+0.5', '3+0.5'], ['1+1', '2+0.5', 3], ['1+1', '2+0.5', '3+1'], ['1+1', '2+0.5', 3], ['1+1', 2, '3+0.5'], ['1+1', 2, 3], ['1+1', 2, '3+1'], ['1+1', 2, 3], ['1+1', '2+1', '3+0.5'], ['1+1', '2+1', 3], ['1+1', '2+1', '3+1'], ['1+1', '2+1', 3], ['1+1', 2, '3+0.5'], ['1+1', 2, 3], ['1+1', 2, '3+1'], ['1+1', 2, 3], [1, '2+0.5', '3+0.5'], [1, '2+0.5', 3], [1, '2+0.5', '3+1'], [1, '2+0.5', 3], [1, 2, '3+0.5'], [1, 2, 3], [1, 2, '3+1'], [1, 2, 3], [1, '2+1', '3+0.5'], [1, '2+1', 3], [1, '2+1', '3+1'], [1, '2+1', 3], [1, 2, '3+0.5'], [1, 2, 3], [1, 2, '3+1'], [1, 2, 3]]

添加和修剪的解决方案:

def pairs(a, b, _l, _c = [], _sum=0):
  if len(_c) == _l:
    yield _c
  else:
    if a:
      for i in b:
        if a[0]+i+_sum < 8:
          yield from pairs(a[1:], b, _l, _c=_c+[a[0]+i], _sum=_sum+a[0]+i)
        if a[0]+_sum < 8:
          yield from pairs(a[1:], b, _l, _c= _c+[a[0]], _sum=_sum+a[0])

print(list(pairs([1, 2, 3], [0.5, 1], 3, _sum=0)))

输出:

[[1.5, 2.5, 3.5], [1.5, 2.5, 3], [1.5, 2.5, 3], [1.5, 2, 3.5], [1.5, 2, 3], [1.5, 2, 4], [1.5, 2, 3], [1.5, 3, 3], [1.5, 3, 3], [1.5, 2, 3.5], [1.5, 2, 3], [1.5, 2, 4], [1.5, 2, 3], [1, 2.5, 3.5], [1, 2.5, 3], [1, 2.5, 4], [1, 2.5, 3], [1, 2, 3.5], [1, 2, 3], [1, 2, 4], [1, 2, 3], [1, 3, 3.5], [1, 3, 3], [1, 3, 3], [1, 2, 3.5], [1, 2, 3], [1, 2, 4], [1, 2, 3], [2, 2.5, 3], [2, 2.5, 3], [2, 2, 3.5], [2, 2, 3], [2, 2, 3], [2, 2, 3.5], [2, 2, 3], [2, 2, 3], [1, 2.5, 3.5], [1, 2.5, 3], [1, 2.5, 4], [1, 2.5, 3], [1, 2, 3.5], [1, 2, 3], [1, 2, 4], [1, 2, 3], [1, 3, 3.5], [1, 3, 3], [1, 3, 3], [1, 2, 3.5], [1, 2, 3], [1, 2, 4], [1, 2, 3]]

编辑:指定下限:

def pairs(a, b, _l, _c = [], _sum=0):
  if len(_c) == _l:
    if _sum > 2:
      yield _c
  else:
    if a:
      for i in b:
        if a[0]+i+_sum < 8:
          yield from pairs(a[1:], b, _l, _c=_c+[a[0]+i], _sum=_sum+a[0]+i)
        if a[0]+_sum < 8:
          yield from pairs(a[1:], b, _l, _c= _c+[a[0]], _sum=_sum+a[0])

print(list(pairs([1, 2, 3], [-1, 1], 3, _sum=0)))

输出:

[[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 3], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 2, 3], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 3, 3], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 2, 3], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 2, 4], [1, 2, 3], [1, 3, 2], [1, 3, 3], [1, 3, 3], [1, 2, 2], [1, 2, 3], [1, 2, 4], [1, 2, 3], [2, 1, 2], [2, 1, 3], [2, 1, 4], [2, 1, 3], [2, 2, 2], [2, 2, 3], [2, 2, 3], [2, 3, 2], [2, 2, 2], [2, 2, 3], [2, 2, 3], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 2, 4], [1, 2, 3], [1, 3, 2], [1, 3, 3], [1, 3, 3], [1, 2, 2], [1, 2, 3], [1, 2, 4], [1, 2, 3]]

【讨论】:

  • 真的很感激这个,它工作得很好。我现在正在尝试向该算法添加额外的约束。例如,如果我现在有 list2 = [-1, 1],我希望新列表中所有元素的总和在 2 到 8 之间。不幸的是,如果 a[0]+i+_sum 2 将输出一个空列表。我假设是因为它永远不会通过 if 语句。您将如何修改代码以适应这种情况?
  • @ValentinC。要指定“下限”,我建议在 yield 递归基本情况之前设置一个条件来检查总和,或者将函数调用的结果过滤到仅总和在 2 到 8 之间的子列表。请参阅我最近的编辑,因为我实现了前者
  • 我怎么会错过。再次感谢您抽出宝贵时间回答!
  • @ValentinC 递归确实是据我所知的唯一方法。对于您的问题的变体,确实存在迭代解决方案,即list1list2 的组合组,其中list2 中的每个值仅出现一次(或以更短、更合理的@987654331 长度出现多次@),但结果与您实际所需的输出几乎没有相似之处。
  • @ValentinC。至于优化,ShadowRanger 的反应最好,因为用更大的输入是不可能做到的。当然,可以做一些事情来加快程序的执行,但只能达到一定的输入大小。在那之后程序仍然会挂起。
【解决方案2】:

如果没有更多更严格的限制,您所描述的内容是不可能的(即使在那时也可能是不可能的)。

让我们看看lists 的一个组。想象一下,您正在尝试添加 15 个 30 的索引,所有五个可能的值都在起作用。这是 515(超过 3000 万)种方式来填写 15 个选定的索引,30 种方式选择 15 组索引来填写(超过 1.55 亿),总计 4,733,811,035,156,250,000(4.7 quintillion)列表。可能会少一些,因为您必须至少使用这五个值中的每一个(否则您将使用一组较小的选定值重新计算相同的list),但它仍然会很疯狂。这仅适用于 15 索引,五个值的情况;其他的会稍微小一些,但大多数人在人的一生中仍然无法单独计算,更不用说聚合计算了。

您可以尝试通过先发制人地计算大量输入 list 的总和,并跳过保证超过最大累积值的任何值组合,以及先发制人地减少多重性来过滤它任何会让你超过上限的价值。但这闻起来很像an XY problem;任何时候你考虑到这种程度的组合疯狂,你可能有更好的方法来完成你的任务(和/或你的任务是不可能的)。

对于您的简单案例,您只需结合itertools 工具即可:

from itertools import combinations, product

def make_lists(list1, list2, limit):
    maxvalues = limit - sum(list1)
    minlist2 = min(list2)
    for numindices in range(1, len(list1)+1):
        if minlist2 * numindices >= maxvalues:
            continue
        for indices in combinations(range(len(list1)), numindices):
            for values in product([x for x in list2 if x < maxvalues], repeat=numindices):
                if sum(values) >= maxvalues:
                    continue
                newlist = list1[:]
                for i, v in zip(indices, values):
                    newlist[i] += v
                yield newlist

但是对于任何有意义的输入长度,这将花费“宇宙的热寂”时间。递归解决方案可以更有效地过滤掉无效输出,但除非限制非常严格,否则您仍然会在程序完成之前死亡。

【讨论】:

  • 我完全理解这个问题的大小,感谢上面的代码。它实际上非常快但内存密集,目前这不是问题。但是,我无法理解如何在代码中包含额外的约束。我将如何: 1. 对新列表应用最小总和约束?说 5 到 20 之间的新列表总和。 2. 如果我将上述推断为一个不合理的数字,有没有办法仅随机更改 list1 中的元素子集?假设一半或三分之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-12
  • 1970-01-01
  • 1970-01-01
  • 2013-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多