Skip to content

Instantly share code, notes, and snippets.

@cad0p
Created April 11, 2021 23:11
Show Gist options
  • Save cad0p/a868bad021fb7ec1640746199d551a94 to your computer and use it in GitHub Desktop.
Save cad0p/a868bad021fb7ec1640746199d551a94 to your computer and use it in GitHub Desktop.
Python Decorator to avoid recursion limit on recursive functions

I built a decorator with the memoize idea: it will take care of your system recursion limit, and avoid it by splitting calculation in batches that are as large as the system can offer. It will also of course remember all the values for easy retrieval

Short working version:

import sys
from math import floor, ceil

def memoize(f):
    memo = [None]
    limit = sys.getrecursionlimit()
    limit = floor(limit/2 - 3)
    limit_list = [None] * limit

    def handler(x):
        reps = ceil((x-max(limit,len(memo)-1))/limit)
        for i in range(reps):
            if memo[len(memo)-1] is not None or len(memo)-1 == 0:
                memo.extend(limit_list)
            x_i = len(memo) - 1
            handler(x_i)
        if x - (len(memo)-1) > 0:
            memo.extend(limit_list)
        if memo[x] is None:
            memo[x] = f(x)
        return memo[x]
    return handler

You can then run it:

@memoize
def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)


print(fib(1000))

Result: 42246963333923048787067256023414..

Explained version:

import sys
from math import floor, ceil

def memoize(f):
    memo = [None]
    limit = sys.getrecursionlimit()
    # now divide it because we are adding double layer here
    # a function of a function..
    limit = floor(limit/2 - 3)
    limit_list = [None] * limit
    print(f'limit: {limit}')

    def handler(x):
        reps = ceil((x-max(limit,len(memo)-1))/limit)
        for i in range(reps):
            print(f'rep {i+1} of {reps}')
            print(f'x: {x}\nmemo: {len(memo)-1}')
            if memo[len(memo)-1] is not None or len(memo)-1 == 0:
                # only do this if the list is not to be checked
                # for example a previous calculation has given
                # a calculation up to 50, but the limit is 70
                # so the values from 50 to 70 are [None]
                # so we need not to extend the list, but to check
                # the elements from 0 to 70 (the last batch)
                memo.extend(limit_list)
            print(memo)
            # len(memo) changes each time,
            # so each iteration the recursion limit gets added
            x_i = len(memo) - 1
            print(f'x_{i+1}: {x_i}')
            handler(x_i)
            if i+1 == reps:
                print(f'Final step: from {x_i} to {x}')

        if x - (len(memo)-1) > 0:
            memo.extend(limit_list)

        if memo[x] is None:
            memo[x] = f(x)
        return memo[x]

    return handler

It even uses memory to avoid going back when calling it again!

print(fib(1000))
print(fib(2000))

This is the output:

limit: 497
rep 1 of 2
x: 1000
memo: 0
x_1: 497
rep 2 of 2
x: 1000
memo: 497
x_2: 994
Final step: from 994 to 1000
434665576869374564356..
rep 1 of 2
x: 2000
memo: 1491
x_1: 1491
rep 2 of 2
x: 2000
memo: 1491
x_2: 1988
Final step: from 1988 to 2000
422469633339230487870672560..
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment