How do I write a generic memoize function?

后端 未结 15 1628
情话喂你
情话喂你 2020-12-09 05:25

I\'m writing a function to find triangle numbers and the natural way to write it is recursively:

function triangle (x)
   if x == 0 then return 0 end
   retu         


        
相关标签:
15条回答
  • 2020-12-09 06:12
    function memoize (f)
       local cache = {}
       return function (x)
                 if cache[x] then
                    return cache[x]
                 else
                    local y = f(x)
                    cache[x] = y
                    return y
                 end
              end
    end
    
    triangle = memoize(triangle);
    

    Note that to avoid a stack overflow, triangle would still need to be seeded.

    0 讨论(0)
  • 2020-12-09 06:16

    Update: Commenters have pointed out that memoization is a good way to optimize recursion. Admittedly, I hadn't considered this before, since I generally work in a language (C#) where generalized memoization isn't so trivial to build. Take the post below with that grain of salt in mind.

    I think Luke likely has the most appropriate solution to this problem, but memoization is not generally the solution to any issue of stack overflow.

    Stack overflow usually is caused by recursion going deeper than the platform can handle. Languages sometimes support "tail recursion", which re-uses the context of the current call, rather than creating a new context for the recursive call. But a lot of mainstream languages/platforms don't support this. C# has no inherent support for tail-recursion, for example. The 64-bit version of the .NET JITter can apply it as an optimization at the IL level, which is all but useless if you need to support 32-bit platforms.

    If your language doesn't support tail recursion, your best option for avoiding stack overflows is either to convert to an explicit loop (much less elegant, but sometimes necessary), or find a non-iterative algorithm such as Luke provided for this problem.

    0 讨论(0)
  • 2020-12-09 06:17

    Here is a generic C# 3.0 implementation, if it could help :

    public static class Memoization
    {
        public static Func<T, TResult> Memoize<T, TResult>(this Func<T, TResult> function)
        {
            var cache = new Dictionary<T, TResult>();
            var nullCache = default(TResult);
            var isNullCacheSet = false;
            return  parameter =>
                    {
                        TResult value;
    
                        if (parameter == null && isNullCacheSet)
                        {
                            return nullCache;
                        }
    
                        if (parameter == null)
                        {
                            nullCache = function(parameter);
                            isNullCacheSet = true;
                            return nullCache;
                        }
    
                        if (cache.TryGetValue(parameter, out value))
                        {
                            return value;
                        }
    
                        value = function(parameter);
                        cache.Add(parameter, value);
                        return value;
                    };
        }
    }
    

    (Quoted from a french blog article)

    0 讨论(0)
  • 2020-12-09 06:18

    Extending the idea, it's also possible to memoize functions with two input parameters:

    function memoize2 (f)
       local cache = {}
       return function (x, y)
                 if cache[x..','..y] then
                    return cache[x..','..y]
                 else
                    local z = f(x,y)
                    cache[x..','..y] = z
                    return z
                 end
              end
    end
    

    Notice that parameter order matters in the caching algorithm, so if parameter order doesn't matter in the functions to be memoized the odds of getting a cache hit would be increased by sorting the parameters before checking the cache.

    But it's important to note that some functions can't be profitably memoized. I wrote memoize2 to see if the recursive Euclidean algorithm for finding the greatest common divisor could be sped up.

    function gcd (a, b) 
       if b == 0 then return a end
       return gcd(b, a%b)
    end
    

    As it turns out, gcd doesn't respond well to memoization. The calculation it does is far less expensive than the caching algorithm. Ever for large numbers, it terminates fairly quickly. After a while, the cache grows very large. This algorithm is probably as fast as it can be.

    0 讨论(0)
  • 2020-12-09 06:20

    Mathematica has a particularly slick way to do memoization, relying on the fact that hashes and function calls use the same syntax:

    triangle[0] = 0;
    triangle[x_] := triangle[x] = x + triangle[x-1]
    

    That's it. It works because the rules for pattern-matching function calls are such that it always uses a more specific definition before a more general definition.

    Of course, as has been pointed out, this example has a closed-form solution: triangle[x_] := x*(x+1)/2. Fibonacci numbers are the classic example of how adding memoization gives a drastic speedup:

    fib[0] = 1;
    fib[1] = 1;
    fib[n_] := fib[n] = fib[n-1] + fib[n-2]
    

    Although that too has a closed-form equivalent, albeit messier: http://mathworld.wolfram.com/FibonacciNumber.html

    I disagree with the person who suggested this was inappropriate for memoization because you could "just use a loop". The point of memoization is that any repeat function calls are O(1) time. That's a lot better than O(n). In fact, you could even concoct a scenario where the memoized implementation has better performance than the closed-form implementation!

    0 讨论(0)
  • 2020-12-09 06:20

    In C# 3.0 - for recursive functions, you can do something like:

    public static class Helpers
    {
        public static Func<A, R> Memoize<A, R>(this Func<A, Func<A,R>,  R> f)
        {
            var map = new Dictionary<A, R>();
            Func<A, R> self = null;
            self = (a) =>
            {
                R value;
                if (map.TryGetValue(a, out value))
                    return value;
                value = f(a, self);
                map.Add(a, value);
                return value;
            };
            return self;
        }        
    }
    

    Then you can create a memoized Fibonacci function like this:

    var memoized_fib = Helpers.Memoize<int, int>((n,fib) => n > 1 ? fib(n - 1) + fib(n - 2) : n);
    Console.WriteLine(memoized_fib(40));
    
    0 讨论(0)
提交回复
热议问题