【问题标题】:Memoization functor wrapper in c++C ++中的记忆函子包装器
【发布时间】:2014-08-29 12:44:25
【问题描述】:

这是我为函数编写的通用记忆包装器。它使用tuplehash

template<typename R, typename... Args>
class memofunc{
    typedef R (*func)(Args...);
    func fun_;
    unordered_map<tuple<Args...>, R, tuplehash::hash<tuple<Args...> > > map_;
public:
    memofunc(func fu):fun_(fu){}
    R operator()(Args&&... args){
    auto key = make_tuple(std::forward<Args>(args)...);
    auto q = map_.find(key);
    if(q == map_.end()){
        R res = fun_(std::forward<Args>(args)...);
        map_.insert({key,res});
        return res;
    }else{
        return q->second;
    }
    }
};

斐波那契数的用法示例。

long long fibo(long long x){
    static memofunc<long long, long long> memf(fibo);
    // try to replace fibo with this new fibo but doesn't work, why?
    // function<long long(long long)> fibo = memf; 

    if(x <= 2) return 1;
    // this works but involves changing the original code.
    // how to write code such that I dont need to manually add this code in?
    return memf(x-1) + memf(x-2); 
    // old code
    // return fibo(x-1) + fibo(x-2);
}

问题是,理想情况下,我可以在递归函数的开头添加几行并完成记忆。但是简单的替换是行不通的,这就是我卡住的地方。

【问题讨论】:

  • 你有什么问题?
  • 我曾经写过generic memoization wrapper
  • 我认为OP希望在不修改memoized功能的情况下提供memoization。而且我认为递归函数是不可能的。
  • 旁白:将template&lt;typename R, typename... Args&gt; class memofunc{ 替换为template&lt;typename Sig&gt; class memofunc; template&lt;typename R, typename...Args&gt; class memofunc&lt;R(Args)&gt; { -- 然后将使用更改为memofunc&lt;long long(long long)&gt;

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


【解决方案1】:

您的问题似乎是您在每次函数调用时都制作了一份记忆器的本地副本,然后将其销毁。

这是一个简单的单参数版本的记忆器,它似乎可以工作:

#include <iostream>
#include <functional>
#include <unordered_map>

template<typename Sig, typename F=Sig* >
struct memoize_t;
template<typename R, typename Arg, typename F>
struct memoize_t<R(Arg), F> {
  F f;
  mutable std::unordered_map< Arg, R > results;
  template<typename... Args>
  R operator()( Args&&... args ) const {
    Arg a{ std::forward<Args>(args)... }; // in tuple version, std::tuple<...> a
    auto it = results.find(a);
    if (it != results.end())
      return it->second;
    R retval = f(a); // in tuple version, use a tuple-to-arg invoker
    results.emplace( std::forward<Arg>(a), retval ); // not sure what to do here in tuple version
    return retval;
  }
};

template<typename F>
memoize_t<F> memoize( F* func ) {
  return {func};
}

int foo(int x) {
  static auto mem = memoize(foo);
  auto&& foo = mem;
  std::cout << "processing...\n";
  if (x <= 0) return foo(x+2)-foo(x+1); // bwahaha
  if (x <= 2) return 1;
  return foo(x-1) + foo(x-2);;
}
int main() {
  std::cout << foo(10) << "\n";
}

live example

请注意,foo(10) 只调用了 10 次 foo

这也承认:

#define CAT2(A,B,C) A##B##C
#define CAT(A,B,C) CAT2(A,B,C)
#define MEMOIZE(F) \
  static auto CAT( memoize_static_, __LINE__, F ) = memoize(F); \
  auto&& F = CAT( memoize_static_, __LINE__, F )

int foo(int x) {
  MEMOIZE(foo);
  std::cout << "processing...\n";
  if (x <= 0) return 0;
  if (x <= 2) return 1;
  return foo(x-1) + foo(x-2);;
}

对于喜欢宏的人来说。

3 步版本可能会更好。

首先,前导函数和 memoizer 包装器的前向声明。

其次,在函数内部,函数名的别名,所以递归调用使用记忆函数。

第三,在函数声明之后,函数名的别名,所以外部调用使用memoized版本。

上面的代码只记录递归调用,而不是初始调用。

【讨论】:

  • 您的operator() 模板不能导致多次转化吗?例如。如果参数之一是 std::string 但参数是字符串文字。
  • @dyp 是的。对于真实版本,我会完美转发到本地 std::tuple,然后是 emplace。有趣的是,这留下了一个奇怪的漏洞——emplace 查找最终发生在调用f 之后,这意味着f 可以修改参数并更改缓存结果的位置。上面的代码可以避免这种情况,但它使用operator= 而不是直接构造R。有没有办法解决这个问题?
  • 为什么是auto&amp;&amp; foo = mem;
  • @BЈовић 因为没有那条线,foofoo 内部调用会调用foo,而不是mem。我们希望记住递归调用。这会创建一个名为 foo 的别名,当 foo 中的代码调用 foo(x-1) 时,它会通过别名。
  • retval 将被返回,一方面,所以moveing 似乎不礼貌。 ;) 我们可以返回results[a],但我们已经在retval 中有它。
猜你喜欢
  • 1970-01-01
  • 2022-12-17
  • 2011-08-29
  • 1970-01-01
  • 2010-11-07
  • 2017-11-18
  • 2015-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多