Search code examples
pythonrecursionproof

Given two functions, find a threshold such that one is always bigger than the other


I've implemented the following two functions in Python:

def c(m, n):
    if m == 0:
        return 0
    elif m == 1 and n >= 0:
        return n**2+n+1
    elif m > 1 and n == 0:
        return c(m-1, 1)
    elif m > 1 and n > 0:
        return c(m-1, c(m, n-1))

def d(n):
    exp_num = n-1
    result = 2
    while exp_num != -1:
        result = result**2
        exp_num -= 1
    final_result = 2**result
    return final_result

Where the inputs m and n are both natural numbers. I'm asked to find an x, such that for all numbers y >= x, c(y,y) > d(y).

Some inputs and outputs:

c(1, 1) = 3

c(2, 2) = 183

d(1) = 16

d(2) = 65536

d(3) = 115792089237316195423570985008687907853269984665640564039457584007913129639936

As you can see d grows extremely faster than c. How can I approach this problem? Any help would be much appreciated.


Solution

  • The function c is a variant of Ackermann–Péter function.

    It claims to fame is that it is not Primitive Recursive which for purposes here means it quickly runs out of stack space in its computation as the numbers become very large.

    The problem is to find min x, such that c(y, y) > d(y), for y >= x.

    We believe this is c(3, 3) but it can't be computed. We did compute for 1 & 2:

    d(1) = 16, d(2) = 65536, d(3) = 115792089237316195423570985008687907853269984665640564039457584007913129639936

    c(1) = 3, c(2, 2) = 183, c(3, 3) = ?

    Because it's not primitively-recursive, c(3, 3) is difficult to compute (i.e. run out of stack space). However, rather than an exact number, we can obtain a lower-bound by by limiting the recursions in the function definition.

    This is done as follows.

    # Will use Memoization which eliminates repeated calculations in recursive functions
    class Memoize:
        def __init__(self, fn):
            self.fn = fn
            self.memo = {}
    
        def __call__(self, *args):
            if args not in self.memo:
                self.memo[args] = self.fn(*args)
            return self.memo[args]
    
    @Memoize
    def c(m, n, cnt = 0, MAX_RECURSION = 20):
        " Refactor function c to have a max recursion depth "
    
        if cnt > MAX_RECURSION:
            return 0   # We know the return value is always >= 0, but normally
                       # much larger.  By quitting early and returning zero we are
                       # ensuring our final computation will be smaller than it would
                       # otherwise if we had not limited the recurison depth
                       #
    
        if m == 0:
            return 0
        elif m == 1 and n >= 0:
            return n**2+n+1
        elif m > 1 and n == 0:
            return c(m-1, 1, cnt+1)
        elif m > 1 and n > 0:
            return c(m-1, c(m, n-1, cnt+1), cnt+1)
    
    def d(n):
        exp_num = n-1
        result = 2
        while exp_num != -1:
            result = result**2
            exp_num -= 1
        final_result = 2**result
        return final_result
    
    
    value_d = d(3)
    value_c = c(3, 3)
    
    print('Number of digits in d(3) is {}'.format(len(str(value_d))))
    #>>> Number of digits in d(3) is 78
    
    print('Number of digits in c(3, 3) is {}'.format(len(str(value_c))))
    #>>>Number of digits in c(3, 3) is 74176
    

    Thus, we see that c(3, 3) has ~1K more digits than d(3). It will be even more since we stopped the recursion early in computing c(3, 3).