快速python算法,从子集总和等于给定比率的数字列表中找到所有可能的分区

Sha*_*Han 5 python algorithm recursion combinations subset

假设我有一个从 0 到 9 的 20 个随机整数列表。我想将列表划分为N子集,以便子集总和的比率等于给定值,并且我想找到所有可能的分区。我编写了以下代码并使其适用于该N = 2案例。

import random
import itertools

#lst = [random.randrange(10) for _ in range(20)]
lst = [2, 0, 1, 7, 2, 4, 9, 7, 6, 0, 5, 4, 7, 4, 5, 0, 4, 5, 2, 3]

def partition_sum_with_ratio(numbers, ratios):
    target1 = round(int(sum(numbers) * ratios[0] / (ratios[0] + ratios[1])))
    target2 = sum(numbers) - target1
    p1 = [seq for i in range(len(numbers), 0, -1) for seq in
          itertools.combinations(numbers, i) if sum(seq) == target1
          and sum([s for s in numbers if s not in seq]) == target2]

    p2 = [tuple(n for n in numbers if n not in seq) for seq in p1]

    return list(zip(p1, p2))

partitions = partition_sum_with_ratios(lst, ratios=[4, 3])
print(partitions[0])
Run Code Online (Sandbox Code Playgroud)

输出:

((2, 0, 1, 2, 4, 6, 0, 5, 4, 4, 5, 0, 4, 5, 2), (7, 9, 7, 7, 3))
Run Code Online (Sandbox Code Playgroud)

如果计算每个子集的总和,您会发现比率为 44 : 33 = 4 : 3,这正是输入值。但是,我希望该函数适用于任意数量的子集。例如,我期望

partition_sum_with_ratio(lst, ratios=[4, 3, 3])
Run Code Online (Sandbox Code Playgroud)

返回类似的东西

((2, 0, 1, 2, 4, 6, 0, 5, 4, 4, 3), (5, 0, 4, 5, 2, 7), (9, 7, 7))
Run Code Online (Sandbox Code Playgroud)

我已经考虑这个问题一个月了,我发现这非常困难。我的结论是,这个问题只能通过递归来解决。我想知道是否有任何相对较快的算法。有什么建议?

Dav*_*tat 3

是的,需要递归。基本逻辑是将一部分分为一部分和其余部分,然后以所有可能的方式递归地分割其余部分。我跟随你的思路,假设一切都是可区分的,这创造了很多可能性,可能多得无法一一列举。尽管如此:

import itertools


def totals_from_ratios(sum_numbers, ratios):
    sum_ratios = sum(ratios)
    totals = [(sum_numbers * ratio) // sum_ratios for ratio in ratios]
    residues = [(sum_numbers * ratio) % sum_ratios for ratio in ratios]
    for i in sorted(
        range(len(ratios)), key=lambda i: residues[i] * ratios[i], reverse=True
    )[: sum_numbers - sum(totals)]:
        totals[i] += 1
    return totals


def bipartitions(numbers, total):
    n = len(numbers)
    for k in range(n + 1):
        for combo in itertools.combinations(range(n), k):
            if sum(numbers[i] for i in combo) == total:
                set_combo = set(combo)
                yield sorted(numbers[i] for i in combo), sorted(
                    numbers[i] for i in range(n) if i not in set_combo
                )


def partitions_into_totals(numbers, totals):
    assert totals
    if len(totals) == 1:
        yield [numbers]
    else:
        for first, remaining_numbers in bipartitions(numbers, totals[0]):
            for rest in partitions_into_totals(remaining_numbers, totals[1:]):
                yield [first] + rest


def partitions_into_ratios(numbers, ratios):
    totals = totals_from_ratios(sum(numbers), ratios)
    yield from partitions_into_totals(numbers, totals)


lst = [2, 0, 1, 7, 2, 4, 9, 7, 6, 0, 5, 4, 7, 4, 5, 0, 4, 5, 2, 3]
for part in partitions_into_ratios(lst, [4, 3, 3]):
    print(part)
Run Code Online (Sandbox Code Playgroud)