相同元素的排列:有效避免冗余排列

时间:2019-12-18 14:28:54

标签: python itertools

我的问题:

例如,我有4个数字

a, b, c, d = np.random.rand(4)

我需要所有可能的总和a + b - ca + b -db + c - db + c -a

我发现我可以像这样

from itertools import permutations

p = np.array(list(set(permutations([1, 1, -1, 0])))).T
sums = np.random.rand(4) @ p

到目前为止,到目前为止,我已经解决了我的问题...伤害我的感觉是使用permutations(…),因为n个数字组合的排列数当然是n!,而set所操作的过滤器会将有效排列的数量减少到百分之几。

我的问题:

是否有可能获得例如

p = np.array(list(set(permutations([1]*5 + [-1]*4 + [0]*6)))).T

没有计算所有1,307,674,368,000(几乎相同)的排列?


按照Lukas ThalerChris_Rands的建议,我检查了sympy.utilities.iterables.multiset_permutations的可能用法

In [1]: from itertools import permutations                                                

In [2]: from sympy.utilities.iterables import multiset, multiset_permutations             

In [3]: for n in (2,3): 
   ...:     l = [1]*n +[2]*n + [3]*n 
   ...:     ms = multiset(l) 
   ...:     print(n*3) 
   ...:     %timeit list(multiset_permutations(ms)) 
   ...:     %timeit set(permutations(l)) 
   ...:                                                                                   
6
568 µs ± 3.71 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
91.3 µs ± 571 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
9
9.32 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
58.3 ms ± 323 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

在我看来

  1. 对于可忽略不计的计算量,无论如何都没关系
  2. 当事情变得严峻时,Sympy中的纯Python实现很容易克服了使用setitertools.permutations的蛮力方法

所以我得出的结论是我的问题与Chris所提到的问题重复,我投票决定将其结束。

2 个答案:

答案 0 :(得分:1)

使用combinations可以降低复杂度:

from itertools import combinations
import numpy as np

z = 6 # number of zeros
p = 5 # number of 1s
n = 4 # number of -1s

indexes = set(range(z+p+n))

res = np.array([(*plus, *minus, *(zero for zero in indexes - set(plus) - set(minus)))
                for plus in combinations(indexes, p)
                for minus in combinations(indexes-set(plus), n)])

perm = np.zeros((res.shape[0], z+p+n), dtype=np.int8)
perm[np.arange(res[:,:p].shape[0])[:,None], res[:,:p]] = 1
perm[np.arange(res[:,p:p+n].shape[0])[:,None], res[:,p:p+n]] = -1

基本上,我没有选择所有排列(复杂度(n+p+z)!),而是先选择了正数元素(复杂度(n+p+z)!/(p!(n+z)!)),然后选择了否定元素(复杂度(n+z)!/(n!z!))。产生的复杂度应类似于(n+p+z)!/(n!p!z!)

答案 1 :(得分:0)

我们可以这样操作,首先使用combinations来选择求和中使用的数字,然后再次使用它来选择正(和负)数字。

from itertools import combinations, chain
from random import random

def our_sums_of(nums, num_positive):
    return (2 * sum(positives) - sum(nums) 
            for positives in combinations(nums, num_positive))

nums = [random() for _ in range(15)]
num_positive = 5
num_negative = 4

list(chain.from_iterable(map(lambda c: our_sums_of(c, num_positive), 
                             combinations(nums, num_positive + num_negative))))