在 C++ 中进行记忆的正确方法是混合 Y-combinator。
您的基本功能需要修改。它不是直接调用自身,而是将对自身的模板化引用作为其第一个参数(或std::function<Same_Signature>
递归作为其第一个参数)。
我们从Y-combinator开始。然后我们在 上添加一个缓存operator()
并将其重命名为memoizer
,并给它一个固定的签名(用于表)。
唯一剩下的就是写一个tuple_hash<template<class...>class Hash>
对元组进行哈希处理的方法。
可以被记忆的函数的类型是(((Args...)->R), Args...) -> R
,这使得记忆器成为类型( (((Args...) -> R), Args...) -> R ) -> ((Args...) -> R)
。使用 Y 组合器来产生“传统”递归实现也很有用。
请注意,如果函数 memoized 在调用期间修改了它的参数,则 memoizer 会将结果缓存在错误的位置。
struct wrap {};
template<class Sig, class F, template<class...>class Hash=std::hash>
struct memoizer;
template<class R, class...Args, class F, template<class...>class Hash>
struct memoizer<R(Args...), F, Hash> {
using base_type = F;
private:
F base;
mutable std::unordered_map< std::tuple<std::decay_t<Args>...>, R, tuple_hash<Hash> > cache;
public:
template<class... Ts>
R operator()(Ts&&... ts) const
{
auto args = std::make_tuple(ts...);
auto it = cache.find( args );
if (it != cache.end())
return it->second;
auto&& retval = base(*this, std::forward<Ts>(ts)...);
cache.emplace( std::move(args), retval );
return decltype(retval)(retval);
}
template<class... Ts>
R operator()(Ts&&... ts)
{
auto args = std::tie(ts...);
auto it = cache.find( args );
if (it != cache.end())
return it->second;
auto&& retval = base(*this, std::forward<Ts>(ts)...);
cache.emplace( std::move(args), retval );
return decltype(retval)(retval);
}
memoizer(memoizer const&)=default;
memoizer(memoizer&&)=default;
memoizer& operator=(memoizer const&)=default;
memoizer& operator=(memoizer&&)=default;
memoizer() = delete;
template<typename L>
memoizer( wrap, L&& f ):
base( std::forward<L>(f) )
{}
};
template<class Sig, class F>
memoizer<Sig, std::decay_t<F>> memoize( F&& f ) { return {wrap{}, std::forward<F>(f)}; }
基于此 SO 帖子的硬编码哈希函数的实时示例。
auto fib = memoize<size_t(size_t)>(
[](auto&& fib, size_t i)->size_t{
if (i<=1) return 1;
return fib(i-1)+fib(i-2);
}
);