Home > Net >  Approach to finding the minimum amount of numbers to add from a set to reach a certain number?
Approach to finding the minimum amount of numbers to add from a set to reach a certain number?

Time:08-05

Say I have a set containing positive integers, and a target number m. Is there an algorithm that finds the shortest way (using the least numbers) by adding them to each other to reach m? Each number in the set can be used more than once.

For example, if the set is {1, 2} and the target number is 7, the shortest way is 2 2 2 1, which has a length of 4 (instead of something like 2 2 1 1 1, which has a length of 5)

I have already tried to code an algorithm to do this, but it is extremely slow. Here is the code I have:

import itertools


def can_make_sum(target, numbers_to_use):
    numbers = [False] * (target   1)  # numbers[i] stores False if i cannot be made with numbers_to_use, or a tuple
    # storing its factors with the smallest multiplier
    for number in numbers_to_use:
        multiplier = 1
        while True:
            product = multiplier * number
            if product > target:
                break
            if not numbers[product]:  # If the value is False
                numbers[product] = (number, multiplier)
            else:
                if numbers[product][1] > multiplier:  # only replace the multiplier if it is smaller than the previous
                    numbers[product] = (number, multiplier)  # multiplier, so it is the shortest way to make that number

            multiplier  = 1

    usable_numbers = []
    for product in numbers:
        if product != False:
            for _ in range(product[1]):
                usable_numbers.append(product[0])

    answer = []  # Final answer

    for i in range(1, len(numbers)):
        subsets = list(itertools.combinations(usable_numbers, i))  # All subsets of the usable numbers with length i
        for subset in subsets:
            total = sum(subset)
            if total == target:  # This is the smallest subset
                for num in subset:
                    for _ in range(numbers[num][1]):
                        answer.append(numbers[num][0])

                return answer


print(can_make_sum(13, {1, 2, 3}))

Just trying to make 13 with these three numbers already creates a noticeable delay before the answer comes out. Could this be improved and a lot faster, or is this already the fastest method?

CodePudding user response:

Just do an A* search. The nodes of our graph are (current_sum, smallest_number_used). Each will connect with what you get if you add another number that is at most smallest_number_used in size. Each edge has weight 1. And the heuristic we use is that the distance to the end is at least math.ceil(distance_left / smallest_number_used).

Here is code, along with several test cases. I cheated a little, but the idea is the above.

import heapq
import math

def can_make_sum(target, numbers_to_use):
    numbers = list(reversed(sorted(numbers_to_use)))
    visited = set()
    to_explore = [(0, 0, 0, 0, None)]
    while 0 < len(to_explore):
        min_final_steps, current_steps, current_value, min_i, path = heapq.heappop(to_explore)
        key = (current_value, min_i)
        if key not in visited:
            visited.add(key)
            if current_value == target:
                answer = []
                while path is not None:
                    answer.append(path[0])
                    path = path[1]
                return answer

            next_value = current_value   numbers[min_i]
            if next_value <= target:
                next_steps = current_steps   1
                heapq.heappush(
                    to_explore,
                    (
                       next_steps   math.ceil((target - next_value) / numbers[min_i]),
                       next_steps,
                       next_value,
                       min_i,
                       [numbers[min_i], path],
                    ))

            if min_i   1 < len(numbers):
                heapq.heappush(
                    to_explore,
                    (
                       current_steps   math.ceil((target - current_value) / numbers[min_i   1]),
                       current_steps,
                       current_value,
                       min_i   1,
                       path,
                    ))

print(can_make_sum(13, [1, 2, 3]))
print(can_make_sum(7, [1, 2]))
print(can_make_sum(100, [90, 9, 5]))
print(can_make_sum(30, [17, 15, 1]))
print(can_make_sum(26, [17, 15, 4]))
print(len(can_make_sum(1234567, [25, 15, 4])))
  • Related