Home > Mobile >  Get a list of numbers that adds up to a number "n" (using repetition of number) - how woul
Get a list of numbers that adds up to a number "n" (using repetition of number) - how woul

Time:02-10

def getList(n, input_list, caching_list):

The function should return a list that adds up to a number "n" using numbers from the input_list, given that numbers can be repeated. Looking to use recursion.

current code:

def getList(n, input_list, caching_list):
  if n == 0 :
    return caching_list
  if n < 0 or len(input_list) == 0:
    return []

  for i in input_list:
    if find_sum(n-i,input_list,caching_list) == caching_list:
        caching_list.append(i)
        return caching_list
  return []

example n = 13, input_list= [2,3,5] should result in [2,2,2,2,2,3] or [5,5,2] or anything that adds to 13.

Doing this with using a number just 1 time is easy, but how would one consider the case of using same number multiple times?

CodePudding user response:

With recursion, this can be solved using a depth-first search (DFS) algorithm, but it can throw RecursionError: maximum recursion depth exceeded in comparison

def find(n2, plist):
    result = []
    def find2(n):
        if n == 0:
            return True
        if n < 0:
            return False
        for p in plist:
            if find2(n-p):
                result.append(p)
                return True
    find2(n2)
    return result
            
print(find(17, [2,3,5]))       # [3, 2, 2, 2, 2, 2, 2, 2]
print(find(7, [3,5]))          # []
print(find(77777, [3,5,7,11])) # recursion error

To get rid of the recursion error, recursive DFS can be rewritten into iterative DFS.

CodePudding user response:

I find itertools can be very handy for stuff like this. I am sure there are better ways to do it, but this might get you want you need:

import itertools

def find_sum(n, p_list):
    max_len = n // min(p_list)
    min_len = n // max(p_list)

    for i in range(min_len, max_len 1):
        I = itertools.combinations_with_replacement(p_list, i)
        for j in I:
            if(sum(j) == n):
                return(list(j))
    return([])

print(find_sum(17,[2,3,5]))
# [2, 5, 5, 5]
print(find_sum(7, [3, 5]))
# []

You could easily alter the code to give you all combinations.


def find_sum(n, p_list):
    max_len = n // min(p_list)
    min_len = n // max(p_list)
    answers = []
    for i in range(min_len, max_len 1):
        I = itertools.combinations_with_replacement(p_list, i)
        for j in I:
            if(sum(j) == n):
                answers.append(list(j))
    return(answers)
find_sum(17,[2,3,5])
#[[2, 5, 5, 5], [2, 2, 3, 5, 5], [3, 3, 3, 3, 5], [2, 2, 2, 3, 3, 5], [2, 3, 3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 5], [2, 2, 2, 2, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2, 3]]

Regarding @dantebarba's comment, I haven't really looked to see how it will scale for large problems and there may be more efficient approaches.

CodePudding user response:

These 3 lines of code should raise some red flags:

if find_sum(n-i,p,p_list,sum_list) == sum_list:
    sum_list.append(i)
    return sum_list

You have a recursive function storing its results in sum_list, modifying it, and also depending on its values for equality checks. This makes your program extremely difficult to reason about. Does the behavior change if you swap the arguments around the equality sign? I have no idea.

In general, your recursive programs should try to avoid passing their result as a parameter. Since that if statement is really asking whether we can make a certain sum, why not break that into a separate function?

def can_sum(n, p_list, cache={}):
    if n in cache:
        return cache[n]
    if n == 0:
        return True
    if n < 0:
        return False
    return any(can_sum(n-p, p_list) for p in p_list)

You want to use some kind of caching (otherwise, your program is exponential-time), and functools.cache is preferable if you can use outside libraries, but this will do the job.

How should your find_sum function return results without passing the result as a parameter? Just add your value to the end, exactly like your code did:

def find_sum(n, p_list):
    for p in p_list:
        if can_sum(n-p, p_list):
            return find_sum(n-p, p_list)   [p]
    return []
  • Related