在没有追溯过滤的情况下创建对每个元素有限制的组合
Posted
技术标签:
【中文标题】在没有追溯过滤的情况下创建对每个元素有限制的组合【英文标题】:Creating combinations with restrictions to each element without retrospective filtering 【发布时间】:2020-02-22 11:45:19 【问题描述】:我需要创建一个列表的所有组合,但每个元素都有一个上下边界(每个数字增加 +2)。
例如 n=4: 从 [0, 1, 2, 3](下边界)到 [0, 2, 4, 6](上边界)的所有组合应导致:
[[0, 1, 2, 3],
[0, 1, 2, 4],
[0, 1, 2, 5],
[0, 1, 2, 6],
[0, 1, 3, 4],
[0, 1, 3, 5],
[0, 1, 3, 6],
[0, 1, 4, 5],
[0, 1, 4, 6],
[0, 2, 3, 4],
[0, 2, 3, 5],
[0, 2, 3, 6],
[0, 2, 4, 5],
[0, 2, 4, 6]]
直接的解决方案是使用itertools.combinations(range(2*n-1),n)
,然后过滤掉所有无效的。但这首先会产生大量无效组合,然后通过过滤器运行所有组合进一步减慢速度。在我的情况下,这对于大 n 来说效率太低了。
我需要一个解决方案,它甚至不尝试为每个数字循环更高,而只在每个数字的边界内创建组合。
我确定有嵌套循环或回溯的简单解决方案,但我找不到。
【问题讨论】:
当第三个位置的上限为 4 ([0, 2, 4, 6]
) 时,为什么 [0, 1, 5, 6]
在您的列表中?还是我误解了你的上限?
谢谢!你是对的,那是我的错误:已编辑并更正。
怎么样例如[0,2,2,3]
也有效吗?
好问题,但在这种情况下不是。组合中没有重复项,就像 itertools.combinations 会解释它一样 - 但没有不必要的重复项。
【参考方案1】:
这是一个使用递归的解决方案:
def n_increasing(n, start=0, end=0):
if n == 0:
yield []
return
for choice in range(start, end+1):
for remaining in n_increasing(n-1, choice+1, end+2):
yield [choice, *remaining]
用法:
>>> list(n_increasing(4))
[[0, 1, 2, 3],
[0, 1, 2, 4],
[0, 1, 2, 5],
[0, 1, 2, 6],
[0, 1, 3, 4],
[0, 1, 3, 5],
[0, 1, 3, 6],
[0, 1, 4, 5],
[0, 1, 4, 6],
[0, 2, 3, 4],
[0, 2, 3, 5],
[0, 2, 3, 6],
[0, 2, 4, 5],
[0, 2, 4, 6]]
【讨论】:
【参考方案2】:这是一个更一般问题的解决方案,您可以将下限和上限序列作为参数传递:
def constrained_combinations(lower, upper):
lower, upper = list(lower), list(upper)
n = len(lower)
if len(upper) != n:
raise ValueError('lower and upper bound sequences must have same length')
# eliminate impossible options
for i in range(1, n):
lower[i] = max(lower[i], lower[i-1] + 1)
upper[-i-1] = min(upper[-i-1], upper[-i] - 1)
if any(low > high for low, high in zip(lower, upper)):
return () # no solutions
def helper(t, i):
if i == n:
yield t
else:
a, b = lower[i], upper[i]
if t: a = max(a, t[-1] + 1)
for j in range(a, b + 1):
yield from helper(t + (j,), i + 1)
return helper((), 0)
例子:
>>> gen = constrained_combinations([0, 1, 2, 3], [0, 2, 4, 6])
>>> list(gen)
[(0, 1, 2, 3),
(0, 1, 2, 4),
(0, 1, 2, 5),
(0, 1, 2, 6),
(0, 1, 3, 4),
(0, 1, 3, 5),
(0, 1, 3, 6),
(0, 1, 4, 5),
(0, 1, 4, 6),
(0, 2, 3, 4),
(0, 2, 3, 5),
(0, 2, 3, 6),
(0, 2, 4, 5),
(0, 2, 4, 6)]
【讨论】:
【参考方案3】:您可以将递归与生成器一起使用:
a, b = [0, 1, 2, 3], [0, 2, 4, 6]
def combos(d, c = []):
if not d:
yield c
else:
yield from [b for i in range(*d[0]) for b in combos(d[1:], c+[i]) if not c or i > c[-1]]
print(list(combos(list(zip(a, [i+1 for i in b])))))
输出:
[0, 1, 2, 3]
[0, 1, 2, 4]
[0, 1, 2, 5]
[0, 1, 2, 6]
[0, 1, 3, 4]
[0, 1, 3, 5]
[0, 1, 3, 6]
[0, 1, 4, 5]
[0, 1, 4, 6]
[0, 2, 3, 4]
[0, 2, 3, 5]
[0, 2, 3, 6]
[0, 2, 4, 5]
[0, 2, 4, 6]
【讨论】:
以上是关于在没有追溯过滤的情况下创建对每个元素有限制的组合的主要内容,如果未能解决你的问题,请参考以下文章