Optimization 如何编写通用的memoize函数?

Optimization 如何编写通用的memoize函数?,optimization,recursion,lua,closures,memoization,Optimization,Recursion,Lua,Closures,Memoization,我正在编写一个查找函数,而编写它的自然方式是递归的: function triangle (x) if x == 0 then return 0 end return x+triangle(x-1) end 但尝试计算前100000个三角形数失败,过了一段时间堆栈溢出。这是一个理想的函数,但我想要一个解决方案,将记忆我传递给它的任何函数 function memoize (f) local cache = {} return function (x)

我正在编写一个查找函数,而编写它的自然方式是递归的:

function triangle (x)
   if x == 0 then return 0 end
   return x+triangle(x-1)
end
但尝试计算前100000个三角形数失败,过了一段时间堆栈溢出。这是一个理想的函数,但我想要一个解决方案,将记忆我传递给它的任何函数

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);
请注意,为了避免堆栈溢出,仍然需要为triangle添加种子。

这里是一个通用的C#3.0实现,如果可能的话:

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;
                };
    }
}
公共静态类备忘录
{
公共静态函数备忘录(此函数)
{
var cache=newdictionary();
var nullCache=default(TResult);
var isNullCacheSet=false;
返回参数=>
{
结果值;
if(参数==null&&isNullCacheSet)
{
返回空缓存;
}
if(参数==null)
{
nullCache=函数(参数);
isNullCacheSet=true;
返回空缓存;
}
if(cache.TryGetValue(参数,输出值))
{
返回值;
}
值=函数(参数);
cache.Add(参数、值);
返回值;
};
}
}
(引自a)

在Scala中(未经测试):

请注意,这只适用于arity 1的函数,但使用curry可以使其工作。更微妙的问题是
memoize(f)!=记忆(f)
任何函数的
f
。解决这个问题的一个非常狡猾的方法如下:

val correctMem = memoize(memoize _)

我认为这不会编译,但它确实说明了这一点。

您对原始问题也提出了错误的问题;)

对于这种情况,这是一种更好的方法:

三角形(n)=n*(n-1)/2


此外,假设公式中没有如此简洁的解决方案,那么在这里,记忆仍然是一种糟糕的方法。在这种情况下,最好只编写一个简单的循环。请参阅以获得更全面的讨论。

更新:评论者指出,记忆化是优化递归的好方法。诚然,我以前没有考虑过这一点,因为我通常在一种语言(C#)中工作,在这种语言中,广义记忆的构建并不是那么简单。考虑到这一点,在下面的帖子上发表意见

我认为这是一个问题,但记忆通常不是解决任何堆栈溢出问题的方法

堆栈溢出通常是由于递归深入到平台无法处理的程度而导致的。语言有时支持“”,它重新使用当前调用的上下文,而不是为递归调用创建新上下文。但是很多主流语言/平台不支持这一点。例如,C#对尾部递归没有固有的支持。64位版本的.NET JITter可以将其应用为IL级别的优化,如果您需要支持32位平台,这几乎是无用的


如果您的语言不支持尾部递归,那么避免堆栈溢出的最佳选择是转换为显式循环(虽然不太优雅,但有时是必要的),或者找到一个非迭代算法,如为这个问题提供的Luke。

扩展了这个想法,还可以使用两个输入参数记忆函数:

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
请注意,参数顺序在缓存算法中很重要,因此,如果要记忆的函数中的参数顺序不重要,则在检查缓存之前对参数进行排序会增加缓存命中的几率

但重要的是要注意,有些功能无法进行有益的记忆。我写了memoize2,看看寻找最大公约数的递归算法是否可以加快

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

事实证明,gcd对记忆效果不好。它所做的计算远比缓存算法便宜。即使是大数字,它也会很快终止。一段时间后,缓存变得非常大。此算法可能已经尽可能快了。

我打赌类似的方法应该适用于Lua中的变量参数列表:

local function varg_tostring(...)
    local s = select(1, ...)
    for n = 2, select('#', ...) do
        s = s..","..select(n,...)
    end
    return s
end

local function memoize(f)
    local cache = {}
    return function (...)
        local al = varg_tostring(...)
        if cache[al] then
            return cache[al]
        else
            local y = f(...)
            cache[al] = y
            return y
        end
    end
end

您可能还可以使用带有_tostring的元表做一些聪明的事情,这样就可以使用tostring()转换参数列表。哦,可能性。

< P>在不同语言的回忆录中,我想用非语言改变的C++例子来回应@ OnByOn.LeVielnal.com。

首先,单参数函数的记忆器:

template <class Result, class Arg, class ResultStore = std::map<Arg, Result> >
class memoizer1{
public:
    template <class F>
    const Result& operator()(F f, const Arg& a){
        typename ResultStore::const_iterator it = memo_.find(a);
        if(it == memo_.end()) {
            it = memo_.insert(make_pair(a, f(a))).first;
        }
        return it->second;
    }
private:
    ResultStore memo_;
};
还有司机,回忆一下

int fib(int n) {
    static memoizer1<int,int> memo;
    return memo(fib_, n);
}
intfib(intn){
静态备忘录1备忘录;
返回备忘录(fib_un);
}
在codepad.org上。测量调用数以验证正确性。(此处插入单元测试…)


这只记忆一个输入函数。概括多个参数或不同的参数,留给读者作为练习

Mathematica有一种特别巧妙的方法来进行记忆,这依赖于散列和函数调用使用相同语法的事实:

triangle[0] = 0;
triangle[x_] := triangle[x] = x + triangle[x-1]
就这样。它之所以有效,是因为模式匹配函数调用的规则总是在更一般的定义之前使用更具体的定义

当然,正如已经指出的,这个例子有一个封闭形式的解决方案:
三角形[x_]:=x*(x+1)/2
。斐波那契数是一个经典的例子,说明了添加记忆功能会带来巨大的加速:

fib[0] = 1;
fib[1] = 1;
fib[n_] := fib[n] = fib[n-1] + fib[n-2]
尽管这也有一个封闭形式的等价物,尽管更混乱:

我不同意有人认为这不适合用于回忆录,因为你可以“只使用一个循环”。记忆的要点是任何重复的函数调用都是O(1)次。这比O(n)好多了。事实上,你甚至可以编造一个场景
triangle[0] = 0;
triangle[x_] := triangle[x] = x + triangle[x-1]
fib[0] = 1;
fib[1] = 1;
fib[n_] := fib[n] = fib[n-1] + fib[n-2]
# This is the documentation for Memoize 1.01
use Memoize;
memoize('slow_function');
slow_function(arguments);    # Is faster than it was before
local function m(f)
  local t = { }
  local function mf(x, ...) -- memoized f
    assert(x ~= nil, 'nil passed to memoized function')
    if select('#', ...) > 0 then
      t[x] = t[x] or m(function(...) return f(x, ...) end)
      return t[x](...)
    else
      t[x] = t[x] or f(x)
      assert(t[x] ~= nil, 'memoized function returns nil')
      return t[x]
    end
  end
  return mf
end
local globalCache = {}

local function getFromCache(cache, args)
  local node = cache
  for i=1, #args do
    if not node.children then return {} end
    node = node.children[args[i]]
    if not node then return {} end
  end
  return node.results
end

local function insertInCache(cache, args, results)
  local arg
  local node = cache
  for i=1, #args do
    arg = args[i]
    node.children = node.children or {}
    node.children[arg] = node.children[arg] or {}
    node = node.children[arg]
  end
  node.results = results
end


-- public function

local function memoize(f)
  globalCache[f] = { results = {} }
  return function (...)
    local results = getFromCache( globalCache[f], {...} )

    if #results == 0 then
      results = { f(...) }
      insertInCache(globalCache[f], {...}, results)
    end

    return unpack(results)
  end
end

return memoize
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;
    }        
}
var memoized_fib = Helpers.Memoize<int, int>((n,fib) => n > 1 ? fib(n - 1) + fib(n - 2) : n);
Console.WriteLine(memoized_fib(40));
public int triangle(final int n){
   return n * (n - 1) / 2;
}
int[] memo = new int[n+1];
int sum = 0;
for(int i = 0; i <= n; ++i)
{
  sum+=i;
  memo[i] = sum;
}
return memo[n];