【问题标题】:Combinations of M elements from N with non consecutive repetitions来自 N 的 M 个元素与非连续重复的组合
【发布时间】:2019-02-20 10:22:03
【问题描述】:

我有以下问题,可以总结如下:

假设你有两个大于 0 的整数 N(它定义了 数组n=np.array(range(N)) 和 M。我们想生成所有 可能的元素组合 of n 有长度 M,条件是没有相等的元素是连续的

例如,对于 N=3 (n=[0,1,2]) 和 M=3,我们应该得到:

(0,1,0), (0,1,2) (0,2,0), (0,2,1), (1,0,1), (1,0,2), (1,2,0), (1,2,1), (2,0,1), (2,0,2), (2,1,0), (2,1,2)

(0,0,1), (1,1,1), (2,1,1)...等组合不必出现。 请注意,所有有效组合的数量仅由N*(N-1)**(M-1) 给出。

到目前为止,对于这样的例子,我正在使用这个简单的脚本(它还计算从 m=1 到 m=M 的所有不同长度的组合):

import numpy as np
N = 3
M = 3
p = np.array(range(N))  

ic = [0]*M
c2 = np.zeros((int(N*(N-1)**(M-1)),M))
c1 = np.zeros((int(N*(N-1)**(M-2)),M-1))
c0 = np.zeros((int(N*(N-1)**(M-3)),M-2))
for i in p:
    c0[ic[0],:] = [i]
    ic[0] += 1
    for j in p[p!=i]:
        c1[ic[1],:] = [i,j]
        ic[1] += 1
        for k in p[p!=j]:
            c2[ic[2],:] = [i,j,k]
            ic[2] += 1

问题在于,这只适用于 M=3 的特定情况,并且 M 可以是任何大于 0 的整数。所以对于某些 M,前面的代码应该有 M 个必须手动引入的嵌套循环。

我尝试定义一个可变循环数的递归函数,例如计算组合的 number 的函数(上面的等式给出的数字):

def rec_f(c,N,M):   
    if n>=1:
        for x in range(N):             
            c=rec_f(c,N,M-1)
    else:
        c += 1            
    return c

我什至不知道为什么它适用于那个简单的问题。现在,问题是我需要知道先前循环的索引才能复制生成所有可能组合的脚本,但我不知道该怎么做。

我还尝试制作一个独特的for 循环(将迭代 N*(N-1)^(M-1) 次),记住这些组合可以表示为以 N 为底的数字,但玩了一段时间后,我没有任何用处。

如果有任何帮助,我将不胜感激,在此先感谢(很抱歉发了这么长的帖子)!

【问题讨论】:

    标签: python numpy for-loop recursion combinatorics


    【解决方案1】:

    只需将最后一个元素(如果有)作为可选参数添加到递归函数中。此外,不需要N 参数,只需传递要从中选择的元素(也使其更普遍适用)。另外,我建议将其设为生成器函数,因为组合的数量可能会变得相当大,因此您可以在它们出现时一个一个地使用它们。

    def combinations(elements, m, last=-1):
        if m:
            for x in elements:
                if x != last:
                    for rest in combinations(elements, m-1, x):
                        yield (x,) + rest
        else:
            yield ()
    

    或者更简洁一点,使用yield from 生成器表达式:

    def combinations(elements, m, last=-1):
        if m:
            yield from ((x,) + rest for x in elements if x != last
                                    for rest in combinations(elements, m-1, x))
        else:
            yield ()
    

    两个版本的示例结果:

    print(*combinations(range(3), 3))
    # (0, 1, 0), (0, 1, 2), (0, 2, 0), (0, 2, 1), (1, 0, 1), (1, 0, 2), (1, 2, 0), (1, 2, 1), (2, 0, 1), (2, 0, 2), (2, 1, 0), (2, 1, 2)
    

    【讨论】:

      【解决方案2】:

      您所描述的可以通过使用 Python 的(强大的)itertools 库来实现,然后根据您的条件进行过滤。但是您想要的是数组的 product 而不是 combination

      这是一种方法,假设参数 N 和 M。

      import numpy as np
      import itertools
      
      p = np.arange(N)
      

      获取长度为3的产品:

      product_iterator = itertools.product(p, repeat=M)
      

      这为您提供了一个迭代器对象。你可以从中得到一个具体的列表(并立即把它变成一个数组,在这个例子中,虽然我称它为一个列表):

      product_list = np.array(list(product_iterator))
      

      此时你得到了一个包含所有 27 种组合的数组:[ [0 0 0], [0 0 1], [0 0 2], ..., [2 2 1], [2 2 2] ] .现在您可以按所需的标准过滤这些。

      在您的情况下,“没有连续的重复元素”相当于检查两个连续元素之间的差异是否永远不为零。所以我们得到了差异:

      diffs = np.diff(product_list,axis=1)
      

      这会产生:

      [[ 0  0]
       [ 0  1]
       [ 0  2]
       [ 1 -1]
       [ 1  0]
       [ 1  1]
       [ 2 -2]
       [ 2 -1]
       [ 2  0]
       [-1  0]
       [-1  1]
       [-1  2]
       [ 0 -1]
       [ 0  0]
       [ 0  1]
       [ 1 -2]
       [ 1 -1]
       [ 1  0]
       [-2  0]
       [-2  1]
       [-2  2]
       [-1 -1]
       [-1  0]
       [-1  1]
       [ 0 -2]
       [ 0 -1]
       [ 0  0]]
      

      现在我们逐行检查是否有零:

      no_consec_indexes = np.apply_along_axis(lambda x: np.all(x), 1, diffs)
      

      这会产生一个布尔数组no_consec_indexes

      array([False, False, False,  True, False,  True,  True,  True, False,
             False,  True,  True, False, False, False,  True,  True, False,
             False,  True,  True,  True, False,  True, False, False, False])
      

      你可以用它来过滤掉原来的产品数组:

      product_list[no_consec_indexes]
      

      这会产生您想要的答案:

       array([[0, 1, 0],
             [0, 1, 2],
             [0, 2, 0],
             [0, 2, 1],
             [1, 0, 1],
             [1, 0, 2],
             [1, 2, 0],
             [1, 2, 1],
             [2, 0, 1],
             [2, 0, 2],
             [2, 1, 0],
             [2, 1, 2]])
      

      【讨论】:

      • 非常感谢您提供的非常详细的回答,非常感谢。虽然老实说我意识到了这种可能性,但我认为执行时间会太长,所以我拒绝了它。现在已经提出了几种方法,我将尝试看看它们之间的表现如何。
      猜你喜欢
      • 2013-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-24
      相关资源
      最近更新 更多