自动记忆功能,支持递归功能

时间:2014-01-10 14:15:54

标签: c++ c++11 recursion memoization

博文Automatic Memoization in c++0x提供了生成现有函数的memoized版本的功能。之前在stackoverflow上讨论了博客文章和相关代码(例如What does this C++11 code do?),但是,这些解决方案都不能提供一个能够正确记忆递归函数的完全通用的memoizer。

当然,有一个通过使用类似的东西来改变递归调用的技巧(假设我们有一个memoizer,例如在已经到位的博客文章memoize中提供的那个):

std::function<int (int)> f;
int fib(int n) {
  if  (n < 2) return n;
  return f(n-1) + f(n-2);
}

int main(void) {
  f = memoize(std::function<int (int)>(fib));
}

但这更像是一种解决方法,而不是一个正确的解决方案,因为我们仍然需要访问我们想要记忆的功能。一个合适的解决方案应该能够完全记忆任何功能,包括某些库中定义的功能。然而,看起来,产生这样的解决方案是我无法实现的(假设有可能),因此我问:

  

真正的通用备忘录功能是否可行?    如何实现这样的壮举?

如果这是不可能的,至少有一种方法可以概括上述方法。有些东西(不编译且无效的C ++):

int fib(int n){
  if  (n < 2) return n;
  return this_func(n-1) + this_func(n-2);
}

其中this_func类似于类的this指针而是函数。 [修改>这可能仍会遇到this_func指针指向fib而不是记忆fib]的问题

3 个答案:

答案 0 :(得分:1)

由于缓存需要在函数调用之间共享,因此您必须将其作为参数传递,否则将共享它。分享它的一种方法是使用函数对象:

struct fib
{
    std::map<std::tuple<int>, int> cache;

    int operator()(int n)
    {
        if(n < 2) return n;

        auto memoize = [this](int p)
        {
            auto i = cache.find(p);
            if(i == cache.end()) i = cache.insert({p, (*this)(p)}).first;
            return i->second;
        };

        return memoize(n-1) + memoize(n-2);
    }
};

您可以将memoize部分分解出去。

还有一个临时生命周期的技巧,可以将memoized函数作为参数传递;像这样的东西:

struct recurse // possibly a class template
{
    std::function<int(int, recurse const&)> f; // possibly `mutable`

    template<class T>
    recurse(T&& p) : f( memoize(decltype(f){p}) )
    {}

    int operator()(int x) const
    {
        return f(x, *this);
    }
};

int fib(int n, recurse const& f);

int fib(int n, recurse const& f = {fib})
{
    if(n < 2) return n;
    return f(n-1) + f(n-2); // or `fib(n-1, f) + fib(n-2, f)`
}

然而,这需要更改memoize,因为recurse const&不能(也不应该)成为内部map的一部分。

<子> N.B。那些const&也可以&&进行终身扩展,但由于移动语义可能会造成混淆

答案 1 :(得分:0)

我的猜测是,在保持在已定义行为的范围内时,这是不可能的,因为没有办法抽象出递归调用。事实上,我想不出比你提供的全局变量版本更好的东西。

提供比全局变量更强大的抽象点的一个明显想法是添加函数作为第一个参数进行递归:

int fib(std::function<int (int)> rec, int n)
{
    if (n < 2) return n;
    return rec(n - 1) + rec(n - 2);
}

然后,您可以修改memoize函数,将memoized版本传递给第一个参数,事情就可以了。这个技巧通常用于实现(统一/无类型)函数语言(如scheme)中的相同目标。

然而,这种技巧依赖于"Y combinator"这个我认为在C ++中不存在的东西,因为它具有无限类型。

答案 2 :(得分:0)

以下可能有所帮助,但它仍然是一个黑客,它很丑......:

int fib(void* f, int n)
{
  if (n < 2) return n;
  auto this_func = *reinterpret_cast<std::function<int (void*, int)>*>(f);
  return this_func(f, n - 1) + this_func(f, n - 2);
}


int main(int argc, char *argv[])
{
    std::function<int (void*, int n)> fib1 = &fib;
    std::cout << fib1(reinterpret_cast<void*>(&fib1), 5) << std::endl;

    auto f = memoize(fib1);
    std::cout << f(reinterpret_cast<void*>(&f), 5) << std::endl;

    return 0;
}

我使用void*,因为正确的签名是递归的: - /