Cadabra
Computer algebra system for field theory problems
lru_cache.hh
Go to the documentation of this file.
1 // STL-like templated LRU cache.
2 //
3 // Copyright (C) 2024 Daniel Butter <dbutter@gmail.com>
4 // Distributed under the MIT license.
5 //
6 
16 #ifndef lru_cache_hh_
17 #define lru_cache_hh_
18 
19 #include <map>
20 #include <list>
21 #include <stdexcept>
22 #include <iterator>
23 
24 template <typename Key, typename Value, typename Compare = std::less<Key>>
25 class LRUcache {
26 public:
27  using value_type = std::pair<Key, Value>;
28  using List = std::list<value_type>;
29  using iterator = typename List::iterator;
30  using const_iterator = typename List::const_iterator;
31  using size_type = size_t;
32 
33 
34  LRUcache(size_t capacity) : max_size(capacity) {}
35 
36  // Touches a list iterator, moving it to the top
37  void touch(iterator& it) {
38  if (it != cache_list.begin()) {
39  cache_list.splice(cache_list.begin(), cache_list, it);
40  }
41  }
42 
43  // Access an item by key (possibly touching it)
44  Value& at(const Key& key, bool touching = true) {
45  auto lookup_it = cache_lookup.find(key);
46  if (lookup_it == cache_lookup.end()) {
47  throw std::out_of_range("Key not found");
48  }
49  if (touching) {
50  touch(lookup_it->second);
51  }
52  return lookup_it->second->second;
53  }
54 
55  // Insert or update an item (always touches)
56  void insert(const Key& key, const Value& value) {
57  auto lookup_it = cache_lookup.find(key);
58  if (lookup_it != cache_lookup.end()) {
59  touch(lookup_it->second);
60  lookup_it->second->second = value;
61  } else {
62  cache_list.emplace_front(key, value);
63  cache_lookup[key] = cache_list.begin();
64  while (cache_list.size() > max_size) {
65  evict();
66  }
67  }
68  }
69 
70  // Access an item (or create it) using operator[] (always touches)
71  Value& operator[](const Key& key) {
72  auto lookup_it = cache_lookup.find(key);
73  if (lookup_it != cache_lookup.end()) {
74  // If found, move it to the front of the list (LRU behavior)
75  touch(lookup_it->second);
76  return lookup_it->second->second;
77  } else {
78  // Create new entry, inserting at the front of the list
79  cache_list.emplace_front(key, Value());
80  iterator new_iter = cache_list.begin();
81  cache_lookup[key] = new_iter;
82  while (cache_list.size() > max_size) {
83  evict();
84  }
85  return new_iter->second;
86  }
87  }
88 
89  // Find a key (possibly touching it)
90  iterator find(const Key& key, bool touching = true) {
91  auto lookup_it = cache_lookup.find(key);
92  if (lookup_it == cache_lookup.end()) {
93  return cache_list.end(); // Key not found
94  }
95  if (touching) {
96  touch(lookup_it->second);
97  }
98  return lookup_it->second;
99  }
100 
101  // Check if a key exists (possibly touching it)
102  bool contains(const Key& key, bool touching = true) {
103  return find(key, touching) != end();
104  }
105 
106  // Remove an item by key
107  size_t erase(const Key& key) {
108  auto lookup_it = cache_lookup.find(key);
109  if (lookup_it != cache_lookup.end()) {
110  cache_list.erase(lookup_it->second);
111  cache_lookup.erase(lookup_it);
112  return 1;
113  }
114  return 0;
115  }
116 
117  // Remove an item by iterator
119  cache_lookup.erase(it->first);
120  return cache_list.erase(it);
121  }
123  cache_lookup.erase(it->first);
124  return cache_list.erase(it);
125  }
126 
127 
128  // Clear the cache
129  void clear() {
130  cache_list.clear();
131  cache_lookup.clear();
132  }
133 
134  // Check if the cache is empty
135  bool empty() const {
136  return cache_list.empty();
137  }
138 
139  // Get the current size of the cache
140  size_t size() const {
141  assert(cache_list.size() == cache_lookup.size());
142  return cache_list.size();
143  }
144 
145  // Resize the cache
146  void resize(size_t new_size) {
147  max_size = new_size;
148  while (cache_list.size() > max_size) {
149  evict();
150  }
151  }
152 
153  // Iterators
154  iterator begin() { return cache_list.begin(); }
155  iterator end() { return cache_list.end(); }
156 
157 
158 private:
159 
160  List cache_list; // Doubly linked list of key-value pairs
161  std::map<Key, iterator, Compare> cache_lookup; // Map to locate list nodes
162  size_t max_size;
163 
164  void evict() {
165  auto lru = cache_list.back().first;
166  cache_lookup.erase(lru);
167  cache_list.pop_back();
168  }
169 };
170 
171 
172 #endif
Definition: lru_cache.hh:25
typename List::const_iterator const_iterator
Definition: lru_cache.hh:30
iterator find(const Key &key, bool touching=true)
Definition: lru_cache.hh:90
iterator erase(iterator &it)
Definition: lru_cache.hh:122
size_t size_type
Definition: lru_cache.hh:31
void touch(iterator &it)
Definition: lru_cache.hh:37
void resize(size_t new_size)
Definition: lru_cache.hh:146
Value & at(const Key &key, bool touching=true)
Definition: lru_cache.hh:44
bool empty() const
Definition: lru_cache.hh:135
void evict()
Definition: lru_cache.hh:164
LRUcache(size_t capacity)
Definition: lru_cache.hh:34
Value & operator[](const Key &key)
Definition: lru_cache.hh:71
iterator begin()
Definition: lru_cache.hh:154
std::map< Key, iterator, Compare > cache_lookup
Definition: lru_cache.hh:161
size_t size() const
Definition: lru_cache.hh:140
void insert(const Key &key, const Value &value)
Definition: lru_cache.hh:56
size_t erase(const Key &key)
Definition: lru_cache.hh:107
void clear()
Definition: lru_cache.hh:129
size_t max_size
Definition: lru_cache.hh:162
List cache_list
Definition: lru_cache.hh:160
iterator end()
Definition: lru_cache.hh:155
std::pair< Key, Value > value_type
Definition: lru_cache.hh:27
typename List::iterator iterator
Definition: lru_cache.hh:29
std::list< value_type > List
Definition: lru_cache.hh:28
bool contains(const Key &key, bool touching=true)
Definition: lru_cache.hh:102
iterator erase(const_iterator &it)
Definition: lru_cache.hh:118