I'm trying to write a function that finds all possible combinations of coins that yield a specified amount, for example it calculates all possible way to give change for the amount 2 British pounds from the list of denominations 1p, 2p, 5p,10p,20p,50p,1pound,2pound. I'm stuck with this and can't find the right solution.
I want the main function to be recursive, because I want to understand recursion better. The algorithm must backtrack, if the combination found at some moment exceeds the amount which is to be matched the program should return to previous steps and start from different point.
Thus far I've written a normal (not recursive) function that computes all possible combinations of coins in a given country if each coin is used only once (this is fairly straightforward). I am not trying to find the right combination for a given sum yet, just all possible combinations of coins.
def calcCoins(coins):
"""
returns all possible combinations of coins, when called with
[1,2,5,10,20,50,100,200] returns a list of 126 Counters containing
for instance Counter{1:1}, Counter{1:1,2:1,5:1}, Counter {50:1,100:1} etc
"""
i,combs = 1, []
while i < len(coins):
for x in combinations(coins,i):
combs.append(Counter(x))
i += 1
return combs
Now I have a clumsy recursive function that accepts a combination and desired amount as arguments and returns all possible ways in which a change equal to this amount can be given.
def findSum(comb,goal,rightOnes):
if rightOnes == None:
rightOnes = []
if sum(comb.elements()) == goal:
comb_ = Counter(comb)
if comb_ in rightOnes:
# probably a cycle, return combinations gathered and exit
return rightOnes
rightOnes.append(comb_)
elif sum(comb.elements()) > goal:
#this is meant to be backtracking
return False
for k in comb:
comb[k] += 1
if findSum(comb,goal,rightOnes) != False:
return findSum(comb,goal,rightOnes)
else:
comb[k] = 1
return rightOnes
The function runs and returns correctly for very small combinations: e.g. for
test2 = Counter({10: 1, 20: 1})
findSum(test2,200,[])
It returns:
[Counter({10: 18, 20: 1}), Counter({10: 16, 20: 2}),
Counter({10: 14, 20: 3}), Counter({10: 12, 20: 4}),
Counter({10: 10, 20: 5}), Counter({10: 8, 20: 6}),
Counter({20: 7, 10: 6}), Counter({20: 8, 10: 4}),
Counter({20: 9, 10: 2})]
But for larger ones, such as
test3 = Counter({1: 1, 2: 1, 10: 1})
test4 = Counter({1: 1, 2: 1, 100: 1, 10: 1})
it exceeds the limit of recursion. It runs fine until some moment, prints out partial results but then at some point it exceeds maximum recursion depth.
What are the mistakes I'm making which cuase this function to run amok? Is it something with my implementation of backtracking? Am I omitting some case? How to optimize this function so that it does not exceed maxim recursion depth?
Thanks in advance!
EDIT: Here is the traceback:
if findSum(comb,goal,rightOnes) != False:
File "C:\playground\python\problem31.py", line 105, in findSum
if sum(comb.elements()) == goal:
File "C:\Python27\lib\collections.py", line 459, in elements
return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
RuntimeError: maximum recursion depth exceeded while calling a Python object
and the last partial result, just before the break of the function (called with test3)
[Counter({1: 163, 2: 1, 20: 1, 10: 1, 5: 1}), Counter({1: 161, 2: 2, 20: 1, 10: 1, 5: 1}),
Counter({1: 159, 2: 3, 20: 1, 10: 1, 5: 1}), Counter({1: 157, 2: 4, 20: 1, 10: 1, 5: 1}),
Counter({1: 155, 2: 5, 20: 1, 10: 1, 5: 1}), Counter({1: 153, 2: 6, 20: 1, 10: 1, 5: 1})]
The “maximum recursion depth exceeded in comparison” error is raised when you try to execute a function that exceeds Python's built in recursion limit. You can fix this error by rewriting your program to use an iterative approach or by increasing the recursion limit in Python.
The maximal number of nested calls (including the first one) is called recursion depth. In our case, it will be exactly n . The maximal recursion depth is limited by JavaScript engine. We can rely on it being 10000, some engines allow more, but 100000 is probably out of limit for the majority of them.
In backtracking, we use recursion to explore all the possibilities until we get the best result for the problem.
The Python interpreter limits the recursion limit so that infinite recursions are avoided. The “sys” module in Python provides a function called setrecursionlimit() to modify the recursion limit in Python. It takes one parameter, the value of the new recursion limit. By default, this value is usually 10^3.
First of all, as the first answer to this question shows, because of the semantics of Python as a language, recursion isn't a particularly efficient paradigm. However, as is pointed out there, it is possible to use sys.setrecursionlimit(2000)
. (Or however much you need) I want to stress that this is the "lazy" solution, I strongly recommend using your first (non-recursive) version instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With