在C++11中编写通用记忆函数
Writing Universal memoization function in C++11
正在寻找一种实现通用通用内存化函数的方法,该函数将接受一个函数并返回该函数的内存化版本?
在python中寻找类似@moom(来自Norving的网站)的装饰器。
def memo(f):
table = {}
def fmemo(*args):
if args not in table:
table[args] = f(*args)
return table[args]
fmemo.memo = table
return fmemo
更一般地说,有没有一种方法可以在C++中表达通用和可重用的装饰器,可能使用C++11的新特性?
返回lambda:的紧凑型
template <typename R, typename... Args>
std::function<R (Args...)> memo(R (*fn)(Args...)) {
std::map<std::tuple<Args...>, R> table;
return [fn, table](Args... args) mutable -> R {
auto argt = std::make_tuple(args...);
auto memoized = table.find(argt);
if(memoized == table.end()) {
auto result = fn(args...);
table[argt] = result;
return result;
} else {
return memoized->second;
}
};
}
在C++14中,可以使用广义返回类型推导来避免返回std::function
所带来的额外间接性。
让这一点变得完全通用,允许传递任意函数对象而不首先将它们封装在std::function
中,留给读者练习。
在C++中进行记忆的正确方法是在.中混合Y组合子
您的基本函数需要修改。它不直接调用自己,而是将对自己的模板化引用作为第一个参数(或者,std::function<Same_Signature>
递归作为第一个自变量)。
我们从Y组合子开始。然后,我们在operator()
上添加一个缓存,并将其重命名为memoizer
,并为其提供一个固定签名(用于表)。
剩下的就是编写一个tuple_hash<template<class...>class Hash>
,对元组进行散列。
可以记忆的函数类型是(((Args...)->R), Args...) -> R
,这使得记忆器类型为( (((Args...) -> R), Args...) -> R ) -> ((Args...) -> R)
。有一个Y组合子来产生一个"传统"递归实现也很有用。
请注意,如果函数memoryized在调用过程中修改了其参数,则memoryizer会将结果缓存在错误的位置。
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);
}
);
我也遇到了同样的问题。我创建的宏也支持递归(在递归代码中稍作修改)。这是:
#include <map>
#include <tuple>
#define MEMOIZATOR(N, R, ...)
R _ ## N (__VA_ARGS__);
std::map<std::tuple<__VA_ARGS__>, R> _memo_ ## N;
template <typename ... Args>
R N (Args ... args) {
auto& _memo = _memo_ ## N;
auto result = _memo.find(std::make_tuple(args...));
if (result != _memo.end()) {
return result->second;
}
else {
auto result = _ ## N (args...);
_memo[std::make_tuple(args...)] = result;
return result;
}
}
用法非常简单:
MEMOIZATOR(fibonacci, long int, int);
long int _fibonacci(int n) { // note the leading underscore
// this makes recursive function to go through wrapper
if (n == 1 or n == 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(40) // uses memoizator so it works in linear time
// (try it with and without memoizator)
在行动中看到它:http://ideone.com/C3JEUT:)
尽管@KerrekSB发布了另一个答案的链接,但我认为我也会把我的答案扔到戒指上(它可能比链接的答案稍微复杂一些,尽管本质上非常相似):
#include <functional>
#include <map>
#include <tuple>
#include <utility>
/*! brief A template functor class that can be utilized to memoize any
* given function taking any number of arguments.
*/
template <typename R, typename... Args>
struct memoize_wrapper
{
private:
std::map<std::tuple<Args...>, R> memo_;
std::function<R(Args...)> func_;
public:
/*! brief Auto memoization constructor.
*
* param func an the std::function to be memoized.
*/
memoize_wrapper(std::function<R(Args...)> func)
: func_(func)
{ }
/*! brief Memoization functor implementation.
*
* param a Argument values that match the argument types for the
* (previously) supplied function.
* return A value of return type R equivalent to calling func(a...).
* If this function has been called with these parameters
* previously, this will take O(log n) time.
*/
R operator()(Args&&... a)
{
auto tup = std::make_tuple(std::forward<Args>(a)...);
auto it = memo_.find(tup);
if(it != memo_.end()) {
return it->second;
}
R val = func_(a...);
memo_.insert(std::make_pair(std::move(tup), val));
return val;
}
}; //end struct memoize_wrapper
编辑:示例用法:
第2版:如前所述,这不适用于递归函数。
#include "utility/memoize_wrapper.hpp"
#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>
long factorial(long i)
{
long result = 1;
long current = 2;
while(current <= i) {
result *= current;
++current;
}
return result;
}
int main()
{
std::vector<int> arg {10, 9, 8, 7, 6, 10, 9, 8, 7, 6};
std::transform(arg.begin(), arg.end(), arg.begin(), memoize_wrapper<long, long>(factorial));
for(long i : arg) {
std::cout << i << "n";
}
}
下面是一个(线程安全)C++17函数模板,它的作用类似于std::invoke
,但会存储结果:
/**
* @brief Drop-in replacement for std::invoke which memoizes the return
* result.
*
* @param[in] function The function whose result needs to be cached
* @param[in] args The function arguments
*
* @tparam Function The function type
* @tparam Args The argument types
*
* @return A value obtained either by evaluating the function, or by
* recalling it from a cache.
*
* @note The function provided must not be a type-erase function object
* like a raw function pointer or std::function, because this
* function depends on the uniqueness of the Function template
* parameter. If you were to call invoke_memoized(f, a) and
* invoke_memoized(g, b) in the same translation unit, where f and g
* were function pointers of the same type, and a and b were
* arguments of the same type, you'd end up using the same cache for
* both functions f and g. A reasonable attempt is made to detect
* these misuse cases via static_assert.
*/
template<typename Function, typename... Args>
auto invoke_memoized(Function function, Args... args)
{
using key_type = std::tuple<Args...>;
using value_type = std::invoke_result_t<Function, Args...>;
static_assert(! std::is_same_v<Function, std::function<value_type(Args...)>>,
"cannot memoize on std::function (use a lambda instead)");
static_assert(! std::is_same_v<Function, value_type(*)(Args...)>,
"cannot memoize on function pointer (use a lambda instead)");
static std::mutex mutex;
static std::map<key_type, value_type> cache;
auto key = std::tuple(args...);
auto lock = std::lock_guard<std::mutex>(mutex);
if (cache.count(key))
{
return cache[key];
}
return cache[key] = std::apply(function, key);
}
你可以这样使用它:
auto c = invoke_memoized(std::plus<>(), 1, 2.3);
为函数对象和参数类型的每个组合维护一个静态缓存。如前所述,std::function
和原始函数指针被拒绝,因为类型擦除的函数会混淆它们的缓存。您可以很容易地修改此函数来限制缓存大小。
- "error: no matching function for call to"构造函数错误
- 什么时候调用组成单元对象的析构函数
- 继承函数的重载解析
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- C++模板来检查友元函数的存在
- 递归函数计算序列中的平方和(并输出过程)
- 对RValue对象调用的LValue ref限定成员函数
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 为什么使用 "this" 指针调用派生成员函数?
- 将对象数组的引用传递给函数
- 函数调用中参数的顺序重要吗
- 递归函数有效,但无法记忆
- 如何将记忆应用于此递归函数?
- 如何在函数中分配的自由记忆
- WM_Paint的 BeginPaint() 函数的记忆丧失
- 记忆化的递归阶乘函数
- 在C++11中编写通用记忆函数
- c++中的记忆函数包装器