我正在使用itertools.combinations()
来迭代整数元组。
我对满足某些条件的最低金额的元组感兴趣:
def findLowestNiceTuple:
for tup in itertools.combinations(range(1, 6), 2):
if niceTuple(tup):
return tup
生成器的默认顺序不是元素总和的顺序。例如:
>>> itertools.combinations(range(1, 6), 2)
给出一个生成器,它将产生以下元素:
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
如您所见,(1,5)的总和大于(2,3)的总和。为了提前终止,我需要按..., (1, 4), (2, 3), (1, 5), ...
的顺序添加元组。
对于适度数量的组合,您可以使用sorted()
:
>>> sorted(itertools.combinations(range(1, 6), 2), key=sum)
[(1, 2), (1, 3), (1, 4), (2, 3), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
但是,sorted()
将生成器转换为完全保留在内存中的列表。这意味着它不再能很好地扩展。像itertools.combinations(range(1, 600), 400)
之类的东西将不可避免地产生MemoryError
。
是否有更友好的方式来达到预期效果?
PS:我确实意识到完全迭代我提到的最后一个序列需要很长时间,但我正在寻找的元组应该非常接近开始。如果我可以指望订单,我可以像第一个片段一样提前终止。答案 0 :(得分:3)
以下是我如何解决它,使用递归函数找到总和为给定值的所有组合:
def ordered_combinations(pop, n):
pop = sorted(pop)
for s in range(sum(pop[:n]), sum(pop[-n:])+1):
yield from get_sums(pop, s, n)
def get_sums(pop, s, n):
if n == 1:
if s in pop:
yield [s]
return
for i, v in enumerate(pop):
if sum(pop[i:i+n]) > s:
return
for rest in get_sums(pop[i+1:], s-v, n-1):
rest.append(v)
yield rest
以下是输出的示例:
>>> for c in ordered_combinations(range(1, 8), 4):
print(c, sum(c))
[4, 3, 2, 1] 10
[5, 3, 2, 1] 11
[6, 3, 2, 1] 12
[5, 4, 2, 1] 12
[7, 3, 2, 1] 13
[6, 4, 2, 1] 13
[5, 4, 3, 1] 13
[7, 4, 2, 1] 14
[6, 5, 2, 1] 14
[6, 4, 3, 1] 14
[5, 4, 3, 2] 14
[7, 5, 2, 1] 15
[7, 4, 3, 1] 15
[6, 5, 3, 1] 15
[6, 4, 3, 2] 15
[7, 6, 2, 1] 16
[7, 5, 3, 1] 16
[6, 5, 4, 1] 16
[7, 4, 3, 2] 16
[6, 5, 3, 2] 16
[7, 6, 3, 1] 17
[7, 5, 4, 1] 17
[7, 5, 3, 2] 17
[6, 5, 4, 2] 17
[7, 6, 4, 1] 18
[7, 6, 3, 2] 18
[7, 5, 4, 2] 18
[6, 5, 4, 3] 18
[7, 6, 5, 1] 19
[7, 6, 4, 2] 19
[7, 5, 4, 3] 19
[7, 6, 5, 2] 20
[7, 6, 4, 3] 20
[7, 6, 5, 3] 21
[7, 6, 5, 4] 22
组合总是首先产生最大值,作为我如何将它们构建为列表的工件(通过在末尾添加小值,而不是通过连接到前面)。如果您希望从最小到最大排序,可以将rest.append(v); yield rest
行更改为yield [v]+rest
。
代码使用Python 3.3中引入的yield from
语法。如果您使用的是不支持该版本的早期版本,则可以使用此等效代码:
for v in get_sums(pop, s, n):
yield v
该代码甚至可以处理您描述的800个组合范围内的400种组合的极端情况。这是该计算的前20个结果(仅显示其最大的10个值,因为其余的都是相同的390到1),以及它们的总和:
>>> for i, v in enumerate(ordered_combinations(range(1, 800), 400)):
if i >= 20:
break
print(v[:10], sum(v))
[400, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80200
[401, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80201
[402, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80202
[401, 400, 398, 397, 396, 395, 394, 393, 392, 391] 80202
[403, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80203
[402, 400, 398, 397, 396, 395, 394, 393, 392, 391] 80203
[401, 400, 399, 397, 396, 395, 394, 393, 392, 391] 80203
[404, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80204
[403, 400, 398, 397, 396, 395, 394, 393, 392, 391] 80204
[402, 401, 398, 397, 396, 395, 394, 393, 392, 391] 80204
[402, 400, 399, 397, 396, 395, 394, 393, 392, 391] 80204
[401, 400, 399, 398, 396, 395, 394, 393, 392, 391] 80204
[405, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80205
[404, 400, 398, 397, 396, 395, 394, 393, 392, 391] 80205
[403, 401, 398, 397, 396, 395, 394, 393, 392, 391] 80205
[403, 400, 399, 397, 396, 395, 394, 393, 392, 391] 80205
[402, 401, 399, 397, 396, 395, 394, 393, 392, 391] 80205
[402, 400, 399, 398, 396, 395, 394, 393, 392, 391] 80205
[401, 400, 399, 398, 397, 395, 394, 393, 392, 391] 80205
[406, 399, 398, 397, 396, 395, 394, 393, 392, 391] 80206
因为它是递归的,所以如果你请求1000组合,这段代码可能会失败(这是由于Python的默认递归限制)。如有必要,您可以使用sys.setrecursionlimit
修改限制。
如果由于get_sums
切片(并且因此复制)递归步骤中的总体而导致非常大的人群,它也可能存在内存问题。如果您对此代码的使用仅使用range
,则可以通过从pop = sorted(pop)
删除ordered_combinations
行来解决内存问题,因为Python 3的range
对象可以有效切片(即range(1,100)[10:]
为range(11,100)
)。