diff --git a/dynamic_programming/Unbounded_0_1_Knapsack.cpp b/dynamic_programming/Unbounded_0_1_Knapsack.cpp new file mode 100644 index 00000000000..96588fe3936 --- /dev/null +++ b/dynamic_programming/Unbounded_0_1_Knapsack.cpp @@ -0,0 +1,151 @@ +/** + * @file + * @brief Implementation of the Unbounded 0/1 Knapsack Problem + * + * @details + * The Unbounded 0/1 Knapsack problem allows taking unlimited quantities of each item. + * The goal is to maximize the total value without exceeding the given knapsack capacity. + * Unlike the 0/1 knapsack, where each item can be taken only once, in this variation, + * any item can be picked any number of times as long as the total weight stays within + * the knapsack's capacity. + * + * Given a set of N items, each with a weight and a value, represented by the arrays + * `wt` and `val` respectively, and a knapsack with a weight limit W, the task is to + * fill the knapsack to maximize the total value. + * + * @note weight and value of items is greater than zero + * + * ### Algorithm + * The approach uses dynamic programming to build a solution iteratively. + * A 2D array is used for memoization to store intermediate results, allowing + * the function to avoid redundant calculations. + * + * @author [Sanskruti Yeole](https://github.com/yeolesanskruti) + * @see dynamic_programming/0_1_knapsack.cpp + */ + +#include // Standard input-output stream +#include // Standard library for using dynamic arrays (vectors) +#include // For using assert function to validate test cases +#include // For fixed-width integer types like std::uint16_t + +/** + * @namespace dynamic_programming + * @brief Namespace for dynamic programming algorithms + */ +namespace dynamic_programming { + +/** + * @namespace Knapsack + * @brief Implementation of unbounded 0-1 knapsack problem + */ +namespace unbounded_knapsack { + +/** + * @brief Recursive function to calculate the maximum value obtainable using + * an unbounded knapsack approach. + * + * @param i Current index in the value and weight vectors. + * @param W Remaining capacity of the knapsack. + * @param val Vector of values corresponding to the items. + * @note "val" data type can be changed according to the size of the input. + * @param wt Vector of weights corresponding to the items. + * @note "wt" data type can be changed according to the size of the input. + * @param dp 2D vector for memoization to avoid redundant calculations. + * @return The maximum value that can be obtained for the given index and capacity. + */ +std::uint16_t KnapSackFilling(std::uint16_t i, std::uint16_t W, + const std::vector& val, + const std::vector& wt, + std::vector>& dp) { + if (i == 0) { + if (wt[0] <= W) { + return (W / wt[0]) * val[0]; // Take as many of the first item as possible + } else { + return 0; // Can't take the first item + } + } + if (dp[i][W] != -1) return dp[i][W]; // Return result if available + + int nottake = KnapSackFilling(i - 1, W, val, wt, dp); // Value without taking item i + int take = 0; + if (W >= wt[i]) { + take = val[i] + KnapSackFilling(i, W - wt[i], val, wt, dp); // Value taking item i + } + return dp[i][W] = std::max(take, nottake); // Store and return the maximum value +} + +/** + * @brief Wrapper function to initiate the unbounded knapsack calculation. + * + * @param N Number of items. + * @param W Maximum weight capacity of the knapsack. + * @param val Vector of values corresponding to the items. + * @param wt Vector of weights corresponding to the items. + * @return The maximum value that can be obtained for the given capacity. + */ +std::uint16_t unboundedKnapsack(std::uint16_t N, std::uint16_t W, + const std::vector& val, + const std::vector& wt) { + if(N==0)return 0; // Expect 0 since no items + std::vector> dp(N, std::vector(W + 1, -1)); // Initialize memoization table + return KnapSackFilling(N - 1, W, val, wt, dp); // Start the calculation +} + +} // unbounded_knapsack + +} // dynamic_programming + +/** + * @brief self test implementation + * @return void + */ +static void tests() { + // Test Case 1 + std::uint16_t N1 = 4; // Number of items + std::vector wt1 = {1, 3, 4, 5}; // Weights of the items + std::vector val1 = {6, 1, 7, 7}; // Values of the items + std::uint16_t W1 = 8; // Maximum capacity of the knapsack + // Test the function and assert the expected output + assert(unboundedKnapsack(N1, W1, val1, wt1) == 48); + std::cout << "Maximum Knapsack value " << unboundedKnapsack(N1, W1, val1, wt1) << std::endl; + + // Test Case 2 + std::uint16_t N2 = 3; // Number of items + std::vector wt2 = {10, 20, 30}; // Weights of the items + std::vector val2 = {60, 100, 120}; // Values of the items + std::uint16_t W2 = 5; // Maximum capacity of the knapsack + // Test the function and assert the expected output + assert(unboundedKnapsack(N2, W2, val2, wt2) == 0); + std::cout << "Maximum Knapsack value " << unboundedKnapsack(N2, W2, val2, wt2) << std::endl; + + // Test Case 3 + std::uint16_t N3 = 3; // Number of items + std::vector wt3 = {2, 4, 6}; // Weights of the items + std::vector val3 = {5, 11, 13};// Values of the items + std::uint16_t W3 = 27;// Maximum capacity of the knapsack + // Test the function and assert the expected output + assert(unboundedKnapsack(N3, W3, val3, wt3) == 27); + std::cout << "Maximum Knapsack value " << unboundedKnapsack(N3, W3, val3, wt3) << std::endl; + + // Test Case 4 + std::uint16_t N4 = 0; // Number of items + std::vector wt4 = {}; // Weights of the items + std::vector val4 = {}; // Values of the items + std::uint16_t W4 = 10; // Maximum capacity of the knapsack + assert(unboundedKnapsack(N4, W4, val4, wt4) == 0); + std::cout << "Maximum Knapsack value for empty arrays: " << unboundedKnapsack(N4, W4, val4, wt4) << std::endl; + + std::cout << "All test cases passed!" << std::endl; + +} + +/** + * @brief main function + * @return 0 on successful exit + */ +int main() { + tests(); // Run self test implementation + return 0; +} + diff --git a/greedy_algorithms/binary_addition.cpp b/greedy_algorithms/binary_addition.cpp new file mode 100644 index 00000000000..4a365323815 --- /dev/null +++ b/greedy_algorithms/binary_addition.cpp @@ -0,0 +1,119 @@ +/** + * @file binary_addition.cpp + * @brief Adds two binary numbers and outputs resulting string + * + * @details The algorithm for adding two binary strings works by processing them + * from right to left, similar to manual addition. It starts by determining the + * longer string's length to ensure both strings are fully traversed. For each + * pair of corresponding bits and any carry from the previous addition, it + * calculates the sum. If the sum exceeds 1, a carry is generated for the next + * bit. The results for each bit are collected in a result string, which is + * reversed at the end to present the final binary sum correctly. Additionally, + * the function validates the input to ensure that only valid binary strings + * (containing only '0' and '1') are processed. If invalid input is detected, + * it returns an empty string. + * @author [Muhammad Junaid Khalid](https://github.com/mjk22071998) + */ + +#include /// for reverse function +#include /// for tests +#include /// for input and outputs +#include /// for string class + +/** + * @namespace + * @brief Greedy Algorithms + */ +namespace greedy_algorithms { +/** + * @brief A class to perform binary addition of two binary strings. + */ +class BinaryAddition { + public: + /** + * @brief Adds two binary strings and returns the result as a binary string. + * @param a The first binary string. + * @param b The second binary string. + * @return The sum of the two binary strings as a binary string, or an empty + * string if either input string contains non-binary characters. + */ + std::string addBinary(const std::string& a, const std::string& b) { + if (!isValidBinaryString(a) || !isValidBinaryString(b)) { + return ""; // Return empty string if input contains non-binary + // characters + } + + std::string result; + int carry = 0; + int maxLength = std::max(a.size(), b.size()); + + // Traverse both strings from the end to the beginning + for (int i = 0; i < maxLength; ++i) { + // Get the current bits from both strings, if available + int bitA = (i < a.size()) ? (a[a.size() - 1 - i] - '0') : 0; + int bitB = (i < b.size()) ? (b[b.size() - 1 - i] - '0') : 0; + + // Calculate the sum of bits and carry + int sum = bitA + bitB + carry; + carry = sum / 2; // Determine the carry for the next bit + result.push_back((sum % 2) + + '0'); // Append the sum's current bit to result + } + if (carry) { + result.push_back('1'); + } + std::reverse(result.begin(), result.end()); + return result; + } + + private: + /** + * @brief Validates whether a string contains only binary characters (0 or 1). + * @param str The string to validate. + * @return true if the string is binary, false otherwise. + */ + bool isValidBinaryString(const std::string& str) const { + return std::all_of(str.begin(), str.end(), + [](char c) { return c == '0' || c == '1'; }); + } +}; +} // namespace greedy_algorithms + +/** + * @brief run self test implementation. + * @returns void + */ +static void tests() { + greedy_algorithms::BinaryAddition binaryAddition; + + // Valid binary string tests + assert(binaryAddition.addBinary("1010", "1101") == "10111"); + assert(binaryAddition.addBinary("1111", "1111") == "11110"); + assert(binaryAddition.addBinary("101", "11") == "1000"); + assert(binaryAddition.addBinary("0", "0") == "0"); + assert(binaryAddition.addBinary("1111", "1111") == "11110"); + assert(binaryAddition.addBinary("0", "10101") == "10101"); + assert(binaryAddition.addBinary("10101", "0") == "10101"); + assert(binaryAddition.addBinary("101010101010101010101010101010", + "110110110110110110110110110110") == + "1100001100001100001100001100000"); + assert(binaryAddition.addBinary("1", "11111111") == "100000000"); + assert(binaryAddition.addBinary("10101010", "01010101") == "11111111"); + + // Invalid binary string tests (should return empty string) + assert(binaryAddition.addBinary("10102", "1101") == ""); + assert(binaryAddition.addBinary("ABC", "1101") == ""); + assert(binaryAddition.addBinary("1010", "1102") == ""); + assert(binaryAddition.addBinary("111", "1x1") == ""); + assert(binaryAddition.addBinary("1x1", "111") == ""); + assert(binaryAddition.addBinary("1234", "1101") == ""); +} + +/** + * @brief main function + * @returns 0 on successful exit + */ +int main() { + tests(); /// To execute tests + return 0; +} diff --git a/math/sieve_of_eratosthenes.cpp b/math/sieve_of_eratosthenes.cpp index e011b6c0018..e003706d12b 100644 --- a/math/sieve_of_eratosthenes.cpp +++ b/math/sieve_of_eratosthenes.cpp @@ -1,6 +1,7 @@ /** * @file - * @brief Get list of prime numbers using Sieve of Eratosthenes + * @brief Prime Numbers using [Sieve of + * Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) * @details * Sieve of Eratosthenes is an algorithm that finds all the primes * between 2 and N. @@ -11,21 +12,39 @@ * @see primes_up_to_billion.cpp prime_numbers.cpp */ -#include -#include -#include +#include /// for assert +#include /// for IO operations +#include /// for std::vector /** - * This is the function that finds the primes and eliminates the multiples. + * @namespace math + * @brief Mathematical algorithms + */ +namespace math { +/** + * @namespace sieve_of_eratosthenes + * @brief Functions for finding Prime Numbers using Sieve of Eratosthenes + */ +namespace sieve_of_eratosthenes { +/** + * @brief Function to sieve out the primes + * @details + * This function finds all the primes between 2 and N using the Sieve of + * Eratosthenes algorithm. It starts by assuming all numbers (except zero and + * one) are prime and then iteratively marks the multiples of each prime as + * non-prime. + * * Contains a common optimization to start eliminating multiples of * a prime p starting from p * p since all of the lower multiples * have been already eliminated. - * @param N number of primes to check - * @return is_prime a vector of `N + 1` booleans identifying if `i`^th number is a prime or not + * @param N number till which primes are to be found + * @return is_prime a vector of `N + 1` booleans identifying if `i`^th number is + * a prime or not */ std::vector sieve(uint32_t N) { - std::vector is_prime(N + 1, true); - is_prime[0] = is_prime[1] = false; + std::vector is_prime(N + 1, true); // Initialize all as prime numbers + is_prime[0] = is_prime[1] = false; // 0 and 1 are not prime numbers + for (uint32_t i = 2; i * i <= N; i++) { if (is_prime[i]) { for (uint32_t j = i * i; j <= N; j += i) { @@ -37,9 +56,10 @@ std::vector sieve(uint32_t N) { } /** - * This function prints out the primes to STDOUT - * @param N number of primes to check - * @param is_prime a vector of `N + 1` booleans identifying if `i`^th number is a prime or not + * @brief Function to print the prime numbers + * @param N number till which primes are to be found + * @param is_prime a vector of `N + 1` booleans identifying if `i`^th number is + * a prime or not */ void print(uint32_t N, const std::vector &is_prime) { for (uint32_t i = 2; i <= N; i++) { @@ -50,23 +70,53 @@ void print(uint32_t N, const std::vector &is_prime) { std::cout << std::endl; } +} // namespace sieve_of_eratosthenes +} // namespace math + /** - * Test implementations + * @brief Self-test implementations + * @return void */ -void tests() { - // 0 1 2 3 4 5 6 7 8 9 10 - std::vector ans{false, false, true, true, false, true, false, true, false, false, false}; - assert(sieve(10) == ans); +static void tests() { + std::vector is_prime_1 = + math::sieve_of_eratosthenes::sieve(static_cast(10)); + std::vector is_prime_2 = + math::sieve_of_eratosthenes::sieve(static_cast(20)); + std::vector is_prime_3 = + math::sieve_of_eratosthenes::sieve(static_cast(100)); + + std::vector expected_1{false, false, true, true, false, true, + false, true, false, false, false}; + assert(is_prime_1 == expected_1); + + std::vector expected_2{false, false, true, true, false, true, + false, true, false, false, false, true, + false, true, false, false, false, true, + false, true, false}; + assert(is_prime_2 == expected_2); + + std::vector expected_3{ + false, false, true, true, false, true, false, true, false, false, + false, true, false, true, false, false, false, true, false, true, + false, false, false, true, false, false, false, false, false, true, + false, true, false, false, false, false, false, true, false, false, + false, true, false, true, false, false, false, true, false, false, + false, false, false, true, false, false, false, false, false, true, + false, true, false, false, false, false, false, true, false, false, + false, true, false, true, false, false, false, false, false, true, + false, false, false, true, false, false, false, false, false, true, + false, false, false, false, false, false, false, true, false, false, + false}; + assert(is_prime_3 == expected_3); + + std::cout << "All tests have passed successfully!\n"; } /** - * Main function + * @brief Main function + * @returns 0 on exit */ int main() { tests(); - - uint32_t N = 100; - std::vector is_prime = sieve(N); - print(N, is_prime); return 0; } diff --git a/others/lru_cache2.cpp b/others/lru_cache2.cpp new file mode 100644 index 00000000000..aaa6c943fbf --- /dev/null +++ b/others/lru_cache2.cpp @@ -0,0 +1,277 @@ +/** + * @file + * @brief Implementation for [LRU Cache] + * (https://en.wikipedia.org/wiki/Cache_replacement_policies#:~:text=Least%20Recently%20Used%20(LRU)) + * + * @details + * LRU discards the least recently used value. + * Data structures used - doubly linked list and unordered_map + * + * unordered_map maps the key to the address of the node of the linked list. + * If the element is accessed, the element is moved to the beginning of the + * linked list. + * + * When the cache is full, the last element in the linked list is popped. + * + * @author [Karan Sharma](https://github.com/deDSeC00720) + */ + +#include // for assert +#include // for std::uint32_t +#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 LRUCache + * @tparam K type of key in the LRU + * @tparam V type of value in the LRU + */ +template +class LRUCache { + CacheNode *head; ///< head of the doubly linked list + CacheNode *tail; ///< tail of the doubly linked list + std::uint32_t _capacity; ///< maximum capacity of the cache + + std::unordered_map *> + node_map; ///< maps the key to the node address + + public: + /** + * @brief Constructor, Initialize the head and tail pointers to nullptr and + * initialize the _capacity of the cache + * @param _capacity Total capacity of the cache + */ + explicit LRUCache(int _capacity) + : head(nullptr), tail(nullptr), _capacity(_capacity) {} + + private: + /** + * @brief push the node to the front of the linked list. + * @param node_ptr the node to be pushed + */ + void push_front(CacheNode *node_ptr) { + if (!head) { + head = node_ptr; + tail = node_ptr; + return; + } + + node_ptr->next = head; + head->prev = node_ptr; + head = node_ptr; + } + + /** + * @brief move the existing node in the list to the beginning of the list. + * @param node_ptr node to be moved to the beginning. + */ + void make_recent(CacheNode *node_ptr) { + if (head == node_ptr) { + return; + } + + CacheNode *prev = node_ptr->prev; + CacheNode *next = node_ptr->next; + + prev->next = next; + if (next) { + next->prev = prev; + } else { + tail = prev; + } + + node_ptr->prev = nullptr; + node_ptr->next = nullptr; + push_front(node_ptr); + } + + /** + * @brief pop the last node in the linked list. + */ + void pop_back() { + if (!head) { + return; + } + if (head == tail) { + delete head; + head = nullptr; + tail = nullptr; + return; + } + + CacheNode *temp = tail; + tail = tail->prev; + tail->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]->data.second = value; + make_recent(node_map[key]); + return; + } + + // if the cache is full + // remove the least recently used item + if (node_map.size() == _capacity) { + node_map.erase(tail->data.first); + pop_back(); + } + + CacheNode *newNode = new CacheNode({key, value}); + + node_map[key] = newNode; + push_front(newNode); + } + + /** + * @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"); + } + + // move node to the beginning of the list + V value = node_map[key]->data.second; + make_recent(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 whether the cache is empty or not + * @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. + */ + ~LRUCache() { + auto it = node_map.begin(); + while (it != node_map.end()) { + delete it->second; + ++it; + } + } +}; +} // namespace Cache +} // namespace others + +/** + * @brief self test implementations + * @return void + */ +static void test() { + others::Cache::LRUCache 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()); + + // fetching 1 throws runtime_error + // as 1 was evicted being the least recently used + // when 6 was added + try { + cache.get(1); + } catch (const std::runtime_error &e) { + assert(std::string(e.what()) == "key is not present in the cache"); + } + + // test retrieval of all items in the cache + assert(cache.get(-2) == 20); + 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; +} diff --git a/search/Longest_Increasing_Subsequence_using_binary_search.cpp b/search/longest_increasing_subsequence_using_binary_search.cpp similarity index 100% rename from search/Longest_Increasing_Subsequence_using_binary_search.cpp rename to search/longest_increasing_subsequence_using_binary_search.cpp