Question

I can't figure out why the following code is makes fib run in linear rather than exponential time.

def memoize(obj):
    """Memoization decorator from PythonDecoratorLibrary. Ignores
    **kwargs"""

    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        if args not in cache:
            cache[args] = obj(*args, **kwargs)
        return cache[args]
    return memoizer

@memoize
def fib(n):
    return n if n in (0, 1) else fib(n-1) + fib(n-2)

For example, fib(100) doesn't completely blow up like I expected it to.

My understanding is that @memoize sets fib = memoize(fib). So when you call fib(100), seeing that 100 is not in the cache, it will call obj on 100. But obj is the original fib function, so shouldn't it take just as long (on the first evaluation) as if we didn't have memoization at all?

Was it helpful?

Solution 2

Names are resolved lexically. Just because you call a function named fib from within a function named fib, that doesn't mean it would necessarilly be the same fib.

A (highly inaccurate) demonstration of what's going on is this:

def fib(n):
    return n if n in (0, 1) else globals()['fib'](n-1) + globals()['fib'](n-2)

Since the decorater affects globals, you get the decorated fib at the time the recursive call occurs.

OTHER TIPS

obj in the decorator is indeed the wrapped, unmodified, non-memoized function. However, when said function tries to recurse, it looks up the global name fib, get the memoized wrapper function, and hence also causes the 99th, 98th, ... fibonacci number to be memoized along the way.

"But obj is the original fib function, so shouldn't it take just as long (on the first evaluation) as if we didn't have memoization at all?"

obj (in memoizer) is the indeed original fib function. The trick is that when fib calls itself recursively, it's calling memoize(fib).

def fib(n):
    return n if n in (0, 1) else wrapped(fib(n-1)) + wrapped(fib(n-2))

Where wrapped is the function generated by functools that calls memoize.memoizer. Kinda.

The recursive calls might end up being simple lookups in obj.cache (theoretically O(1)) which is what improves the perf significantly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top