-
Notifications
You must be signed in to change notification settings - Fork 0
/
least_frequently_used.hpp
119 lines (106 loc) · 3.28 KB
/
least_frequently_used.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Andrew Naplavkov
#ifndef STEP20_LEAST_FREQUENTLY_USED_HPP
#define STEP20_LEAST_FREQUENTLY_USED_HPP
#include <algorithm>
#include <list>
#include <unordered_map>
namespace step20::least_frequently_used {
/// An O(1) algorithm for implementing the LFU cache eviction scheme
template <class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>>
class cache {
struct item_type;
using item_list = std::list<item_type>;
using item_iterator = typename item_list::iterator;
struct freq_type;
using freq_list = std::list<freq_type>;
using freq_iterator = typename freq_list::iterator;
struct item_type {
freq_iterator parent;
Key key;
T val;
};
struct freq_type {
std::size_t n;
item_list items;
};
freq_list list_;
std::unordered_map<Key, item_iterator, Hash, KeyEqual> map_;
std::size_t capacity_;
bool equal(freq_iterator it, std::size_t n) const
{
return it != list_.end() && it->n == n;
}
public:
explicit cache(std::size_t capacity) : capacity_(capacity) {}
cache(cache&&) = default;
cache& operator=(cache&&) = default;
cache(const cache&) = delete;
cache& operator=(const cache&) = delete;
virtual ~cache() = default;
/// @return nullptr if key is not found
const T* find(const Key& key)
{
auto it = map_.find(key);
if (it == map_.end())
return nullptr;
auto item = it->second;
auto freq = item->parent;
auto next = std::next(freq);
if (!equal(next, freq->n + 1)) {
if (freq->items.size() == 1) {
++freq->n;
return std::addressof(item->val);
}
next = list_.emplace(next, freq->n + 1);
}
try {
next->items.splice(next->items.end(), freq->items, item);
}
catch (...) {
if (next->items.empty())
list_.erase(next);
throw;
}
item->parent = next;
if (freq->items.empty())
list_.erase(freq);
return std::addressof(item->val);
}
/// Basic exception guarantee.
/// Evicted item is not restored if an exception occurs.
void insert_or_assign(const Key& key, const T& val)
{
if (auto ptr = find(key)) {
*const_cast<T*>(ptr) = val;
return;
}
if (map_.size() >= std::max<std::size_t>(1, capacity_)) {
auto freq = list_.begin();
auto item = freq->items.begin();
map_.erase(item->key);
freq->items.erase(item);
if (freq->items.empty())
list_.erase(freq);
}
auto freq = list_.begin();
auto exists = equal(freq, 1);
freq = exists ? freq : list_.emplace(freq, 1);
auto item = freq->items.end();
try {
item = freq->items.emplace(item, freq, key, val);
map_.emplace(key, item);
}
catch (...) {
if (!exists)
list_.erase(freq);
else if (item != freq->items.end())
freq->items.erase(item);
throw;
}
}
};
} // namespace step20::least_frequently_used
#endif // STEP20_LEAST_FREQUENTLY_USED_HPP