Skip to content

Instantly share code, notes, and snippets.

@hackeris
Created October 31, 2017 02:39
Show Gist options
  • Save hackeris/1e7695742c4a46606864052f35f64463 to your computer and use it in GitHub Desktop.
Save hackeris/1e7695742c4a46606864052f35f64463 to your computer and use it in GitHub Desktop.
// 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