web-dev-qa-db-ja.com

C ++ 11でのユニバーサルメモ化関数の作成

関数を受け取り、そのメモ化バージョンを返す汎用汎用メモ化関数を実装する方法をお探しですか?

Pythonで@memo(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++ 11の新しい機能を使用して、C++で汎用の再利用可能なデコレータを表現する方法はありますか?

52
akrohit

ラムダを返すコンパクトなもの:

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を返すことによって課せられる余分な間接性を回避するために、一般化された戻り型の演ductionを使用できます。

これを完全に一般化し、任意の関数オブジェクトを最初にstd::functionでラップせずに渡すことを許可することは、読者の課題として残されています。

40
JohannesD

C++でメモ化を行う正しい方法は、Yコンビネーターを混在させることです。

基本関数には変更が必要です。自身を直接呼び出すのではなく、最初の引数としてテンプレート化された参照を受け取ります(または、_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コンビネータを使用することも有用です。

メモ化された関数が呼び出し中に引数を変更すると、メモ化機能は結果を間違った場所にキャッシュすることに注意してください。

_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;
  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)}; }
_

実例 に基づくハードコードされたハッシュ関数を使用 this SO post .

_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);
  }
);
_

@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

編集:使用例:

Edit2:指摘したように、これは再帰関数では機能しません。

#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";
    }
}
5
Yuushi

私は同じ問題に苦労しました。再帰も(再帰的なコードを少し修正して)サポートするマクロを作成しました。ここにあります:

#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 :)

3
jendas