更新:添加了通用解决方案。
这里有一个解决方案,代码稍微复杂一些,但不会产生重复元素,可以懒惰地求值:
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 + m 元素的元组其中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 = 3 和m = 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)))
这个想法是一样的,你构建“模板”(这次它们包含值的索引而不是值本身),然后是它们的笛卡尔积。