Skip to content

Commit dbc1ea5

Browse files
committed
recency map
1 parent 93442f1 commit dbc1ea5

2 files changed

Lines changed: 446 additions & 0 deletions

File tree

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#pragma once
2+
3+
#include <functional>
4+
#include <list>
5+
#include <unordered_map>
6+
#include <utility>
7+
8+
namespace sndx {
9+
// ONLY .get, .poke, and .insert_or_update will update recency
10+
template <class KeyT, class ItemT>
11+
class RecencyMap {
12+
public:
13+
using key_type = KeyT;
14+
using value_type = std::pair<const KeyT*, ItemT>;
15+
16+
private:
17+
// front is most recent, back is oldest
18+
std::list<std::pair<const KeyT*, ItemT>> container{};
19+
using ContainerIt = typename decltype(container)::iterator;
20+
21+
std::unordered_map<KeyT, ContainerIt> mapping{};
22+
23+
void updateRecency(ContainerIt& it) {
24+
container.splice(container.begin(), container, it);
25+
it = container.begin();
26+
}
27+
public:
28+
29+
[[nodiscard]]
30+
ItemT* get(const KeyT& key) {
31+
if (auto it = mapping.find(key); it != mapping.end()) {
32+
auto& containerIt = it->second;
33+
34+
updateRecency(containerIt);
35+
return &containerIt->second;
36+
}
37+
return nullptr;
38+
}
39+
40+
bool poke(const KeyT& key) {
41+
return get(key) != nullptr;
42+
}
43+
44+
template <class M>
45+
std::pair<ContainerIt, bool> insert_or_assign(const KeyT& key, M&& value) {
46+
if (auto it = mapping.find(key); it != mapping.end()) {
47+
auto& containerIt = it->second;
48+
49+
updateRecency(containerIt);
50+
containerIt->second = std::forward<M>(value);
51+
return { containerIt, false };
52+
}
53+
54+
container.emplace_front(nullptr, std::forward<M>(value));
55+
auto mit = mapping.emplace(key, container.begin()).first;
56+
container.front().first = &mit->first;
57+
return { container.begin(), true };
58+
}
59+
60+
void pop_most_recent() {
61+
mapping.erase(*container.front().first);
62+
container.pop_front();
63+
}
64+
65+
void pop_least_recent() {
66+
mapping.erase(*container.back().first);
67+
container.pop_back();
68+
}
69+
70+
void erase(const KeyT& key) {
71+
if (auto it = mapping.find(key); it != mapping.end()) {
72+
container.erase(it->second);
73+
mapping.erase(it);
74+
}
75+
}
76+
77+
void clear() {
78+
container.clear();
79+
mapping.clear();
80+
}
81+
82+
[[nodiscard]] bool contains(const KeyT& key) const {
83+
return mapping.contains(key);
84+
}
85+
86+
[[nodiscard]] size_t size() const {
87+
return container.size();
88+
}
89+
90+
[[nodiscard]] bool empty() const {
91+
return container.empty();
92+
}
93+
94+
[[nodiscard]] decltype(auto) front() {
95+
return container.front();
96+
}
97+
98+
[[nodiscard]] decltype(auto) front() const {
99+
return container.front();
100+
}
101+
102+
[[nodiscard]] decltype(auto) back() {
103+
return container.back();
104+
}
105+
106+
[[nodiscard]] decltype(auto) back() const {
107+
return container.back();
108+
}
109+
110+
[[nodiscard]] decltype(auto) begin() {
111+
return container.begin();
112+
}
113+
114+
[[nodiscard]] decltype(auto) begin() const {
115+
return container.begin();
116+
}
117+
118+
[[nodiscard]] decltype(auto) end() {
119+
return container.end();
120+
}
121+
122+
[[nodiscard]] decltype(auto) end() const {
123+
return container.end();
124+
}
125+
};
126+
127+
// ONLY .get, .poke, and .insert_or_update will update recency
128+
template <class KeyT, class ItemT, class TimeT>
129+
class TimeAwareRecencyMap {
130+
public:
131+
using TimestampedT = std::pair<TimeT, ItemT>;
132+
using key_type = KeyT;
133+
using value_type = std::pair<const KeyT*, TimestampedT>;
134+
135+
private:
136+
RecencyMap<KeyT, TimestampedT> underlying{};
137+
138+
std::function<TimeT()> timeProvider;
139+
140+
public:
141+
TimeAwareRecencyMap(std::function<TimeT()> timeProvider):
142+
timeProvider(timeProvider) {}
143+
144+
[[nodiscard]]
145+
ItemT* get(const KeyT& key) {
146+
if (auto timestamped = underlying.get(key)) {
147+
timestamped->first = timeProvider();
148+
return &timestamped->second;
149+
}
150+
return nullptr;
151+
}
152+
153+
bool poke(const KeyT& key) {
154+
return get(key) != nullptr;
155+
}
156+
157+
template <class M>
158+
auto insert_or_assign(const KeyT& key, M&& value) {
159+
return underlying.insert_or_assign(key, std::make_pair<TimeT, ItemT>(timeProvider(), std::forward<M>(value)));
160+
}
161+
162+
void pop_most_recent() {
163+
underlying.pop_most_recent();
164+
}
165+
166+
void pop_least_recent() {
167+
underlying.pop_least_recent();
168+
}
169+
170+
// inclusive
171+
template <class DurationT>
172+
size_t erase_older_than(const DurationT& duration) {
173+
size_t count = 0;
174+
auto now = timeProvider();
175+
while (!underlying.empty()) {
176+
auto delta = now - underlying.back().second.first;
177+
if (delta >= duration) {
178+
underlying.pop_least_recent();
179+
++count;
180+
}
181+
else {
182+
break;
183+
}
184+
}
185+
return count;
186+
}
187+
188+
// exclusive
189+
template <class DurationT>
190+
size_t erase_newer_than(const DurationT& duration) {
191+
size_t count = 0;
192+
auto now = timeProvider();
193+
while (!underlying.empty()) {
194+
auto delta = now - underlying.front().second.first;
195+
if (delta < duration) {
196+
underlying.pop_most_recent();
197+
++count;
198+
}
199+
else {
200+
break;
201+
}
202+
}
203+
return count;
204+
}
205+
206+
void erase(const KeyT& key) {
207+
underlying.erase(key);
208+
}
209+
210+
void clear() {
211+
underlying.clear();
212+
}
213+
214+
[[nodiscard]] bool contains(const KeyT& key) const {
215+
return underlying.contains(key);
216+
}
217+
218+
[[nodiscard]] size_t size() const {
219+
return underlying.size();
220+
}
221+
222+
[[nodiscard]] bool empty() const {
223+
return underlying.empty();
224+
}
225+
226+
[[nodiscard]] decltype(auto) front() {
227+
return underlying.front();
228+
}
229+
230+
[[nodiscard]] decltype(auto) front() const {
231+
return underlying.front();
232+
}
233+
234+
[[nodiscard]] decltype(auto) back() {
235+
return underlying.back();
236+
}
237+
238+
[[nodiscard]] decltype(auto) back() const {
239+
return underlying.back();
240+
}
241+
242+
[[nodiscard]] decltype(auto) begin() {
243+
return underlying.begin();
244+
}
245+
246+
[[nodiscard]] decltype(auto) begin() const {
247+
return underlying.begin();
248+
}
249+
250+
[[nodiscard]] decltype(auto) end() {
251+
return underlying.end();
252+
}
253+
254+
[[nodiscard]] decltype(auto) end() const {
255+
return underlying.end();
256+
}
257+
};
258+
}

0 commit comments

Comments
 (0)