【问题标题】: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 会解释它一样 - 但没有不必要的重复项。

标签: python combinations itertools combinatorics


【解决方案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]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-27
        • 1970-01-01
        • 1970-01-01
        • 2019-11-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多