Created
October 31, 2017 02:39
-
-
Save hackeris/1e7695742c4a46606864052f35f64463 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// from http://codeproject.com/Articles/1209347/Designing-A-Generic-and-Modular-Cplusplus-Memoizer | |
#include <iostream> | |
#include <string> | |
#include <sstream> | |
#include <tuple> | |
#include <map> | |
#include <mutex> | |
#include <unordered_map> | |
namespace memo { | |
namespace cache { | |
namespace detail { | |
template <typename T, typename Mutex> | |
class ContainerLockGuard { | |
public: | |
ContainerLockGuard(const ContainerLockGuard&) = delete; | |
ContainerLockGuard& operator=(const ContainerLockGuard&) = delete; | |
ContainerLockGuard(const T* ptr, Mutex* mutex) throw () | |
: ptr_(ptr), | |
mutex_(mutex) | |
{} | |
ContainerLockGuard(ContainerLockGuard&& that) | |
:ptr_(nullptr), | |
mutex_(nullptr) | |
{ | |
std::swap(ptr_, that.ptr_); | |
std::swap(mutex_, that.mutex_); | |
} | |
~ContainerLockGuard() { | |
if (mutex_ != nullptr) { | |
mutex_->unlock(); | |
} | |
} | |
bool operator!() const { | |
return !ptr_; | |
} | |
const T& operator*() { | |
return *ptr_; | |
} | |
private: | |
T* ptr_; | |
Mutex* mutex_; | |
}; | |
} | |
struct construct_in_place_t {}; | |
template <typename Container > | |
class AssociativeContainerAdapter { | |
public: | |
AssociativeContainerAdapter() {} | |
template <typename... ConsArgs> | |
explicit AssociativeContainerAdapter(construct_in_place_t, ConsArgs&... args) | |
:data_(std::forward<ConsArgs>(args)...) | |
{ | |
} | |
template <typename K> | |
auto lookup(K&& key) const { | |
auto iter = data_.find(std::forward<K>(key)); | |
if (iter == data_.end()) { | |
return (decltype(&iter->second))nullptr; | |
} | |
return &iter->second; | |
} | |
template <typename K, typename V> | |
void remember(K&& key, V&& value) { | |
data_.emplace(std::forward<K>(key), std::forward<V>(value)); | |
} | |
private: | |
Container data_; | |
}; | |
template <typename Container, typename Mutex = std::recursive_mutex> | |
class SynchronizedContainerAdapter { | |
typedef typename Container::mapped_type MappedType; | |
friend class detail::ContainerLockGuard<MappedType, Mutex>; | |
public: | |
SynchronizedContainerAdapter() {} | |
template<typename ...ConsArgs> | |
explicit SynchronizedContainerAdapter(construct_in_place_t, ConsArgs&&... args) | |
:data_(std::forward<ConsArgs>(args)...) | |
{} | |
template<typename K> | |
auto lookup(K&& key) const { | |
std::unique_lock<Mutex> lk(*mutex_); | |
auto iter = data_.find(std::forward<K>(key)); | |
const MappedType* ptr = nullptr; | |
if (iter != data_.end()) { | |
ptr = &iter->second; | |
} | |
lk.release(); | |
return detail::ContainerLockGuard<MappedType, Mutex>(ptr, mutex_.get()); | |
} | |
template<typename K, typename V> | |
void remember(K&& key, V&& value) { | |
std::unique_lock<Mutex> lk(*mutex_); | |
data_.emplace(std::forward<K>(key), std::forward<V>(value)); | |
} | |
private: | |
Container data_; | |
mutable std::unique_ptr<Mutex> mutex_ = std::make_unique<Mutex>(); | |
}; | |
template <typename K, typename V, typename Comparator = std::less<K>> | |
using BasicOrderedCache = AssociativeContainerAdapter<std::map<K, V, Comparator>>; | |
template<typename K, typename V, typename Hash = std::hash<K>, typename KeyEqual = std::equal_to<K>> | |
using BasicUnorderedCache = AssociativeContainerAdapter<std::unordered_map<K, V, Hash, KeyEqual>>; | |
template<typename K, typename V, typename Comparator = std::less<K>> | |
using SynchronizedOrderedCache = SynchronizedContainerAdapter<std::map<K, V, Comparator>>; | |
template<typename K, typename V, typename Hash = std::hash<K>, typename KeyEqual = std::equal_to<K>> | |
using SynchronizedUnorderedCache = SynchronizedContainerAdapter<std::unordered_map<K, V, Hash, KeyEqual>>; | |
} | |
template <typename CacheType, typename CallableType, typename... Args> | |
auto invoke(CacheType&& cache, CallableType&& fn, Args&& ...args) { | |
auto&& pY = cache.lookup(std::tie(args...)); | |
if (!!pY) { | |
return *pY; | |
} | |
auto&& y = fn(std::forward<Args>(args)...); | |
cache.remember(std::tie(args...), std::forward<decltype(y)>(y)); | |
return y; | |
} | |
template <typename CacheType, typename CallableType> | |
class Memoizer { | |
public: | |
template<typename C, typename ...FnArgs> | |
explicit Memoizer(C&& cache, FnArgs&&... args) | |
: cache_(std::forward<C>(cache)) | |
, fn_(std::forward<FnArgs>(args)...) | |
{ | |
} | |
template<typename... Args> | |
auto operator()(Args&&... args) const { | |
return memo::invoke(cache_, fn_, std::forward<Args>(args)...); | |
} | |
private: | |
mutable CacheType cache_; | |
mutable CallableType fn_; | |
}; | |
template <typename CacheType, typename CallableType> | |
auto memoize(CacheType&& cache, CallableType&& f) { | |
typedef Memoizer<std::remove_reference_t<CacheType>, std::remove_reference_t<CallableType>> MemoizerType; | |
return MemoizerType(std::forward<CacheType>(cache), std::forward<CallableType>(f)); | |
} | |
} | |
double logisticFn(double x) | |
{ | |
return 1.0 / (1.0 + std::exp(-x)); | |
} | |
struct LogisticFn { | |
double operator()(double x) const | |
{ | |
return 1.0 / (1.0 + std::exp(-x)); | |
} | |
double evaluate(double x) const | |
{ | |
return operator()(x); | |
} | |
}; | |
struct FuzzyLess { | |
explicit FuzzyLess(double tolerance) | |
: tolerance_(tolerance) | |
{ | |
} | |
bool operator()(const std::tuple<double>& arg1, const std::tuple<double>& arg2) const | |
{ | |
return std::get<0>(arg1) < (std::get<0>(arg2) - tolerance_); | |
} | |
private: | |
double tolerance_; | |
}; | |
void memoizer_test() | |
{ | |
using namespace memo; | |
using namespace memo::cache; | |
// there is a single input of type double, and the result type is double | |
typedef BasicOrderedCache<std::tuple<double>, double, FuzzyLess> CacheType; | |
auto cache = CacheType(cache::construct_in_place_t(), FuzzyLess(1e-6)); | |
// memoize a free-function | |
auto logistic1 = memo::memoize(cache, &logisticFn); | |
std::cout << logistic1(5.0) << std::endl; | |
// memoize a lambda | |
auto logistic2 = memo::memoize(cache, | |
[](double x) -> double { | |
return 1.0 / (1.0 + std::exp(-x)); | |
}); | |
std::cout << logistic2(5.0) << std::endl; | |
// memoize a functor (std::function) | |
auto logistic3 = memo::memoize(cache, std::function<double(double)>(logisticFn)); | |
std::cout << logistic3(5.0) << std::endl; | |
// memoize a user-defined callable object | |
auto logistic4 = memo::memoize(cache, LogisticFn()); | |
std::cout << logistic4(5.0) << std::endl; | |
// memoize a member that is not a call operator | |
auto logistic5 = memo::memoize(cache, std::bind(&LogisticFn::evaluate, | |
LogisticFn(), std::placeholders::_1)); | |
std::cout << logistic5(5.0) << std::endl; | |
} | |
int main() { | |
memoizer_test(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment