所有的 +-r, +-s 的排列组合

9

给定两个数字rs,我想要得到一个列表,其中包含所有n +-rm +-s的排列组合。例如(当r=3.14s=2.71时),

n = 1
m = 1
out = [
    (+r, +s), (+r, -s), (-r, +s), (-r, -s), 
    (+s, +r), (+s, -r), (-s, +r), (-s, -r)
    ]

n = 1
m = 2
out = [
    (+r, +s, +s), (+r, -s, +s), (-r, +s, +s), (-r, -s, +s), ...
    (+s, +r, +s), (-s, +r, +s), (+s, -r, +s), (-s, -r, +s), ...
    ...
    ]

使用 itertools.product([+r, -r], repeat=n) 可以得到 rs 的列表,然后只需要交错它们即可,但我不确定这是否是正确的做法。

效率并不是非常重要,因此我不介意产生许多重复的结果,只需在之后使它们唯一即可。

3个回答

6

更新: 添加了通用解决方案。

这里提供的解决方案代码稍微有些复杂,但不会产生重复元素,并且可以进行惰性求值:

from itertools import combinations, product, chain

r = 3.14
s = 2.71
n = 1
m = 2
idx = combinations(range(n + m), n)
vs = ((r if j in i else s for j in range(n + m)) for i in idx)
res = chain.from_iterable(product(*((+vij, -vij) for vij in vi)) for vi in vs)
print("\n".join(map(str, res)))

输出:

(3.14, 2.71, 2.71)
(3.14, 2.71, -2.71)
(3.14, -2.71, 2.71)
(3.14, -2.71, -2.71)
(-3.14, 2.71, 2.71)
(-3.14, 2.71, -2.71)
(-3.14, -2.71, 2.71)
(-3.14, -2.71, -2.71)
(2.71, 3.14, 2.71)
(2.71, 3.14, -2.71)
(2.71, -3.14, 2.71)
(2.71, -3.14, -2.71)
(-2.71, 3.14, 2.71)
(-2.71, 3.14, -2.71)
(-2.71, -3.14, 2.71)
(-2.71, -3.14, -2.71)
(2.71, 2.71, 3.14)
(2.71, 2.71, -3.14)
(2.71, -2.71, 3.14)
(2.71, -2.71, -3.14)
(-2.71, 2.71, 3.14)
(-2.71, 2.71, -3.14)
(-2.71, -2.71, 3.14)
(-2.71, -2.71, -3.14)

解释

我们可以将输出视为包含 n +/- r 元素和 m +/- s 元素的排列,或者换句话说,是由 n 个 +/- r 元素和其余部分的 +/- s 元素组成的元组。 idx 包含所有可能的 +/- r 元素的位置的元组;例如,对于第一个结果,它是 (0,)

然后,对于每个这些元组 i,我们在 vs 中创建“模板”元组,它们只是具有大小为 n + m 的元组,在其中索引为 i 的元素为 r,其余元素为 s。因此,对于 idx 中的元组 (0,),您将获得 (r, s, s)。如果 n + m 很大,则可以考虑先进行步骤 idx = map(set, idx) 以获得更快的 in 操作,但我不确定在哪个点上这将值得一试。

最后,对于 v 中的每个模板 vi,我需要考虑使用其每个元素的正值和负值的所有可能性。因此,它是 (+vi[0], -vi[0]), (+vi[1], -vi[1]), ... 的笛卡尔积。最后,您只需将每个这些产品的生成器链接在一起即可获得最终结果。

通用解决方案

为了构建一个针对任意数量不同元素的问题的通用解决方案,您需要考虑索引集合的 分区。例如,对于 n = 3m = 5,您可以将 {0, 1, 2, 3, 4, 5, 6, 7} 分成大小为 3 和 5 的两部分的所有可能方式。以下是一个实现:

from itertools import chain, repeat, permutations, product


def partitions(*sizes):
    if not sizes or all(s <= 0 for s in sizes):
        yield ()
    for i_size, size in enumerate(sizes):
        if size <= 0:
            continue
        next_sizes = sizes[:i_size] + (sizes[i_size] - 1,) + sizes[i_size + 1:]
        for p in partitions(*next_sizes):
            yield (i_size,) + p


def signed_permutations(*elems):
    values, sizes = zip(*elems)
    templates = partitions(*sizes)
    return chain.from_iterable(
        product(*((+values[ti], -values[ti]) for ti in t)) for t in templates)


r = 3.14
s = 2.71
n = 1
m = 2
res = signed_permutations((r, n), (s, m))
print("\n".join(map(str, res)))

思路相同,您需要构建“模板”(这次它们包含值的索引而不是值本身),然后从它们中构建笛卡尔积。


1
这似乎不起作用:list(itertools.product(*([[+r, -r]] * 1 + [[+s, -s]] * 1))) 返回 [(3.14, 2.71), (3.14, -2.71), (-3.14, 2.71), (-3.14, -2.71)] -- 3.14 从未出现在第二个位置。 - Nico Schlömer
1
一两句解释肯定有助于理解代码。 - Nico Schlömer
@NicoSchlömer 是的,没错,谢谢,我已经更新了。 - jdehesa
1
@NicoSchlömer 又一次更新,提供了一个适用于任意数量不同元素的通用解决方案。 - jdehesa
1
可以使用Knuth的“算法L”来代替自己的“分区”,例如在https://dev59.com/Pm855IYBdhLWcg3wm1m3上实现。 - Nico Schlömer
显示剩余2条评论

5
您还可以将rspermutations+1-1product结合起来,并使用zip将两者组合在一起。这样,整个构造在我看来会更易读一些:
>>> n, m = 1, 2
>>> r, s = 3.14, 2.71
>>> [[x*i for x,i in zip(perm, prod)] for perm in permutations([r]*n + [s]*m) 
...                                   for prod in product((+1, -1), repeat=n+m)]
[[3.14, 2.71, 2.71],
 [3.14, 2.71, -2.71],
 ...
 [-2.71, -2.71, 3.14],
 [-2.71, -2.71, -3.14]]

4

首先使用product,然后对每个元素进行permutations。然后将所有结果连接起来并传递给set()以删除重复项:

arr = set(itertools.chain.from_iterable([
    itertools.permutations(x)
    for x in itertools.product(*([[+r, -r]] * n + [[+s, -s]] * m))
    ]))
print(arr)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接