0

I have a python list like the following:

sample_list = [20, 15, 35, 50, 2, 300, 225] 
target_value = 200

Now, I want to write a python program that returns a list containing all the elements those total should be less than the target_value and the closest value of target_value. In the case of the mentioned example, the function should return the following list:

[20, 15, 35, 50, 2]

As 2+15+20+35+50 = 122 which is less than target_value = 200 but if I add any other value (300/225), it would be larger than 200.

I'm looking for a very optimized solution for this problem.

sksoumik
  • 845
  • 1
  • 9
  • 23
  • 1
    Where is your try? Where did you fail at? – Iron Fist Feb 06 '21 at 11:49
  • What is the logic to make a list? As many (or less) elements as possible? or must consume orderly? – Chris Feb 06 '21 at 12:01
  • @iron-fist It should be orderly. This problem just came into my mind when I was solving the problem of Two Sum problem from Leetcode. I tried to solve it but I thought it was very naive. So, I wanted to see the optimized solutions of others. – sksoumik Feb 06 '21 at 12:22
  • Does this answer your question? [Find all numbers in a integer list that add up to a number](https://stackoverflow.com/questions/32319836/find-all-numbers-in-a-integer-list-that-add-up-to-a-number) – Tomerikoo Feb 06 '21 at 12:24
  • @Tomerikoo no, it doesn't. – sksoumik Feb 06 '21 at 12:27
  • So show why it doesn't. What you tried and how it fails. You basically need to change the `==` to `<=` from the accepted answer – Tomerikoo Feb 06 '21 at 12:35
  • Your question is also not clear enough. What would happen if `80` was in the list as well? What would be your expected output? Adding it to your current output will bring to a sum of `202` but on the other hand `80+50 = 130 < 200` – Tomerikoo Feb 06 '21 at 12:37
  • Does this answer your question? [Find all numbers that sum closest to a given number python](https://stackoverflow.com/questions/48655612/find-all-numbers-that-sum-closest-to-a-given-number-python) – Tomerikoo Feb 06 '21 at 12:46
  • Does this answer your question? [Finding Combination of Numbers that Sum closest to a target](https://stackoverflow.com/questions/56793992/finding-combination-of-numbers-that-sum-closest-to-a-target) – Tomerikoo Feb 06 '21 at 12:48
  • Are you looking for an efficient dynamic programming approach without using itertools. Because that would be a very interesting question indeed. – pakpe Feb 06 '21 at 13:07
  • the sum of the returned list should never be higher than 200, this is a simple thing to understand from my given description I guess. The sum of the elements in the returned list should be closest to `200` but not more than 200. – sksoumik Feb 06 '21 at 15:47

4 Answers4

2

This is the classic closest sum problem. It lends itself to a dynamic programming algorithm. The following solution has a complexity of O(n.k) where n is the length of the array and k is the desired threshold. This implementation uses a dynamic dictionary, although the same can be done with a dynamic 2D list.

def closestSum(arr, k):
    #create a dp dictionary to grow. key is closest sum so far, value is the list of numbers that add up to key
    dp_dict = {0:[]}

    for num in arr:
        dict_copy = dict(dp_dict)
        # grow or update the dp dict with the closest sum so far
        for sum in dp_dict:
            if sum + num < k:
                dict_copy[sum+num] = dp_dict[sum]+[num]
        dp_dict = dict_copy

    #traceback - find the item in dp dict that is closest to k
    result = (k,[])
    for sum, number_list in dp_dict.items():
        distance = abs(k - sum)
        if distance < result[0]:
            result = (distance, number_list)
    return result[1]

sample_list = [20, 15, 35, 50, 2, 300, 225]
target_value = 200
print(closestSum(sample_list, target_value))
#[20, 15, 35, 50, 2]
pakpe
  • 5,391
  • 2
  • 8
  • 23
1

I don't know if this is the most efficient way of doing it, but it definetly works:

from itertools import combinations

scores = {}
sample_list = [20, 15, 35, 50, 2, 300, 225]
target_value = 200
sample_list = [a for a in sample_list if a <= target_value]

for i in range(len(sample_list)):
    for combination in combinations(sample_list, i + 1):
        if sum(combination) <= target_value:
            scores[sum(combination)] = combination

print(list(scores[max(scores.keys())])) # [20, 15, 35, 50, 2]

To make it more efficient you could break the loop if any combination reaches the target value, since it will be the highest. But since this is a pretty unlikely scenario, it won't change the overall efficiency.

Honn
  • 737
  • 2
  • 18
  • Consider modifying your code to meet the question requirement that the sum must be less than k. Combinations that sum up to k should be excluded. I'm sure you realize that the bigO of running combinations inside a loop is quite large. – pakpe Feb 07 '21 at 00:06
0

Is this the solution you were looking for?

sample_list = [20, 15, 35, 300, 50, 2, 70, 199]
target_value = 200

editable_list = sample_list
final_list=[]
current_value = 0

for i in editable_list:
    absolute_difference_function = lambda list_value : abs(list_value - target_value)
    closest_value = min(editable_list, key=absolute_difference_function)

    if current_value + closest_value > target_value:
        editable_list.remove(closest_value)

    else:
        current_value += closest_value
        final_list.append(closest_value)
        editable_list.remove(closest_value)


print(final_list)
  • This only works, when the elements of the final combination are in the first spots. Something like `sample_list = [20, 15, 35, 300, 50, 2, 225]` wouldn't work. – Honn Feb 06 '21 at 12:06
  • I have edited my solution with your new sample_list. Maybe this is what you are looking for. – Jordy van Dongen Feb 06 '21 at 12:12
  • Yup this works, I am not the author of the question though so I don't what he is looking for. – Honn Feb 06 '21 at 12:23
  • It doesn't work for `[20, 15, 35, 300, 50, 2, 70, 199]` this list. It should return `199` in this case. Number/s should be the closest of 200. – sksoumik Feb 06 '21 at 12:30
  • alright, I'm starting to understand what you want. I'll go find a solution. – Jordy van Dongen Feb 06 '21 at 12:33
  • I have edited my solution again, this will now search for the closest value to the target value and add it to the final list if it stays under the target value. – Jordy van Dongen Feb 06 '21 at 12:47
  • 1
    It still doesn't work. For [3,4,5,9,6,7,8], target = 14, it returns [9] whereas the correct answer is [5, 8]. I am not sure your code is fixable. – pakpe Feb 06 '21 at 23:52
0

I think this can be done much simpler:

def sum_until(nums, target):
    solution = []
    for num in nums:
        if sum(solution) < target and (sum(solution) + num) < target;
            solution.append(num)
        else:
            break
    return solution
TheEagle
  • 5,808
  • 3
  • 11
  • 39
  • The reason it seems simpler is because it doesn't consider all the combination of numbers that are not located contiguously at the beginning of the array. For instance, for [3,4,5,6,7,8], target = 14, it returns [3,4,5] whereas the correct answer is [7,8]. – pakpe Feb 06 '21 at 23:48
  • @pakpe its fulfilling the test case the OP provided, if he wants sonething different he must provide more test cases. – TheEagle Feb 06 '21 at 23:54