If you end up having to convert your recursive functions to iterative approaches, This decorator/class may help ease the pain (depending on the nature of your recursions).
The decorator converts recursive calls in the function to iterative calls with an internal (unlimited) stack. Its main limitations are that it only supports simple recursions where the self-call is part of the return statement, and it requires a change in the way that return statement is expressed. Also, it will add significant overhead to the function calls which will impact performance.
the usage pattern is as follows:
@iterative # decorator to modify the function
def fn(...) # existing declaration
... # and implementation
if baseCase return x # no change to the base case
return fn(...),lambda x: ... # isolate alterations to the recursed
# result in a lambda
For example:
@iterative
def factorial(n):
if n<2: return 1
return factorial(n-1),lambda f:f*n
print(factorial(10)) # 3628800
print(len(str(factorial(10000)))) # 10000! has 35660 digits
@iterative
def sumSquares(n):
if n<2: return n
return sumSquares(n-1),lambda f:f+n**2
print(sumSquares(1000000)) # 333333833333500000
@iterative
def fibo(n):
if n<2: return n
return fibo(n-1),fibo(n-2),lambda n1,n2: n1+n2
print(fibo(10)) # 55
print(fibo(1000)) # will work, ... waiting for output ...
Here is the decorator / class:
from collections import deque
class iterative:
def __init__(self,func):
self.func = func # decorated function
self.stack = deque() # [(argCount, function to call)]
self.nested = False # nested call flag
self.recursed = False # call wants recursion
self.results = [] # result stack
def __call__(self,*args,**kwargs): # replaces original function
if self.nested:
self.recursed = True # defer recursive call to make
return lambda: self.func(*args,**kwargs)
else:
self.nested = True
self.stack.append((0,lambda: self.func(*args,**kwargs))) # top
while self.stack:
useArgs,fn = self.stack.pop() # unstack
if useArgs: # lambda on results
args = self.results[-useArgs:]
self.results = self.results[:-useArgs]
self.results.append(fn(*args))
else:
self.recursed = False # recursive call
result = fn()
if self.recursed: # more recursion -> stack
*calls,final = result
self.stack.append((len(calls),final))
self.stack.extend([(0,r) for r in reversed(calls)])
else:
self.results.append(result) # base cond. -> results
self.nested = False
return self.results.pop(-1) # final result (and reset)