来自N的M个元素与非连续重复的组合

时间:2019-02-20 10:22:04

标签: python numpy for-loop recursion combinatorics

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

  

假设您有两个大于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个嵌套循环,必须手动引入。

我尝试定义一个可变数目的循环的 recursive 函数,像这样一个计算组合的 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为底,但是玩了一段时间后,我没有任何用处。

我将不胜感激,在此先感谢您(感谢您的长帖子)!

2 个答案:

答案 0 :(得分:3)

只需将最后一个元素(如果有)添加为递归函数的可选参数。另外,不需要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)

答案 1 :(得分: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]])