繁体   English   中英

优化功率集搜索

[英]Optimizing the search of a powerset

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

您可以编写自己的递归函数并将目标值作为参数传递。 这样,您可以确定丢失的数量和剩余项目可达到的最大数量,并尽早停止以防万一。

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值也可以作为参数传递,但这无关紧要。)对于“小于”情况,只需将>=<=并反转排序即可获得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))

这将调用_inner函数<1,000次,而不是> 15,000次而不进行sum_a + max_a ...检查(即测试所有组合)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM