1
+ /* *
2
+ * @file
3
+ * @brief Implementation for [LFU Cache]
4
+ * (https://en.wikipedia.org/wiki/Least_frequently_used)
5
+ *
6
+ * LFU discards the least frequently used value. if there are multiple items
7
+ * with the same minimum frequency then, the least recently used among them is
8
+ * discarded. Data structures used - doubly linked list and unordered_map(hash
9
+ * map).
10
+ *
11
+ * Hashmap maps the key to the address of the node of the linked list and its
12
+ * current usage frequency. If the element is accessed the element is removed
13
+ * from the linked list of the current frequency and added to the linked list of
14
+ * incremented frequency.
15
+ *
16
+ * When the cache is full, the last element in the minimum frequency linked list
17
+ * is popped.
18
+ *
19
+ * @author [Karan Sharma](https://github.com/deDSeC00720)
20
+ */
21
+
1
22
#include < iostream>
2
23
#include < unordered_map>
3
24
@@ -18,41 +39,65 @@ using CacheNode = D_Node<std::pair<K, V>>;
18
39
19
40
template <typename K, typename V>
20
41
class LFUCache {
21
- std::unordered_map<K, std::pair<CacheNode<K, V> *, int >> node_map;
42
+ std::unordered_map<K, std::pair<CacheNode<K, V> *, int >>
43
+ node_map; // maps the key to the node address and frequency
22
44
std::unordered_map<int , std::pair<CacheNode<K, V> *, CacheNode<K, V> *>>
23
- freq_map;
45
+ freq_map; // maps the frequency to doubly linked list
24
46
25
- int minFreq;
26
- int _capacity;
47
+ int minFreq; // minimum frequency in the cache
48
+ int _capacity; // maximum capacity of the cache
27
49
28
50
public:
29
- LFUCache (int _capacity) : minFreq(0 ), _capacity(_capacity) {}
51
+ /* *
52
+ * @brief Constructor, Initialize with minFreq and _capacity.
53
+ * @param _capacity Total capacity of the cache.
54
+ */
55
+ explicit LFUCache (int _capacity) : minFreq(0 ), _capacity(_capacity) {}
30
56
31
57
private:
58
+ /* *
59
+ * @brief push the node at first position in the linked list of given
60
+ * frequency
61
+ * @param freq the frequency mapping to the linked list where node should be
62
+ * pushed.
63
+ * @param node node to be pushed to the linked list.
64
+ */
32
65
void push (int freq, CacheNode<K, V> *node) {
66
+ // if freq is not present, then make a new list with node as the head as
67
+ // well as tail.
33
68
if (!freq_map.count (key)) {
34
69
freq_map[freq] = {node, node};
35
70
}
36
71
37
72
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[freq];
38
73
74
+ // insert the node at the beginning of the linked list and update the
75
+ // head.
39
76
p.first ->prev = node;
40
77
node->next = p.first ;
41
78
p.first = node;
42
79
}
43
80
81
+ /* *
82
+ * @brief increase the frequency of node and push it in the respective list.
83
+ * @param p_node the node to be updated
84
+ */
44
85
void inc_freq (std::pair<CacheNode<K, V> *, int > &p_node) {
45
86
CacheNode<K, V> *node = p_node.first ;
46
87
int freq = p_node.second ;
47
88
48
89
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[freq];
49
90
91
+ // if the given node is the only node in the list,
92
+ // then erase the frequency from map
93
+ // and increase minFreq by 1.
50
94
if (p.first == node && p.second == node) {
51
95
freq_map.erase (freq);
52
96
if (minFreq == freq) {
53
97
minFreq = freq + 1 ;
54
98
}
55
99
} else {
100
+ // remove the given node from current freq linked list
56
101
CacheNode<K, V> *prev = node->prev ;
57
102
CacheNode<K, V> *next = node->next ;
58
103
node->prev = nullptr ;
@@ -74,53 +119,95 @@ class LFUCache {
74
119
p_node.second ++;
75
120
}
76
121
122
+ /* *
123
+ * @brief pop the last node in the least frequently used linked list
124
+ */
77
125
void pop () {
78
126
std::pair<CacheNode<K, V> *, CacheNode<K, V> *> &p = freq_map[minFreq];
127
+
128
+ // if there is only one node
129
+ // remove the node and erase
130
+ // the frequency from freq_map
79
131
if (p.first == p.second ) {
80
132
delete p.first ;
81
133
freq_map.erase (minFreq);
82
134
return ;
83
135
}
136
+
137
+ // remove the last node in the linked list
84
138
CacheNode<K, V> *temp = p.second ;
85
139
p.second = temp->prev ;
86
140
p.second ->next = nullptr ;
87
141
delete temp;
88
142
}
89
143
90
144
public:
145
+ /* *
146
+ * @brief upsert a key-value pair
147
+ * @param key key of the key-value pair
148
+ * @param value value of the key-value pair
149
+ */
91
150
void put (K key, V value) {
151
+ // update the value if key already exists
92
152
if (node_map.count (key)) {
93
153
node_map[key].first ->data .second = value;
94
154
inc_freq (node_map[key]);
95
155
return ;
96
156
}
97
157
158
+ // if the cache is full
159
+ // remove the least frequently used item
98
160
if (node_map.size () == _capacity) {
99
161
node_map.erase (freq_map[minFreq].second ->data .first );
100
162
pop ();
101
163
}
164
+
165
+ // insert the new node and set minFreq to 1
102
166
CacheNode<K, V> *node = new CacheNode<K, V>({key, value});
103
167
node_map[key] = {node, 1 };
104
168
minFreq = 1 ;
105
169
push (1 , node);
106
170
}
107
171
172
+ /* *
173
+ * @brief get the value of the key-value pair if exists
174
+ * @param key key of the key-value pair
175
+ * @return the value mapped to the given key
176
+ * @exception exception is thrown if the key is not present in the cache
177
+ */
108
178
V get (K key) {
109
179
if (!node_map.count (key)) {
110
180
throw std::exception (" key is not present in the cache" );
111
181
}
112
182
183
+ // increase the frequency and return the value
113
184
V value = node_map[key].first ->data .second ;
114
185
inc_freq (node_map[key]);
115
186
return value;
116
187
}
117
188
189
+ /* *
190
+ * @brief Returns the number of items present in the cache.
191
+ * @return number of items in the cache
192
+ */
118
193
int size () { return node_map.size (); }
119
194
195
+ /* *
196
+ * @brief Returns the total capacity of the cache
197
+ * @return Total capacity of the cache
198
+ */
120
199
int capacity () { return _capacity; }
121
200
201
+ /* *
202
+ * @brief returns true if the cache is empty, false otherwise.
203
+ * @return true if the cache is empty, false otherwise.
204
+ */
122
205
bool empty () { return node_map.size () == 0 ; }
123
206
207
+ /* *
208
+ * @brief destructs the cache, iterates on the map and deletes every node
209
+ * present in the cache.
210
+ */
124
211
~LFUCache () {
125
212
auto it = node_map.begin ();
126
213
while (it != node_map.end ()) {
0 commit comments