From fddedd88647315964a6a3b16bb0caf55ae8a14b5 Mon Sep 17 00:00:00 2001 From: Nguyen Phuc Chuong <72879387+hollowcrust@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:26:25 +0800 Subject: [PATCH 1/2] fix: Adding documentations, tests, and amending algorithm for gcd_of_n_numbers.cpp (#2766) * Update gcd_of_n_numbers.cpp * Update gcd_of_n_numbers.cpp Reformatting code, comment and test cases, change array data type. * Update gcd_of_n_numbers.cpp * Update gcd_of_n_numbers.cpp * Update gcd_of_n_numbers.cpp * Update gcd_of_n_numbers.cpp --- math/gcd_of_n_numbers.cpp | 131 +++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 29 deletions(-) diff --git a/math/gcd_of_n_numbers.cpp b/math/gcd_of_n_numbers.cpp index 92968ff12..45ba0b074 100644 --- a/math/gcd_of_n_numbers.cpp +++ b/math/gcd_of_n_numbers.cpp @@ -1,41 +1,114 @@ /** * @file - * @brief This program aims at calculating the GCD of n numbers by division - * method + * @brief This program aims at calculating the GCD of n numbers + * + * @details + * The GCD of n numbers can be calculated by + * repeatedly calculating the GCDs of pairs of numbers + * i.e. \f$\gcd(a, b, c)\f$ = \f$\gcd(\gcd(a, b), c)\f$ + * Euclidean algorithm helps calculate the GCD of each pair of numbers + * efficiently * * @see gcd_iterative_euclidean.cpp, gcd_recursive_euclidean.cpp */ -#include +#include /// for std::abs +#include /// for std::array +#include /// for assert +#include /// for IO operations -/** Compute GCD using division algorithm - * - * @param[in] a array of integers to compute GCD for - * @param[in] n number of integers in array `a` +/** + * @namespace math + * @brief Maths algorithms */ -int gcd(int *a, int n) { - int j = 1; // to access all elements of the array starting from 1 - int gcd = a[0]; - while (j < n) { - if (a[j] % gcd == 0) // value of gcd is as needed so far - j++; // so we check for next element - else - gcd = a[j] % gcd; // calculating GCD by division method +namespace math { +/** + * @namespace gcd_of_n_numbers + * @brief Compute GCD of numbers in an array + */ +namespace gcd_of_n_numbers { +/** + * @brief Function to compute GCD of 2 numbers x and y + * @param x First number + * @param y Second number + * @return GCD of x and y via recursion + */ +int gcd_two(int x, int y) { + // base cases + if (y == 0) { + return x; + } + if (x == 0) { + return y; + } + return gcd_two(y, x % y); // Euclidean method +} + +/** + * @brief Function to check if all elements in the array are 0 + * @param a Array of numbers + * @return 'True' if all elements are 0 + * @return 'False' if not all elements are 0 + */ +template +bool check_all_zeros(const std::array &a) { + // Use std::all_of to simplify zero-checking + return std::all_of(a.begin(), a.end(), [](int x) { return x == 0; }); +} + +/** + * @brief Main program to compute GCD using the Euclidean algorithm + * @param a Array of integers to compute GCD for + * @return GCD of the numbers in the array or std::nullopt if undefined + */ +template +int gcd(const std::array &a) { + // GCD is undefined if all elements in the array are 0 + if (check_all_zeros(a)) { + return -1; // Use std::optional to represent undefined GCD + } + + // divisors can be negative, we only want the positive value + int result = std::abs(a[0]); + for (std::size_t i = 1; i < n; ++i) { + result = gcd_two(result, std::abs(a[i])); + if (result == 1) { + break; // Further computations still result in gcd of 1 } - return gcd; + } + return result; +} +} // namespace gcd_of_n_numbers +} // namespace math + +/** + * @brief Self-test implementation + * @return void + */ +static void test() { + std::array array_1 = {0}; + std::array array_2 = {1}; + std::array array_3 = {0, 2}; + std::array array_4 = {-60, 24, 18}; + std::array array_5 = {100, -100, -100, 200}; + std::array array_6 = {0, 0, 0, 0, 0}; + std::array array_7 = {10350, -24150, 0, 17250, 37950, -127650, 51750}; + std::array array_8 = {9500000, -12121200, 0, 4444, 0, 0, 123456789}; + + assert(math::gcd_of_n_numbers::gcd(array_1) == -1); + assert(math::gcd_of_n_numbers::gcd(array_2) == 1); + assert(math::gcd_of_n_numbers::gcd(array_3) == 2); + assert(math::gcd_of_n_numbers::gcd(array_4) == 6); + assert(math::gcd_of_n_numbers::gcd(array_5) == 100); + assert(math::gcd_of_n_numbers::gcd(array_6) == -1); + assert(math::gcd_of_n_numbers::gcd(array_7) == 3450); + assert(math::gcd_of_n_numbers::gcd(array_8) == 1); } -/** Main function */ +/** + * @brief Main function + * @return 0 on exit + */ int main() { - int n; - std::cout << "Enter value of n:" << std::endl; - std::cin >> n; - int *a = new int[n]; - int i; - std::cout << "Enter the n numbers:" << std::endl; - for (i = 0; i < n; i++) std::cin >> a[i]; - - std::cout << "GCD of entered n numbers:" << gcd(a, n) << std::endl; - - delete[] a; - return 0; + test(); // run self-test implementation + return 0; } From 821d20c33a55cc3a70973da1dd2dd675bbc1e250 Mon Sep 17 00:00:00 2001 From: deDSeC00720 <62394683+deDSeC00720@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:02:52 +0530 Subject: [PATCH 2/2] feat: LFU (Least frequently used) cache (#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> --- others/lfu_cache.cpp | 304 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 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 000000000..d893a9659 --- /dev/null +++ b/others/lfu_cache.cpp @@ -0,0 +1,304 @@ +/** + * @file + * @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 + * 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 // for assert +#include // for std::cout +#include // for std::unordered_map + +/** + * @namespace + * @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 + * @tparam T type of the data of the node + */ +template +class D_Node { + public: + 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) {} +}; + +template +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 { + std::unordered_map *, int>> + node_map; ///< maps the key to the node address and frequency + std::unordered_map *, CacheNode *>> + freq_map; ///< maps the frequency to doubly linked list + + int minFreq; ///< minimum frequency in the cache + int _capacity; ///< maximum capacity of the cache + + public: + /** + * @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(freq)) { + freq_map[freq] = {node, node}; + return; + } + + 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 increase_frequency(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; + 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; + } + + /** + * @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; + delete temp; + } + + 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; + increase_frequency(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::runtime_error("key is not present in the cache"); + } + + // increase the frequency and return the value + V value = node_map[key].first->data.second; + increase_frequency(node_map[key]); + return value; + } + + /** + * @brief Returns the number of items present in the cache. + * @return number of items in the cache + */ + int size() const { return node_map.size(); } + + /** + * @brief Returns the total capacity of the cache + * @return Total capacity of the cache + */ + 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() const { return node_map.empty(); } + + /** + * @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()) { + delete it->second.first; + ++it; + } + } +}; +} // namespace Cache +} // namespace others + +/** + * @brief self test implementation + * @return void + */ +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()); + + // 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(6, 60); + + // test the state after inserting more items than the capacity + assert(cache.size() == 5); + assert(cache.capacity() == 5); + assert(!cache.empty()); + + // test retrieval of all items in the cache + assert(cache.get(1) == 10); + assert(cache.get(-2) == 20); + + // 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(4) == 40); + assert(cache.get(5) == -50); + assert(cache.get(6) == 60); + + std::cout << "test - passed\n"; +} + +/** + * @brief main function + * @return 0 on exit + */ +int main() { + test(); // run the self test implementation + return 0; +}