查找总和最接近目标的数字组合

时间:2019-06-27 15:06:39

标签: python-3.x

所以我有一个浮点数的组合,并且我还有一些目标值,我想看看这些浮点数的任何组合(允许重复)是否可以求和得尽可能接近目标值。

现在,我从简单开始,我想看看是否可以使用当前的浮点列表仅达到一个目标值。总和最多可以偏离目标0.5。

我搜索了许多其他代码,但通常它们涉及准确地达到目标的数字组合,并且没有提供一定的回旋余地。

很有可能没有浮点数的组合会达到目标值,我希望代码能够反映出来。

编辑:示例

假设我必须使用组合[13.02,16.08,100.01,44.63]。例如,如果我的目标是134,则代码​​应返回[44.63、44.63、44.63],因为这是唯一总和在134的0.5之内的组合。如果目标是28,则应该得到空括号[]。

2 个答案:

答案 0 :(得分:0)

最简单的方法是遍历数字的所有组合,并跟踪哪个组合在当前时间最接近

import math
import itertools

numbers = [1,2,3,4]
target = 9
best_combination = ((None,))
best_result = math.inf
best_sum = 0

print("target:{}\nnumbers:{}".format(target,numbers))
# generate all combinations of the numbers
# including combinations with different lengths
for L in range(0, len(numbers)+1):
    for combination in itertools.combinations(numbers, L):
        sum = 0
        for number in combination:
            sum += number
        result = target - sum
        if abs(result) < abs(best_result):
            best_result = result
            best_combination = combination
            best_sum = sum
            print("\nnew best\n{}\nsum:{} off by:{}".format(best_combination, best_sum, best_result))

print("\nbest sum{} = {}".format(best_combination, best_sum))

输出

target:9
numbers:[1, 2, 3, 4]

new best
()
sum:0 off by:9

new best
(1,)
sum:1 off by:8

new best
(2,)
sum:2 off by:7

new best
(3,)
sum:3 off by:6

new best
(4,)
sum:4 off by:5

new best
(1, 4)
sum:5 off by:4

new best
(2, 4)
sum:6 off by:3

new best
(3, 4)
sum:7 off by:2

new best
(1, 3, 4)
sum:8 off by:1

new best
(2, 3, 4)
sum:9 off by:0

best sum(2, 3, 4) = 9

如果最后的结果相距太远,则可以取消最后的资格,也可以将最佳结果设置为您希望在开始时被截取的结果。

因此,如果您希望结果与给定目标的距离小于0.5,请将best_result = math.inf更改为best_result = 0.5
该输出(对于输入[1,2,3]和目标9)将为

target:9
numbers:[1, 2, 3]
best sum(None,) = 0.5

这不是最有效的方法。有捷径可走,但这是最简单的第一种方法。

编辑,为允许重复,我首先生成一个列表,其中包含每个数字的最大数量,因此在您的示例中,该列表包括最大13.02s和16.08s以及...

import math
import itertools

target = 134
numbers = [13.02, 16.08, 100.01, 44.63]
n = len(numbers)
long_list_numbers = []
for number in numbers:
    long_list_numbers += ([number] * int(target / number))


best_combination = ((None,))
best_result = math.inf
best_sum = 0

print("target:{}\nnumbers:{}".format(target,numbers))

# generate all combinations of the numbers
# including combinations with different lengths
for L in range(0, len(long_list_numbers)+1):
    for combination in itertools.combinations(long_list_numbers, L):
        sum = 0
        for number in combination:
            sum += number
        result = target - sum
        if abs(result) < abs(best_result):
            best_result = result
            best_combination = combination
            best_sum = sum
            print("\nnew best\n{}\nsum:{} off by:{}".format(best_combination, best_sum, best_result))

print("\nbest sum{} = {}".format(best_combination, best_sum))

输出

target:134
numbers:[13.02, 16.08, 100.01, 44.63]

new best
()
sum:0 off by:134

new best
(13.02,)
sum:13.02 off by:120.98

new best
(16.08,)
sum:16.08 off by:117.92

new best
(100.01,)
sum:100.01 off by:33.989999999999995

new best
(13.02, 100.01)
sum:113.03 off by:20.97

new best
(16.08, 100.01)
sum:116.09 off by:17.909999999999997

new best
(100.01, 44.63)
sum:144.64000000000001 off by:-10.640000000000015

new best
(13.02, 13.02, 100.01)
sum:126.05000000000001 off by:7.949999999999989

new best
(13.02, 16.08, 100.01)
sum:129.11 off by:4.889999999999986

new best
(16.08, 16.08, 100.01)
sum:132.17000000000002 off by:1.829999999999984

new best
(44.63, 44.63, 44.63)
sum:133.89000000000001 off by:0.10999999999998522

best sum(44.63, 44.63, 44.63) = 133.89000000000001

答案 1 :(得分:0)

稍事休息,然后再次解决该问题。我想出了一个我相信会更快的解决方案。尽管对于大的输入它仍然非常非常慢,并且我没有做任何测试或计算来支持它是否更快,但是我什至没有对它进行过两到三个输入的正确性测试。将它作为单独的答案发布,因为它是完全不同的方法。

程序中可能有优化特定功能的空间,但是方法是

初始化
首先将每个数字加到自己的解决方案中。
提出一个没有数字且总和=目标+容忍度的附加解决方案,以该服务器作为要击败的初始分数
如果有任何输入数字比这更好,则将其保存为当前最佳解决方案 将每个数字加到到目前为止已获得的所有总和的集合中

迭代
跟踪当前总和集的大小,如果这个总和集增加了,我们将结束寻找解决方案
列出用于添加解决方案的临时列表,如果我们直接添加到self.solution_list,则会添加要在当前迭代中检查的解决方案,我们不希望这样做
现在我们遍历列表中每个先前找到的解决方案,并检查将数字列表中的每个数字添加到列表中后会发生什么情况
如果先前找到的解决方案和当前数量的总和是唯一的(不是总和),并且比以前的解决方案好,则可以将其添加到临时列表中,以便稍后更新我们的解决方案列表,然后更新我们的集合和最佳解决方案。 br /> 请注意,我们还检查了该解决方案是否比先前的解决方案差,这种情况的唯一发生方式是当我们比目标解决方案更接近目标而超出目标值时,例如,如果先前的解决方案的总和为5,目标6和当前要加的数字是3加3使我们达到8,这是一个较差的解决方案,没有正数的加法会使其更佳
现在,如果我们检查的解决方案没有添加任何数字可以更接近目标,我们就不必再检查该解决方案了,因为它是死胡同,因此我们可以将其从解决方案列表中删除。不过,我们不需要从总和中删除它,也可以将其从最佳溶胶中移除。
类似地,如果我们检查的解决方案将每个数字相加,那么将来访问此解决方案将变得毫无意义,因为添加值将导致值已合计。因此,我们可以再次删除这些
当所有可能的加法数字导致已经发现路径时,大多数解决方案将被淘汰
最后,我们可以检查将临时列表添加到永久列表中,并检查是否添加了任何内容,以便确定我们是否已完成。如果临时添加列表为空,则程序也应完成。

这可能有点难以理解,如果您有任何问题,请告诉我,但我希望这对您有所帮助:)

class solution:

    def __init__(self, numbers, n_sum):
        self.numbers = numbers
        self.n_sum = n_sum

    def getNumbers(self):
        return self.numbers

    def getSum(self):
        return self.n_sum

class solutions:

    def __init__(self, numbers, target, tolerance):
        self.numbers = numbers
        self.target = target

        self.solution_list = [solution([num], num) for num in numbers]
        self.best_sol = solution([None], target + tolerance)

        for sol in self.solution_list:
            if abs(sol.n_sum - target) < abs(self.best_sol.n_sum - target):
                self.best_sol = sol

        self.sums = set(numbers)

    def iterate(self):
        sums_len = len(self.sums)
        add_sols = []
        for sol in self.solution_list:
            # if all get added to current sol then current sol will be obsolete
            # if none get added to current sol then current sol is a dead end
            # if either of these cases are true at the end we can remove this path
            # (leaving it as best solution if it is)
            added_all = True
            added_none = True
            for num in self.numbers:
                new_sum = sol.getSum() + num
                # if new sum is better that sol sum and not in all sums
                # it is a new solution
                if new_sum not in self.sums and abs(self.target - new_sum) < abs(self.target - sol.getSum()):
                    added_none = False
                    new_sol = solution(sol.getNumbers() + [num], new_sum)
                    # update the best solution if new sol is better
                    if abs(self.target - new_sum) < abs(self.target - self.best_sol.n_sum):
                        self.best_sol = new_sol
                    # update sums
                    self.sums.add(new_sum)
                    # add the new solution to a list to add at the end
                    add_sols.append(new_sol)
                else:
                    added_all = False
            # prune redundant branches
            if added_all or added_none:
                self.solution_list.remove(sol)
        # add the solutions
        self.solution_list += add_sols
        # return true when finished
        if len(self.sums) == sums_len:
            return False
        return True

target = 130
numbers = [13.02, 16.08, 100.01, 44.63]
solutions = solutions(numbers, target, 0.1)

i = 0
while(solutions.iterate()):
    print(i)
    i+=1
print(solutions.best_sol.numbers)

0.1公差和134目标的输出

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[None]

0.5公差和134目标的输出

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02]

公差0.5和目标1340

0
1
2
.
.
.
134
[13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 44.63, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 13.02, 44.63, 13.02, 13.02, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 44.63, 13.02, 13.02, 13.02, 100.01, 100.01, 44.63]