优化功率集搜索

时间:2018-07-18 10:06:10

标签: python performance optimization

我有一些代码可以找到符合某些条件的所有项目组合(在大小限制下,在本例中为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!)或接近它),但是有什么方法可以对其进行优化?现在,我在寻找一些基本概念的同时还学习了算法的复杂性,因此欢迎您提出任何建议。

1 个答案:

答案 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_asum_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次。