From 7fb3ca5407c61857de7904084eb053cbdca98efa Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 04:28:00 +0530 Subject: [PATCH 1/7] feat: add lfu cache --- others/lfu_cache.cpp | 132 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 others/lfu_cache.cpp diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp new file mode 100644 index 00000000000..6305ac22cd2 --- /dev/null +++ b/others/lfu_cache.cpp @@ -0,0 +1,132 @@ +#include +#include + +namespace Cache { + +template +class D_Node { + public: + T data; + D_Node *prev; + D_Node *next; + + D_Node(T data) : data(data), prev(nullptr), next(nullptr) {} +}; + +template +using CacheNode = D_Node>; + +template +class LFUCache { + std::unordered_map *, int>> node_map; + std::unordered_map *, CacheNode *>> + freq_map; + + int minFreq; + int _capacity; + + public: + LFUCache(int _capacity) : minFreq(0), _capacity(_capacity) {} + + private: + void push(int freq, CacheNode *node) { + if (!freq_map.count(key)) { + freq_map[freq] = {node, node}; + } + + std::pair *, CacheNode *> &p = freq_map[freq]; + + p.first->prev = node; + node->next = p.first; + p.first = node; + } + + void inc_freq(std::pair *, int> &p_node) { + CacheNode *node = p_node.first; + int freq = p_node.second; + + std::pair *, CacheNode *> &p = freq_map[freq]; + + if (p.first == node && p.second == node) { + freq_map.erase(freq); + if (minFreq == freq) { + minFreq = freq + 1; + } + } else { + CacheNode *prev = node->prev; + CacheNode *next = node->next; + node->prev = nullptr; + node->next = nullptr; + + if (prev) { + prev->next = next; + } else { + p.first = next; + } + + if (next) { + next->prev = prev; + } else { + p.second = prev; + } + } + push(freq + 1, node); + p_node.second++; + } + + void pop() { + std::pair *, CacheNode *> &p = freq_map[minFreq]; + if (p.first == p.second) { + delete p.first; + freq_map.erase(minFreq); + return; + } + CacheNode *temp = p.second; + p.second = temp->prev; + p.second->next = nullptr; + delete temp; + } + + public: + void put(K key, V value) { + if (node_map.count(key)) { + node_map[key].first->data.second = value; + inc_freq(node_map[key]); + return; + } + + if (node_map.size() == _capacity) { + node_map.erase(freq_map[minFreq].second->data.first); + pop(); + } + CacheNode *node = new CacheNode({key, value}); + node_map[key] = {node, 1}; + minFreq = 1; + push(1, node); + } + + V get(K key) { + if (!node_map.count(key)) { + throw std::exception("key is not present in the cache"); + } + + V value = node_map[key].first->data.second; + inc_freq(node_map[key]); + return value; + } + + int size() { return node_map.size(); } + + int capacity() { return _capacity; } + + bool empty() { return node_map.size() == 0; } + + ~LFUCache() { + auto it = node_map.begin(); + while (it != node_map.end()) { + delete it->second.first; + it++; + } + } +}; +} // namespace Cache From a37ce7b870126525888ecac2267a4836cb4064d6 Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 04:29:28 +0530 Subject: [PATCH 2/7] docs: add comments and explanation to class LFUCache --- others/lfu_cache.cpp | 97 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 6305ac22cd2..89febf81293 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -1,3 +1,24 @@ +/** + * @file + * @brief Implementation for [LFU Cache] + * (https://en.wikipedia.org/wiki/Least_frequently_used) + * + * LFU discards the least frequently used value. if there are multiple items + * with the same minimum frequency then, the least recently used among them is + * discarded. Data structures used - doubly linked list and unordered_map(hash + * map). + * + * Hashmap maps the key to the address of the node of the linked list and its + * current usage frequency. If the element is accessed the element is removed + * from the linked list of the current frequency and added to the linked list of + * incremented frequency. + * + * When the cache is full, the last element in the minimum frequency linked list + * is popped. + * + * @author [Karan Sharma](https://github.com/deDSeC00720) + */ + #include #include @@ -18,41 +39,65 @@ using CacheNode = D_Node>; template class LFUCache { - std::unordered_map *, int>> node_map; + std::unordered_map *, int>> + node_map; // maps the key to the node address and frequency std::unordered_map *, CacheNode *>> - freq_map; + freq_map; // maps the frequency to doubly linked list - int minFreq; - int _capacity; + int minFreq; // minimum frequency in the cache + int _capacity; // maximum capacity of the cache public: - LFUCache(int _capacity) : minFreq(0), _capacity(_capacity) {} + /** + * @brief Constructor, Initialize with minFreq and _capacity. + * @param _capacity Total capacity of the cache. + */ + explicit LFUCache(int _capacity) : minFreq(0), _capacity(_capacity) {} private: + /** + * @brief push the node at first position in the linked list of given + * frequency + * @param freq the frequency mapping to the linked list where node should be + * pushed. + * @param node node to be pushed to the linked list. + */ void push(int freq, CacheNode *node) { + // if freq is not present, then make a new list with node as the head as + // well as tail. if (!freq_map.count(key)) { freq_map[freq] = {node, node}; } std::pair *, CacheNode *> &p = freq_map[freq]; + // insert the node at the beginning of the linked list and update the + // head. p.first->prev = node; node->next = p.first; p.first = node; } + /** + * @brief increase the frequency of node and push it in the respective list. + * @param p_node the node to be updated + */ void inc_freq(std::pair *, int> &p_node) { CacheNode *node = p_node.first; int freq = p_node.second; std::pair *, CacheNode *> &p = freq_map[freq]; + // if the given node is the only node in the list, + // then erase the frequency from map + // and increase minFreq by 1. if (p.first == node && p.second == node) { freq_map.erase(freq); if (minFreq == freq) { minFreq = freq + 1; } } else { + // remove the given node from current freq linked list CacheNode *prev = node->prev; CacheNode *next = node->next; node->prev = nullptr; @@ -74,13 +119,22 @@ class LFUCache { p_node.second++; } + /** + * @brief pop the last node in the least frequently used linked list + */ void pop() { std::pair *, CacheNode *> &p = freq_map[minFreq]; + + // if there is only one node + // remove the node and erase + // the frequency from freq_map if (p.first == p.second) { delete p.first; freq_map.erase(minFreq); return; } + + // remove the last node in the linked list CacheNode *temp = p.second; p.second = temp->prev; p.second->next = nullptr; @@ -88,39 +142,72 @@ class LFUCache { } public: + /** + * @brief upsert a key-value pair + * @param key key of the key-value pair + * @param value value of the key-value pair + */ void put(K key, V value) { + // update the value if key already exists if (node_map.count(key)) { node_map[key].first->data.second = value; inc_freq(node_map[key]); return; } + // if the cache is full + // remove the least frequently used item if (node_map.size() == _capacity) { node_map.erase(freq_map[minFreq].second->data.first); pop(); } + + // insert the new node and set minFreq to 1 CacheNode *node = new CacheNode({key, value}); node_map[key] = {node, 1}; minFreq = 1; push(1, node); } + /** + * @brief get the value of the key-value pair if exists + * @param key key of the key-value pair + * @return the value mapped to the given key + * @exception exception is thrown if the key is not present in the cache + */ V get(K key) { if (!node_map.count(key)) { throw std::exception("key is not present in the cache"); } + // increase the frequency and return the value V value = node_map[key].first->data.second; inc_freq(node_map[key]); return value; } + /** + * @brief Returns the number of items present in the cache. + * @return number of items in the cache + */ int size() { return node_map.size(); } + /** + * @brief Returns the total capacity of the cache + * @return Total capacity of the cache + */ int capacity() { return _capacity; } + /** + * @brief returns true if the cache is empty, false otherwise. + * @return true if the cache is empty, false otherwise. + */ bool empty() { return node_map.size() == 0; } + /** + * @brief destructs the cache, iterates on the map and deletes every node + * present in the cache. + */ ~LFUCache() { auto it = node_map.begin(); while (it != node_map.end()) { From 17e2f0c6dc01b5583732ab881e030169ab421e1d Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 04:30:22 +0530 Subject: [PATCH 3/7] test: add tests for class lfu cache --- others/lfu_cache.cpp | 99 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 89febf81293..44304ea6797 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -19,9 +19,15 @@ * @author [Karan Sharma](https://github.com/deDSeC00720) */ -#include -#include +#include // for assert +#include // for IO operations +#include // for hash map +/** + * @namespace + * @brief Other algorithms + */ +namespace others { namespace Cache { template @@ -31,7 +37,7 @@ class D_Node { D_Node *prev; D_Node *next; - D_Node(T data) : data(data), prev(nullptr), next(nullptr) {} + explicit D_Node(T data) : data(data), prev(nullptr), next(nullptr) {} }; template @@ -65,8 +71,9 @@ class LFUCache { void push(int freq, CacheNode *node) { // if freq is not present, then make a new list with node as the head as // well as tail. - if (!freq_map.count(key)) { + if (!freq_map.count(freq)) { freq_map[freq] = {node, node}; + return; } std::pair *, CacheNode *> &p = freq_map[freq]; @@ -116,7 +123,7 @@ class LFUCache { } } push(freq + 1, node); - p_node.second++; + ++p_node.second; } /** @@ -177,7 +184,7 @@ class LFUCache { */ V get(K key) { if (!node_map.count(key)) { - throw std::exception("key is not present in the cache"); + throw std::runtime_error("key is not present in the cache"); } // increase the frequency and return the value @@ -190,19 +197,19 @@ class LFUCache { * @brief Returns the number of items present in the cache. * @return number of items in the cache */ - int size() { return node_map.size(); } + int size() const { return node_map.size(); } /** * @brief Returns the total capacity of the cache * @return Total capacity of the cache */ - int capacity() { return _capacity; } + int capacity() const { return _capacity; } /** * @brief returns true if the cache is empty, false otherwise. * @return true if the cache is empty, false otherwise. */ - bool empty() { return node_map.size() == 0; } + bool empty() const { return node_map.empty(); } /** * @brief destructs the cache, iterates on the map and deletes every node @@ -212,8 +219,80 @@ class LFUCache { auto it = node_map.begin(); while (it != node_map.end()) { delete it->second.first; - it++; + ++it; } } }; } // namespace Cache +} // namespace others + +/** + * @brief A simple test case + * @return void + */ +void test1() { + others::Cache::LFUCache cache(5); + assert(cache.size() == 0); + assert(cache.capacity() == 5); + assert(cache.empty()); + cache.put(1, 10); + cache.put(2, 20); + + assert(cache.size() == 2); + assert(cache.capacity() == 5); + assert(!cache.empty()); + + cache.put(3, 30); + cache.put(4, 40); + cache.put(5, 50); + cache.put(6, 60); + + assert(cache.size() == 5); + assert(cache.capacity() == 5); + assert(!cache.empty()); + + std::cout << "test1 - passed\n"; +} + +/** + * @brief A simple test case + * @return void + */ +void test2() { + others::Cache::LFUCache cache(5); + cache.put(1, 10); + cache.put(2, 20); + cache.put(3, 30); + + assert(cache.get(1) == 10); + cache.get(2); + cache.get(3); + + cache.put(4, 40); + cache.put(5, 50); + cache.put(6, 60); + + assert(cache.get(5) == 50); + + std::cout << "test2 - passed\n"; +} + +/** + * @brief test method with 2 tests + * @return void + */ + +void test() { + test1(); + test2(); +} + +/** + * @brief main function + * @return 0 on exit + */ + +int main() { + test(); + return 0; +} \ No newline at end of file From 01438a6aec0bc1535e73ee80e3fb03903c0e1d73 Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 12:48:37 +0530 Subject: [PATCH 4/7] docs: document namespace and classes --- others/lfu_cache.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 44304ea6797..2a93c21110c 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -3,6 +3,7 @@ * @brief Implementation for [LFU Cache] * (https://en.wikipedia.org/wiki/Least_frequently_used) * + * @details * LFU discards the least frequently used value. if there are multiple items * with the same minimum frequency then, the least recently used among them is * discarded. Data structures used - doubly linked list and unordered_map(hash @@ -28,8 +29,17 @@ * @brief Other algorithms */ namespace others { + +/** + * @namespace + * @brief Cache algorithm + */ namespace Cache { +/** + * @class + * @brief Node for a doubly linked list with data, prev and next pointers + */ template class D_Node { public: @@ -43,15 +53,19 @@ class D_Node { template using CacheNode = D_Node>; +/** + * @class + * @brief LFUCache + */ template class LFUCache { std::unordered_map *, int>> - node_map; // maps the key to the node address and frequency + node_map; ///< maps the key to the node address and frequency std::unordered_map *, CacheNode *>> - freq_map; // maps the frequency to doubly linked list + freq_map; ///< maps the frequency to doubly linked list - int minFreq; // minimum frequency in the cache - int _capacity; // maximum capacity of the cache + int minFreq; ///< minimum frequency in the cache + int _capacity; ///< maximum capacity of the cache public: /** @@ -281,7 +295,6 @@ void test2() { * @brief test method with 2 tests * @return void */ - void test() { test1(); test2(); @@ -291,8 +304,7 @@ void test() { * @brief main function * @return 0 on exit */ - int main() { test(); return 0; -} \ No newline at end of file +} From 85be2b4f032c99e0462fb2e4b8e356225c8c022e Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 12:49:17 +0530 Subject: [PATCH 5/7] test: modify tests to check negative numbers --- others/lfu_cache.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 2a93c21110c..6fc58579ae5 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -250,13 +250,13 @@ void test1() { assert(cache.capacity() == 5); assert(cache.empty()); cache.put(1, 10); - cache.put(2, 20); + cache.put(-2, 20); assert(cache.size() == 2); assert(cache.capacity() == 5); assert(!cache.empty()); - cache.put(3, 30); + cache.put(3, -30); cache.put(4, 40); cache.put(5, 50); cache.put(6, 60); @@ -274,11 +274,11 @@ void test1() { */ void test2() { others::Cache::LFUCache cache(5); - cache.put(1, 10); + cache.put(-1, -10); cache.put(2, 20); cache.put(3, 30); - assert(cache.get(1) == 10); + assert(cache.get(-1) == -10); cache.get(2); cache.get(3); From 0333f1902181ee2b79ea68bc3c07aa758d28f658 Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sat, 5 Oct 2024 19:58:48 +0530 Subject: [PATCH 6/7] docs: document template params and class data members --- others/lfu_cache.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 6fc58579ae5..37756149a94 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -21,8 +21,8 @@ */ #include // for assert -#include // for IO operations -#include // for hash map +#include // for std::cout +#include // for std::unordered_map /** * @namespace @@ -39,13 +39,14 @@ namespace Cache { /** * @class * @brief Node for a doubly linked list with data, prev and next pointers + * @tparam T type of the data of the node */ template class D_Node { public: - T data; - D_Node *prev; - D_Node *next; + T data; ///< data of the node + D_Node *prev; ///< previous node in the doubly linked list + D_Node *next; ///< next node in the doubly linked list explicit D_Node(T data) : data(data), prev(nullptr), next(nullptr) {} }; @@ -56,6 +57,8 @@ using CacheNode = D_Node>; /** * @class * @brief LFUCache + * @tparam K type of key in the LFU + * @tparam V type of value in the LFU */ template class LFUCache { @@ -103,7 +106,7 @@ class LFUCache { * @brief increase the frequency of node and push it in the respective list. * @param p_node the node to be updated */ - void inc_freq(std::pair *, int> &p_node) { + void increase_frequency(std::pair *, int> &p_node) { CacheNode *node = p_node.first; int freq = p_node.second; @@ -172,7 +175,7 @@ class LFUCache { // update the value if key already exists if (node_map.count(key)) { node_map[key].first->data.second = value; - inc_freq(node_map[key]); + increase_frequency(node_map[key]); return; } @@ -203,7 +206,7 @@ class LFUCache { // increase the frequency and return the value V value = node_map[key].first->data.second; - inc_freq(node_map[key]); + increase_frequency(node_map[key]); return value; } @@ -292,7 +295,7 @@ void test2() { } /** - * @brief test method with 2 tests + * @brief self test implementation * @return void */ void test() { @@ -305,6 +308,6 @@ void test() { * @return 0 on exit */ int main() { - test(); + test(); // run the self test implementation return 0; } From 97ad610fd5130b23e4d21b73a45b3627da7d434c Mon Sep 17 00:00:00 2001 From: deDSeC00720 Date: Sun, 6 Oct 2024 13:35:10 +0530 Subject: [PATCH 7/7] test: make test func static and move tests in the same func --- others/lfu_cache.cpp | 59 +++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/others/lfu_cache.cpp b/others/lfu_cache.cpp index 37756149a94..d893a96599d 100644 --- a/others/lfu_cache.cpp +++ b/others/lfu_cache.cpp @@ -244,63 +244,54 @@ class LFUCache { } // namespace others /** - * @brief A simple test case + * @brief self test implementation * @return void */ -void test1() { +static void test() { others::Cache::LFUCache cache(5); + + // test the initial state of the cache assert(cache.size() == 0); assert(cache.capacity() == 5); assert(cache.empty()); + + // test insertion in the cache cache.put(1, 10); cache.put(-2, 20); + // test the state of cache after inserting some items assert(cache.size() == 2); assert(cache.capacity() == 5); assert(!cache.empty()); - cache.put(3, -30); + // test getting items from the cache + assert(cache.get(1) == 10); + assert(cache.get(-2) == 20); + + cache.put(-3, -30); cache.put(4, 40); - cache.put(5, 50); + cache.put(5, -50); cache.put(6, 60); + // test the state after inserting more items than the capacity assert(cache.size() == 5); assert(cache.capacity() == 5); assert(!cache.empty()); - std::cout << "test1 - passed\n"; -} + // test retrieval of all items in the cache + assert(cache.get(1) == 10); + assert(cache.get(-2) == 20); -/** - * @brief A simple test case - * @return void - */ -void test2() { - others::Cache::LFUCache cache(5); - cache.put(-1, -10); - cache.put(2, 20); - cache.put(3, 30); + // fetching -3 throws runtime_error + // as -3 was evicted being the least frequently used + // when 6 was added + // assert(cache.get(-3) == -30); - assert(cache.get(-1) == -10); - cache.get(2); - cache.get(3); + assert(cache.get(4) == 40); + assert(cache.get(5) == -50); + assert(cache.get(6) == 60); - cache.put(4, 40); - cache.put(5, 50); - cache.put(6, 60); - - assert(cache.get(5) == 50); - - std::cout << "test2 - passed\n"; -} - -/** - * @brief self test implementation - * @return void - */ -void test() { - test1(); - test2(); + std::cout << "test - passed\n"; } /**