我有一些代码可以找到符合某些条件的所有项目组合(在大小限制下,在本例中为8),但是在收集大约20个项目之后,它变得太慢了。这是代码的简化版本:
from itertools import chain, combinations
import timeit
def powerset(iterable, n):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
mx = min(len(s), n)
return chain.from_iterable(combinations(s, r) for r in range(1, mx+1))
collection = [
(0, 10, "item1"),
(5, 5, "item2"),
(10, 0, "item3"),
]
targetA = 5
targetB = 5
def build():
output = []
for s in powerset(collection, 8):
a, b = (0, 0)
for item in s:
a += item[0]
b += item[1]
if a >= targetA and b >= targetB:
output.append(s)
return output
print(timeit.timeit('build()', number=100, globals=globals()))
我的原始代码对项目使用类,并且具有较大的集合,并且需要能够搜索大于或小于任意值的值。我知道这只是蛮力搜索(我认为这是O(n!)
或接近它),但是有什么方法可以对其进行优化?现在,我在寻找一些基本概念的同时还学习了算法的复杂性,因此欢迎您提出任何建议。
答案 0 :(得分:0)
您可以编写自己的递归函数并将目标值作为参数传递。这样,您可以确定丢失的数量和剩余物品可达到的最大数量,并在无法再组合时尽早停止。
def combs_with_sum(collection, num, targetA, targetB):
def _inner(i, n, s):
# found a valid combination with n elements?
sum_a = sum(c[0] for c in s)
sum_b = sum(c[1] for c in s)
if n == 0 and sum_a >= targetA and sum_b >= targetB:
yield s
# more elements to go and still valid solutions?
max_a = sum(sorted(c[0] for c in collection[i:])[-n:])
max_b = sum(sorted(c[1] for c in collection[i:])[-n:])
if n > 0 and i < len(collection) and sum_a + max_a >= targetA and sum_b + max_b >= targetB:
# combinations without and with the current element
yield from _inner(i+1, n, s)
yield from _inner(i+1, n-1, s + [collection[i]])
return (x for n in range(1, num+1) for x in _inner(0, n, []))
在这里,sum_a >= targetA and sum_b >= targetB
确保只产生有效的结果,并且sum_a + max_a >= targetA and sum_b + max_b >= targetB
防止搜索不可行的组合。这将在每个回合中对集合进行切片,排序和再次切片,以找到剩余元素可实现的最大总和,但这会阻止搜索大量分支。 (sum_a
和sum_b
的值也可以作为参数传递,但这无关紧要。)对于“小于”情况,只需将>=
转换为{{ 1}}并反转排序,以获取<=
个最小剩余项的总和。
应用于一些随机测试数据:
n
这将调用from random import randint, seed
seed(0)
collection = [(randint(1, 10), randint(1, 10), i) for i in range(20)]
print(collection)
res = list(combs_with_sum(collection, 4, 30, 30))
函数<1,000次,而不是不进行_inner
检查(即测试所有组合)的> 15,000次。