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])))