【问题标题】:Find all numbers that sum closest to a given number python查找总和最接近给定数字python的所有数字
【发布时间】:2018-02-07 23:54:58
【问题描述】:

我在下面有一个脚本,它给出了最接近给定总和的 2 个值。它还遍历给定总和的列表,并在每次迭代后删除已使用的数字。

我需要修改这个脚本,以便它产生最接近每个总和的必要数量的值,而不是 2。脚本需要接受浮点值并且不能重复使用值。实际上,它需要选择最接近目标的最有效集合,更新集合以删除使用的值,然后移动到下一个目标等

对于需要 3 个数字或 4 个等才能真正接近总和的特定用例/集合,它不能很好地工作。我需要这个脚本也能够接受浮点值,这个脚本目前可以。

任何建议将不胜感激。另外,如果有人知道更好的脚本,请告诉我。

import sys
def find_closese_sum(numbers, target):
    start = 0
    end = len(numbers) - 1
    result = sys.maxint
    result_tuple = None
    while start < end:
        if numbers[start] + numbers[end] == target:
            print 0, (numbers[start], numbers[end])
            return
        elif numbers[start] + numbers[end] > target:
            if abs(numbers[start] + numbers[end] - target) < result:
                result = abs(numbers[start] + numbers[end] - target)
                result_tuple = (numbers[start], numbers[end])
            end -= 1
        else:
            if abs(numbers[start] + numbers[end] - target) < result:
                result = abs(numbers[start] + numbers[end] - target)
                result_tuple = (numbers[start], numbers[end])
            start += 1

    for i in result_tuple:
        numbers.remove(i)

    return result_tuple

if __name__ == "__main__":
    target = [14,27,39]
    numbers = [1,5,5,10,7,8,11,13,66,34]
    print numbers
    numbers = sorted(numbers)


    for i in target:
        result_shown = find_closese_sum(numbers, i)

        print result_shown

【问题讨论】:

  • 这是subset sum problem。是NP-complete;这并不一定意味着没有比蛮力更好的方法,但它可能意味着没有明显更好的方法。讨论at this other question.
  • @PhilippVengrinovich 在您的示例中,numbers = [1,5,5,10,7,8,11,13,66,34],您有两个值 5。如果对于第二个目标数字,组合包含 5,应该从 numbers 列表中删除哪个 5?第一?第二个?还是两者兼而有之?
  • 即如果使用5,更新后的序列应该是什么:[1,10,7,8,11,13,66,34][1,5,10,7,8,11,13,66,34]
  • @AGNGazer 在回答你的问题时,它应该是第二个序列。因此,如果任何组合使用 5,则应从列表中删除该单个 5,但应保留其他组合。

标签: python python-2.7 subset-sum


【解决方案1】:

我没有看到任何优雅的方法来做到这一点,所以你可能不得不暴力破解解决方案。制作所有可能的数字子集,对它们求和,然后检查哪个最接近目标。它看起来像这样。

from itertools import permutations

def find_closest_sum(numbers, target, n):
    permlist = list(permutations(numbers, n))
    sumlist = [sum(l) for l in permlist]
    maxpos = 0
    for i in range(1, len(sumlist)):
        if abs(sumlist[i] - target) < abs(sumlist[maxpos]-target):
             maxpos = i

     return permlist[maxpos]

numbers = [1,5,5,10,7,8,11,13,66,34]
result_shown = find_closest_sum(numbers, 20, 4)
print result_shown

使用排列会使您编写的许多代码变得不必要。

【讨论】:

    【解决方案2】:

    我的回答是,使用 python 3,但你应该能够轻松地移植它。

    from itertools import combinations as c
    
    if __name__ == "__main__":
        target = [14,27,39]
        numbers = [1,5,5,10,7,8,11,13,66,34]
    
        for combo in range(1,4):
            for i in target:
                #lambda to find the difference between sum and target
                diff = lambda x: abs(sum(x) - i)
                #get all unique combinations
                combos = {tuple(sorted(c)) for c in c(numbers, combo)}
                #sort them
                combos = sorted(combos, key = diff)
                #get the smallest difference
                smallest = diff(combos[0])
                #filter out combos larger than the smaller difference
                result = [c for c in combos if diff(c) == smallest]
                print('results for {}, best combinations are off by {}:'.format(i, smallest))
                print(result)
    

    您说您需要排除用于先前结果的数字。为此,只需将它们从列表中删除:

                #after print(result), inside the "for i in target" loop
                #if you want to exclude all numbers used in all combinations
                numbers = [n for n in numbers if n not in [b for a in result for b in a]]
                #if you only need to remove the first match
                numbers = [n for n in numbers if n not in result[0]]
    

    【讨论】:

    • 谢谢,这很有帮助!您将如何修改此脚本,使其不重复使用用于生成前一对的值,不幸的是,这是我需要遵循的条件之一?所以基本上输出将是任何最接近总和的组合,删除使用的数字,然后使用更新的数字集和另一个总和重试。我尝试修改您的代码,但找不到不重复使用数字的好解决方案。非常感谢您在这方面的帮助!
    • 再次感谢您!最后一个问题 - 当我尝试使用/修改您的脚本时,我似乎无法只返回最有效的序列。因此,例如对于 2 个目标数字,返回的第一个序列将是“最小”尽可能接近 0 的位置,与第二个目标数字相同,同时删除已经用于最有效的第一组的值等。每个组合到目前为止,我已经尝试过返回列表索引超出范围错误。再次感谢,我一直在与这个作斗争一段时间,非常感谢您的帮助
    • 我认为在这种情况下发生的事情是,由于您多次循环列表,您实际上是从列表中删除所有数字,因此没有任何组合剩余。由您决定如何处理此问题。请注意,我的外部列表包含 1、2 和 3 个数字的组合,我假设您实际上并不需要同时所有这些数字,因此请尝试仅执行一个循环(例如,组合 = 3)。由于您只有十个号码,您很快就会用完。
    • 谢谢,这非常有帮助,但是当我尝试在浮点数和浮点数目标的大型数据集上运行时,脚本不起作用。我会尝试更多地解决这个问题,再次感谢!
    【解决方案3】:
    import numpy as np
    import itertools
    
    def find_closese_sum(numbers, targets):
        numbers = numbers[:]
        for t in targets:
            if not numbers:
                break
            combs = sum([list(itertools.combinations(numbers, r))
                         for r in range(1, len(numbers)+1)], [])
            sums = np.asarray(list(map(sum, combs)))
            bestcomb = combs[np.argmin(np.abs(np.asarray(sums) - t))]
            numbers = list(set(numbers).difference(bestcomb))
            print("Target: {},  combination: {}".format(t, bestcomb))
    

    示例

    In [101]: import numpy as np
         ...: import itertools
         ...: 
         ...: def find_closese_sum(numbers, targets):
         ...:     numbers = numbers[:]
         ...:     for t in targets:
         ...:         if not numbers:
         ...:             break
         ...:         combs = sum([list(itertools.combinations(numbers, r))
         ...:                      for r in range(1, len(numbers)+1)], [])
         ...:         sums = np.asarray(list(map(sum, combs)))
         ...:         bestcomb = combs[np.argmin(np.abs(np.asarray(sums) - t))]
         ...:         numbers = list(set(numbers).difference(bestcomb))
         ...:         print("Target: {},  combination: {}".format(t, bestcomb))
         ...: 
         ...: targets = [14,27.,39]
         ...: numbers = [1.0,5,5,10,7,8,11,13,66,34]
         ...: find_closese_sum(numbers, targets)
         ...: 
    Target: 14,  combination: (1.0, 13)
    Target: 27.0,  combination: (5, 5, 10, 7)
    Target: 39,  combination: (8, 34)
    

    【讨论】:

    • 非常感谢您的快速回复。我尝试运行您的脚本,但无法成功执行。您能否让我知道您在哪里传递了哪些值才能运行?提前谢谢!
    • @PhilippVengrinovich 我已经修复了我的代码中的两个错别字(抱歉)并添加了一个真正可行的示例。
    • 谢谢!但是,我注意到您的脚本不适用于浮点值,并且还重用了值。不幸的是,这些是我对这个脚本的一些条件。您是否认为可以以允许完全相同的输出但使用浮点数且不重用值的方式修改您的脚本?再次感谢您的帮助!
    • 谢谢,效果很好!我还尝试使用以下值运行您的脚本:targets = [778592.81, 87702.82, 39757.92, 24590.12, 12534.6, 28441.06] (请参阅下一条评论中的数字)但所有脚本给我的都是“杀死:9”
    • numbers = [3317.64, 2424.72, 13535.4, 3317.64, 4859.04, 20701.2, 5522.72, 8098.4, 11146.8, 1546.632, 23796.32, 2981.36, 1546.632, 17041.28, 34080.56, 4247.68, 27215.76, 13845.72, 38686.08, 51123.84 , 153371.52, 102241.68,
    【解决方案4】:

    我的方法不复杂,试试这个:

    import itertools
    def finding_closet(ls,target,depth):
        closest = []
        for i in itertools.combinations(ls, depth):
    
            if sum(i) == target:
                return i
            else:
                closest.append((abs(sum(i) - target), i))
        return min(closest)[1]
    

    测试用例 0:

    print(finding_closet([1,5,5,10,7,8,11,13,66,34],20,2))
    

    输出:

    (7, 13)
    

    测试用例 1:

    print(finding_closet([1,5,5,10,7,8,11,13,66,34],20,4))
    

    输出:

    (1, 5, 5, 8)
    

    test_case_3:

    print(finding_closet([1,5,5,10,7,8,11,13,66,34],20,8))
    

    输出:

    (1, 5, 5, 10, 7, 8, 11, 13)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-02
      • 2021-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多