Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 821d20c

Browse files
corvo-007realstealthninja
andauthored
feat: LFU (Least frequently used) cache (TheAlgorithms#2757)
* feat: add lfu cache * docs: add comments and explanation to class LFUCache * test: add tests for class lfu cache * docs: document namespace and classes * test: modify tests to check negative numbers * docs: document template params and class data members * test: make test func static and move tests in the same func --------- Co-authored-by: realstealthninja <68815218+realstealthninja@users.noreply.github.com>
1 parent fddedd8 commit 821d20c

File tree

1 file changed

+304
-0
lines changed

1 file changed

+304
-0
lines changed

‎others/lfu_cache.cpp‎

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/**
2+
* @file
3+
* @brief Implementation for [LFU Cache]
4+
* (https://en.wikipedia.org/wiki/Least_frequently_used)
5+
*
6+
* @details
7+
* LFU discards the least frequently used value. if there are multiple items
8+
* with the same minimum frequency then, the least recently used among them is
9+
* discarded. Data structures used - doubly linked list and unordered_map(hash
10+
* map).
11+
*
12+
* Hashmap maps the key to the address of the node of the linked list and its
13+
* current usage frequency. If the element is accessed the element is removed
14+
* from the linked list of the current frequency and added to the linked list of
15+
* incremented frequency.
16+
*
17+
* When the cache is full, the last element in the minimum frequency linked list
18+
* is popped.
19+
*
20+
* @author [Karan Sharma](https://github.com/deDSeC00720)
21+
*/
22+
23+
#include <cassert> // for assert
24+
#include <iostream> // for std::cout
25+
#include <unordered_map> // for std::unordered_map
26+
27+
/**
28+
* @namespace
29+
* @brief Other algorithms
30+
*/
31+
namespace others {
32+
33+
/**
34+
* @namespace
35+
* @brief Cache algorithm
36+
*/
37+
namespace Cache {
38+
39+
/**
40+
* @class
41+
* @brief Node for a doubly linked list with data, prev and next pointers
42+
* @tparam T type of the data of the node
43+
*/
44+
template <typename T>
45+
class D_Node {
46+
public:
47+
T data; ///< data of the node
48+
D_Node<T> *prev; ///< previous node in the doubly linked list
49+
D_Node<T> *next; ///< next node in the doubly linked list
50+
51+
explicit D_Node(T data) : data(data), prev(nullptr), next(nullptr) {}
52+
};
53+
54+
template <typename K, typename V>
55+
using CacheNode = D_Node<std::pair<K, V>>;
56+
57+
/**
58+
* @class
59+
* @brief LFUCache
60+
* @tparam K type of key in the LFU
61+
* @tparam V type of value in the LFU
62+
*/
63+
template <typename K, typename V>
64+
class LFUCache {
65+
std::unordered_map<K, std::pair<CacheNode<K, V> *, int>>
66+
node_map; ///< maps the key to the node address and frequency
67+
std::unordered_map<int, std::pair<CacheNode<K, V> *, CacheNode<K, V> *>>
68+
freq_map; ///< maps the frequency to doubly linked list
69+
70+
int minFreq; ///< minimum frequency in the cache
71+
int _capacity; ///< maximum capacity of the cache
72+
73+
public:
74+
/**
75+
* @brief Constructor, Initialize with minFreq and _capacity.
76+
* @param _capacity Total capacity of the cache.
77+
*/
78+
explicit LFUCache(int _capacity) : minFreq(0), _capacity(_capacity) {}
79+
80+
private:
81+
/**
82+
* @brief push the node at first position in the linked list of given
83+
* frequency
84+
* @param freq the frequency mapping to the linked list where node should be
85+
* pushed.
86+
* @param node node to be pushed to the linked list.
87+
*/
88+
void push(int freq, CacheNode<K, V> *node) {
89+
// if freq is not present, then make a new list with node as the head as
90+
// well as tail.
91+
if (!freq_map.count(freq)) {
92+
freq_map[freq] = {node, node};
93+
return;
94+
}
95+
96+
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[freq];
97+
98+
// insert the node at the beginning of the linked list and update the
99+
// head.
100+
p.first->prev = node;
101+
node->next = p.first;
102+
p.first = node;
103+
}
104+
105+
/**
106+
* @brief increase the frequency of node and push it in the respective list.
107+
* @param p_node the node to be updated
108+
*/
109+
void increase_frequency(std::pair<CacheNode<K, V> *, int> &p_node) {
110+
CacheNode<K, V> *node = p_node.first;
111+
int freq = p_node.second;
112+
113+
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[freq];
114+
115+
// if the given node is the only node in the list,
116+
// then erase the frequency from map
117+
// and increase minFreq by 1.
118+
if (p.first == node && p.second == node) {
119+
freq_map.erase(freq);
120+
if (minFreq == freq) {
121+
minFreq = freq + 1;
122+
}
123+
} else {
124+
// remove the given node from current freq linked list
125+
CacheNode<K, V> *prev = node->prev;
126+
CacheNode<K, V> *next = node->next;
127+
node->prev = nullptr;
128+
node->next = nullptr;
129+
130+
if (prev) {
131+
prev->next = next;
132+
} else {
133+
p.first = next;
134+
}
135+
136+
if (next) {
137+
next->prev = prev;
138+
} else {
139+
p.second = prev;
140+
}
141+
}
142+
push(freq + 1, node);
143+
++p_node.second;
144+
}
145+
146+
/**
147+
* @brief pop the last node in the least frequently used linked list
148+
*/
149+
void pop() {
150+
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[minFreq];
151+
152+
// if there is only one node
153+
// remove the node and erase
154+
// the frequency from freq_map
155+
if (p.first == p.second) {
156+
delete p.first;
157+
freq_map.erase(minFreq);
158+
return;
159+
}
160+
161+
// remove the last node in the linked list
162+
CacheNode<K, V> *temp = p.second;
163+
p.second = temp->prev;
164+
p.second->next = nullptr;
165+
delete temp;
166+
}
167+
168+
public:
169+
/**
170+
* @brief upsert a key-value pair
171+
* @param key key of the key-value pair
172+
* @param value value of the key-value pair
173+
*/
174+
void put(K key, V value) {
175+
// update the value if key already exists
176+
if (node_map.count(key)) {
177+
node_map[key].first->data.second = value;
178+
increase_frequency(node_map[key]);
179+
return;
180+
}
181+
182+
// if the cache is full
183+
// remove the least frequently used item
184+
if (node_map.size() == _capacity) {
185+
node_map.erase(freq_map[minFreq].second->data.first);
186+
pop();
187+
}
188+
189+
// insert the new node and set minFreq to 1
190+
CacheNode<K, V> *node = new CacheNode<K, V>({key, value});
191+
node_map[key] = {node, 1};
192+
minFreq = 1;
193+
push(1, node);
194+
}
195+
196+
/**
197+
* @brief get the value of the key-value pair if exists
198+
* @param key key of the key-value pair
199+
* @return the value mapped to the given key
200+
* @exception exception is thrown if the key is not present in the cache
201+
*/
202+
V get(K key) {
203+
if (!node_map.count(key)) {
204+
throw std::runtime_error("key is not present in the cache");
205+
}
206+
207+
// increase the frequency and return the value
208+
V value = node_map[key].first->data.second;
209+
increase_frequency(node_map[key]);
210+
return value;
211+
}
212+
213+
/**
214+
* @brief Returns the number of items present in the cache.
215+
* @return number of items in the cache
216+
*/
217+
int size() const { return node_map.size(); }
218+
219+
/**
220+
* @brief Returns the total capacity of the cache
221+
* @return Total capacity of the cache
222+
*/
223+
int capacity() const { return _capacity; }
224+
225+
/**
226+
* @brief returns true if the cache is empty, false otherwise.
227+
* @return true if the cache is empty, false otherwise.
228+
*/
229+
bool empty() const { return node_map.empty(); }
230+
231+
/**
232+
* @brief destructs the cache, iterates on the map and deletes every node
233+
* present in the cache.
234+
*/
235+
~LFUCache() {
236+
auto it = node_map.begin();
237+
while (it != node_map.end()) {
238+
delete it->second.first;
239+
++it;
240+
}
241+
}
242+
};
243+
} // namespace Cache
244+
} // namespace others
245+
246+
/**
247+
* @brief self test implementation
248+
* @return void
249+
*/
250+
static void test() {
251+
others::Cache::LFUCache<int, int> cache(5);
252+
253+
// test the initial state of the cache
254+
assert(cache.size() == 0);
255+
assert(cache.capacity() == 5);
256+
assert(cache.empty());
257+
258+
// test insertion in the cache
259+
cache.put(1, 10);
260+
cache.put(-2, 20);
261+
262+
// test the state of cache after inserting some items
263+
assert(cache.size() == 2);
264+
assert(cache.capacity() == 5);
265+
assert(!cache.empty());
266+
267+
// test getting items from the cache
268+
assert(cache.get(1) == 10);
269+
assert(cache.get(-2) == 20);
270+
271+
cache.put(-3, -30);
272+
cache.put(4, 40);
273+
cache.put(5, -50);
274+
cache.put(6, 60);
275+
276+
// test the state after inserting more items than the capacity
277+
assert(cache.size() == 5);
278+
assert(cache.capacity() == 5);
279+
assert(!cache.empty());
280+
281+
// test retrieval of all items in the cache
282+
assert(cache.get(1) == 10);
283+
assert(cache.get(-2) == 20);
284+
285+
// fetching -3 throws runtime_error
286+
// as -3 was evicted being the least frequently used
287+
// when 6 was added
288+
// assert(cache.get(-3) == -30);
289+
290+
assert(cache.get(4) == 40);
291+
assert(cache.get(5) == -50);
292+
assert(cache.get(6) == 60);
293+
294+
std::cout << "test - passed\n";
295+
}
296+
297+
/**
298+
* @brief main function
299+
* @return 0 on exit
300+
*/
301+
int main() {
302+
test(); // run the self test implementation
303+
return 0;
304+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /