diff --git a/.github/stale.yml b/.github/stale.yml index 0bbe23e5d..6506453ea 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -13,6 +13,7 @@ onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - "approved" + - "dont-close" # Set to true to ignore issues in a project (defaults to false) exemptProjects: false diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml new file mode 100644 index 000000000..aa3ddbd7f --- /dev/null +++ b/.github/workflows/codeql_analysis.yml @@ -0,0 +1,48 @@ +name: "CodeQL" +on: [push, pull_request] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@main + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@main + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@main + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@main diff --git a/DIRECTORY.md b/DIRECTORY.md index 239b9d539..c046be5c7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -54,6 +54,7 @@ * [Tree 234](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/data_structures/tree_234.cpp) * [Trie Modern](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/data_structures/trie_modern.cpp) * [Trie Tree](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/data_structures/trie_tree.cpp) + * [Trie Using Hashmap](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/data_structures/trie_using_hashmap.cpp) ## Dynamic Programming * [0 1 Knapsack](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/dynamic_programming/0_1_knapsack.cpp) @@ -161,6 +162,7 @@ * [Largest Power](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/largest_power.cpp) * [Lcm Sum](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/lcm_sum.cpp) * [Least Common Multiple](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/least_common_multiple.cpp) + * [Linear Recurrence Matrix](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/linear_recurrence_matrix.cpp) * [Magic Number](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/magic_number.cpp) * [Miller Rabin](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/miller_rabin.cpp) * [Modular Division](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/modular_division.cpp) diff --git a/README.md b/README.md index 9ebf34e65..393a7c579 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/C-Plus-Plus) -[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/TheAlgorithms/C-Plus-Plus.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/TheAlgorithms/C-Plus-Plus/context:cpp) +[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/TheAlgorithms/C-Plus-Plus.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/TheAlgorithms/C-Plus-Plus/context:cpp) +[![CodeQL CI](https://github.com/TheAlgorithms/C-Plus-Plus/actions/workflows/codeql_analysis.yml/badge.svg)](https://github.com/TheAlgorithms/C-Plus-Plus/actions/workflows/codeql_analysis.yml) [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms) [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/CONTRIBUTING.md) ![GitHub repo size](https://img.shields.io/github/repo-size/TheAlgorithms/C-Plus-Plus?color=red&style=flat-square) @@ -11,10 +12,9 @@ [![Income](https://img.shields.io/liberapay/receives/TheAlgorithms.svg?logo=liberapay)](https://liberapay.com/TheAlgorithms) [![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/TheAlgorithms/donate) - ## Overview -The repository is a collection of open-source implementation of a variety of algorithms implemented in C++ and licensed under [MIT License](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/LICENSE). The algorithms span a variety of topics from computer science, mathematics and statistics, data science, machine learning, engineering, etc.. The implementations and the associated documentation are meant to provide a learning resource for educators and students. Hence, one may find more than one implementation for the same objective but using a different algorithm strategies and optimizations. +The repository is a collection of open-source implementation of a variety of algorithms implemented in C++ and licensed under [MIT License](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/LICENSE). The algorithms span a variety of topics from computer science, mathematics and statistics, data science, machine learning, engineering, etc.. The implementations and the associated documentation are meant to provide a learning resource for educators and students. Hence, one may find more than one implementation for the same objective but using a different algorithm strategies and optimizations. ## Features diff --git a/data_structures/trie_using_hashmap.cpp b/data_structures/trie_using_hashmap.cpp new file mode 100644 index 000000000..5fb9602a9 --- /dev/null +++ b/data_structures/trie_using_hashmap.cpp @@ -0,0 +1,345 @@ +/** + * @file + * @author [Venkata Bharath](https://github.com/bharath000) + * @brief Implementation of [Trie](https://en.wikipedia.org/wiki/Trie) data + * structure using HashMap for different characters and method for predicting + * words based on prefix. + * @details The Trie data structure is implemented using unordered map to use + * memory optimally, predict_words method is developed to recommend words based + * on a given prefix along with other methods insert, delete, search, startwith + * in trie. + * @see trie_modern.cpp for difference + */ +#include /// for assert +#include /// for IO operations +#include /// for std::shared_ptr +#include /// for std::stack +#include /// for std::unordered_map +#include /// for std::vector + +/** + * @namespace data_structures + * @brief Data structures algorithms + */ +namespace data_structures { + +/** + * @namespace trie_using_hashmap + * @brief Functions for [Trie](https://en.wikipedia.org/wiki/Trie) data + * structure using hashmap implementation + */ +namespace trie_using_hashmap { + +/** + * @brief Trie class, implementation of trie using hashmap in each trie node + * for all the characters of char16_t(UTF-16)type with methods to insert, + * delete, search, start with and to recommend words based on a given + * prefix. + */ +class Trie { + private: + /** + * @brief struct representing a trie node. + */ + struct Node { + std::unordered_map> + children; ///< unordered map with key type char16_t and value is a + ///< shared pointer type of Node + bool word_end = false; ///< boolean variable to represent the node end + }; + + std::shared_ptr root_node = + std::make_shared(); ///< declaring root node of trie + + public: + ///< Constructor + Trie() = default; + + /** + * @brief insert the string into the trie + * @param word string to insert in the trie + */ + void insert(const std::string& word) { + std::shared_ptr curr = root_node; + for (char ch : word) { + if (curr->children.find(ch) == curr->children.end()) { + curr->children[ch] = std::make_shared(); + } + curr = curr->children[ch]; + } + + if (!curr->word_end && curr != root_node) { + curr->word_end = true; + } + } + + /** + * @brief search a word/string inside the trie + * @param word string to search for + * @returns `true` if found + * @returns `false` if not found + */ + bool search(const std::string& word) { + std::shared_ptr curr = root_node; + for (char ch : word) { + if (curr->children.find(ch) == curr->children.end()) { + return false; + } + curr = curr->children[ch]; + if (!curr) { + return false; + } + } + + if (curr->word_end) { + return true; + } else { + return false; + } + } + + /** + * @brief search a word/string that starts with a given prefix + * @param prefix string to search for + * @returns `true` if found + * @returns `false` if not found + */ + bool startwith(const std::string& prefix) { + std::shared_ptr curr = root_node; + for (char ch : prefix) { + if (curr->children.find(ch) == curr->children.end()) { + return false; + } + curr = curr->children[ch]; + } + return true; + } + + /** + * @brief delete a word/string from a trie + * @param word string to delete from trie + */ + void delete_word(std::string word) { + std::shared_ptr curr = root_node; + std::stack> nodes; + int cnt = 0; + for (char ch : word) { + if (curr->children.find(ch) == curr->children.end()) { + return; + } + if (curr->word_end) { + cnt++; + } + + nodes.push(curr->children[ch]); + curr = curr->children[ch]; + } + // Delete only when it's a word, and it has children after + // or prefix in the line + if (nodes.top()->word_end) { + nodes.top()->word_end = false; + } + // Delete only when it has no children after + // and also no prefix in the line + while (!(nodes.top()->word_end) && nodes.top()->children.empty()) { + nodes.pop(); + nodes.top()->children.erase(word.back()); + word.pop_back(); + } + } + + /** + * @brief helper function to predict/recommend words that starts with a + * given prefix from the end of prefix's node iterate through all the child + * nodes by recursively appending all the possible words below the trie + * @param prefix string to recommend the words + * @param element node at the end of the given prefix + * @param results list to store the all possible words + * @returns list of recommended words + */ + std::vector get_all_words(std::vector results, + const std::shared_ptr& element, + std::string prefix) { + if (element->word_end) { + results.push_back(prefix); + } + if (element->children.empty()) { + return results; + } + for (auto const& x : element->children) { + std::string key = ""; + key = x.first; + prefix += key; + + results = + get_all_words(results, element->children[x.first], prefix); + + prefix.pop_back(); + } + + return results; + } + + /** + * @brief predict/recommend a word that starts with a given prefix + * @param prefix string to search for + * @returns list of recommended words + */ + std::vector predict_words(const std::string& prefix) { + std::vector result; + std::shared_ptr curr = root_node; + // traversing until the end of the given prefix in trie + + for (char ch : prefix) { + if (curr->children.find(ch) == curr->children.end()) { + return result; + } + + curr = curr->children[ch]; + } + + // if the given prefix is the only word without children + if (curr->word_end && curr->children.empty()) { + result.push_back(prefix); + return result; + } + + result = get_all_words( + result, curr, + prefix); ///< iteratively and recursively get the recommended words + + return result; + } +}; +} // namespace trie_using_hashmap +} // namespace data_structures + +/** + * @brief Self-test implementations + * @returns void + */ +static void test() { + data_structures::trie_using_hashmap::Trie obj; + // Inserting data into trie using the insert + // method and testing it with search method + obj.insert("app"); + obj.insert("abscond"); + obj.insert("about"); + obj.insert("apps"); + obj.insert("apen"); + obj.insert("apples"); + obj.insert("apple"); + obj.insert("approach"); + obj.insert("bus"); + obj.insert("buses"); + obj.insert("Apple"); + obj.insert("Bounce"); + + assert(!obj.search("appy")); + std::cout << "appy is not a word in trie" << std::endl; + + assert(!obj.search("car")); + std::cout << "car is not a word in trie" << std::endl; + assert(obj.search("app")); + assert(obj.search("apple")); + assert(obj.search("apples")); + assert(obj.search("apps")); + assert(obj.search("apen")); + assert(obj.search("approach")); + assert(obj.search("about")); + assert(obj.search("abscond")); + assert(obj.search("bus")); + assert(obj.search("buses")); + assert(obj.search("Bounce")); + assert(obj.search("Apple")); + + std::cout << "All the Inserted words are present in the trie" << std::endl; + + // test for startwith prefix method + assert(!obj.startwith("approachs")); + assert(obj.startwith("approach")); + assert(obj.startwith("about")); + assert(!obj.startwith("appy")); + assert(obj.startwith("abscond")); + assert(obj.startwith("bus")); + assert(obj.startwith("buses")); + assert(obj.startwith("Bounce")); + assert(obj.startwith("Apple")); + assert(obj.startwith("abs")); + assert(obj.startwith("b")); + assert(obj.startwith("bus")); + assert(obj.startwith("Bo")); + assert(obj.startwith("A")); + assert(!obj.startwith("Ca")); + + assert(!obj.startwith("C")); + + std::cout << "All the tests passed for startwith method" << std::endl; + + // test for predict_words/recommendation of words based on a given prefix + + std::vector pred_words = obj.predict_words("a"); + + for (const std::string& str : obj.predict_words("a")) { + std::cout << str << std::endl; + } + assert(pred_words.size() == 8); + std::cout << "Returned all words that start with prefix a " << std::endl; + pred_words = obj.predict_words("app"); + + for (const std::string& str : pred_words) { + std::cout << str << std::endl; + } + + assert(pred_words.size() == 5); + std::cout << "Returned all words that start with prefix app " << std::endl; + pred_words = obj.predict_words("A"); + + for (const std::string& str : pred_words) { + std::cout << str << std::endl; + } + + assert(pred_words.size() == 1); + std::cout << "Returned all words that start with prefix A " << std::endl; + pred_words = obj.predict_words("bu"); + + for (const std::string& str : pred_words) { + std::cout << str << std::endl; + } + + assert(pred_words.size() == 2); + std::cout << "Returned all words that start with prefix bu " << std::endl; + + // tests for delete method + + obj.delete_word("app"); + assert(!obj.search("app")); + std::cout << "word app is deleted sucessful" << std::endl; + + pred_words = obj.predict_words("app"); + for (const std::string& str : pred_words) { + std::cout << str << std::endl; + } + assert(pred_words.size() == 4); + std::cout << "app is deleted sucessful" << std::endl; + + // test case for Chinese language + + obj.insert("č‹šæžœ"); + assert(obj.startwith("苹")); + pred_words = obj.predict_words("h"); + + assert(pred_words.size() == 0); + std::cout << "No word starts with prefix h in trie" << std::endl; + + std::cout << "All tests passed" << std::endl; +} + +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + test(); // run self-test implementaions + return 0; +} diff --git a/math/linear_recurrence_matrix.cpp b/math/linear_recurrence_matrix.cpp new file mode 100644 index 000000000..98c90e5f8 --- /dev/null +++ b/math/linear_recurrence_matrix.cpp @@ -0,0 +1,372 @@ +/** + * @brief Evaluate recurrence relation using [matrix + * exponentiation](https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/). + * @details + * Given a recurrence relation; evaluate the value of nth term. + * For e.g., For fibonacci series, recurrence series is `f(n) = f(n-1) + f(n-2)` + * where `f(0) = 0` and `f(1) = 1`. + * Note that the method used only demonstrates + * recurrence relation with one variable (n), unlike `nCr` problem, since it has + * two (n, r) + * + * ### Algorithm + * This problem can be solved using matrix exponentiation method. + * @see here for simple [number exponentiation + * algorithm](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/modular_exponentiation.cpp) + * or [explaination + * here](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). + * @author [Ashish Daulatabad](https://github.com/AshishYUO) + */ +#include /// for assert +#include /// for IO operations +#include /// for std::vector STL + +/** + * @namespace math + * @brief Mathematical algorithms + */ +namespace math { +/** + * @namespace linear_recurrence_matrix + * @brief Functions for [Linear Recurrence + * Matrix](https://www.hackerearth.com/practice/notes/matrix-exponentiation-1/) + * implementation. + */ +namespace linear_recurrence_matrix { +/** + * @brief Implementation of matrix multiplication + * @details Multiplies matrix A and B, given total columns in A are equal to + * total given rows in column B + * @tparam T template type for integer as well as floating values, default is + * long long int + * @param _mat_a first matrix of size n * m + * @param _mat_b second matrix of size m * k + * @returns `_mat_c` resultant matrix of size n * k + * Complexity: `O(n*m*k)` + * @note The complexity in this case will be O(n^3) due to the nature of the + * problem. We'll be multiplying the matrix with itself most of the time. + */ +template +std::vector> matrix_multiplication( + const std::vector>& _mat_a, + const std::vector>& _mat_b, const int64_t mod = 1000000007) { + // assert that columns in `_mat_a` and rows in `_mat_b` are equal + assert(_mat_a[0].size() == _mat_b.size()); + std::vector> _mat_c(_mat_a.size(), + std::vector(_mat_b[0].size(), 0)); + /** + * Actual matrix multiplication. + */ + for (uint32_t i = 0; i < _mat_a.size(); ++i) { + for (uint32_t j = 0; j < _mat_b[0].size(); ++j) { + for (uint32_t k = 0; k < _mat_b.size(); ++k) { + _mat_c[i][j] = + (_mat_c[i][j] % mod + + (_mat_a[i][k] % mod * _mat_b[k][j] % mod) % mod) % + mod; + } + } + } + return _mat_c; +} +/** + * @brief Returns whether matrix `mat` is a [zero + * matrix.](https://en.wikipedia.org/wiki/Zero_matrix) + * @tparam T template type for integer as well as floating values, default is + * long long int + * @param _mat A matrix + * @returns true if it is a zero matrix else false + */ +template +bool is_zero_matrix(const std::vector>& _mat) { + for (uint32_t i = 0; i < _mat.size(); ++i) { + for (uint32_t j = 0; j < _mat[i].size(); ++j) { + if (_mat[i][j] != 0) { + return false; + } + } + } + return true; +} + +/** + * @brief Implementation of Matrix exponentiation + * @details returns the matrix exponentiation `(B^n)` in `k^3 * O(log2(power))` + * time, where `k` is the size of matrix (k by k). + * @tparam T template type for integer as well as floating values, default is + * long long int + * @param _mat matrix for exponentiation + * @param power the exponent value + * @returns the matrix _mat to the power `power (_mat^power)` + */ +template +std::vector> matrix_exponentiation( + std::vector> _mat, uint64_t power, + const int64_t mod = 1000000007) { + /** + * Initializing answer as identity matrix. For simple binary + * exponentiation reference, [see + * here](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/math/modular_exponentiation.cpp) + */ + if (is_zero_matrix(_mat)) { + return _mat; + } + + std::vector> _mat_answer(_mat.size(), + std::vector(_mat.size(), 0)); + + for (uint32_t i = 0; i < _mat.size(); ++i) { + _mat_answer[i][i] = 1; + } + // exponentiation algorithm here. + while (power > 0) { + if (power & 1) { + _mat_answer = matrix_multiplication(_mat_answer, _mat, mod); + } + power >>= 1; + _mat = matrix_multiplication(_mat, _mat, mod); + } + + return _mat_answer; +} + +/** + * @brief Implementation of nth recurrence series. + * @details Returns the nth term in the recurrence series. + * Note that the function assumes definition of base cases from `n = 0` + * (e.g., for fibonacci, `f(0)` has a defined value `0`) + * @tparam T template type for integer as well as floating values, default is + * long long int + * @param _mat [square matrix](https://en.m.wikipedia.org/wiki/Square_matrix) + * that evaluates the nth term using exponentiation + * @param _base_cases 2D array of dimension `1*n` containing values which are + * defined for some n (e.g., for fibonacci, `f(0)` and `f(1)` are defined, and + * `f(n)` where `n > 1` is evaluated on previous two values) + * @param nth_term the nth term of recurrence relation + * @param constant_or_sum_included whether the recurrence relation has a + * constant value or is evaluating sum of first n terms of the recurrence. + * @returns the nth term of the recurrence relation in `O(k^3. log(n))`, where k + * is number of rows and columns in `_mat` and `n` is the value of `nth_term` + * If constant_or_sum_included is true, returns the sum of first n terms in + * recurrence series + */ +template +T get_nth_term_of_recurrence_series( + const std::vector>& _mat, + const std::vector>& _base_cases, uint64_t nth_term, + bool constant_or_sum_included = false) { + assert(_mat.size() == _base_cases.back().size()); + + /** + * If nth term is a base case, then return base case directly. + */ + + if (nth_term < _base_cases.back().size() - constant_or_sum_included) { + return _base_cases.back()[nth_term - constant_or_sum_included]; + } else { + /** + * Else evaluate the expression, so multiplying _mat to itself (n - + * base_cases.length + 1 + constant_or_sum_included) times. + */ + std::vector> _res_matrix = + matrix_exponentiation(_mat, nth_term - _base_cases.back().size() + + 1 + constant_or_sum_included); + + /** + * After matrix exponentiation, multiply with the base case to evaluate + * the answer. The answer is always at the end of the array. + */ + std::vector> _res = + matrix_multiplication(_base_cases, _res_matrix); + + return _res.back().back(); + } +} +} // namespace linear_recurrence_matrix +} // namespace math + +/** + * @brief Self test-implementations + * @returns void + */ +static void test() { + /* + * Example 1: [Fibonacci + * series](https://en.wikipedia.org/wiki/Fibonacci_number); + * + * [fn-2 fn-1] [0 1] == [fn-1 (fn-2 + fn-1)] => [fn-1 fn] + * [1 1] + * + * Let A = [fn-2 fn-1], and B = [0 1] + * [1 1], + * + * Since, A.B....(n-1 times) = [fn-1 fn] + * we can multiply B with itself n-1 times to obtain the required value + */ + std::vector> fibonacci_matrix = {{0, 1}, {1, 1}}, + fib_base_case = {{0, 1}}; + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + fibonacci_matrix, fib_base_case, 11) == 89LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + fibonacci_matrix, fib_base_case, 39) == 63245986LL); + /* + * Example 2: [Tribonacci series](https://oeis.org/A000073) + * [0 0 1] + * [fn-3 fn-2 fn-1] [1 0 1] = [(fn-2) (fn-1) (fn-3 + fn-2 + fn-1)] + * [0 1 1] + * => [fn-2 fn-1 fn] + * + * [0 0 1] + * Let A = [fn-3 fn-2 fn-1], and B = [1 0 1] + * [0 1 1] + * + * Since, A.B....(n-2 times) = [fn-2 fn-1 fn] + * we will have multiply B with itself n-2 times to obtain the required + * value () + */ + + std::vector> tribonacci = {{0, 0, 1}, + {1, 0, 1}, + {0, 1, 1}}, + trib_base_case = { + {0, 0, 1}}; // f0 = 0, f1 = 0, f2 = 1 + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + tribonacci, trib_base_case, 11) == 149LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + tribonacci, trib_base_case, 36) == 615693474LL); + + /* + * Example 3: [Pell numbers](https://oeis.org/A000129) + * `f(n) = 2* f(n-1) + f(n-2); f(0) = f(1) = 2` + * + * [fn-2 fn-1] [0 1] = [(fn-1) fn-2 + 2*fn-1)] + * [1 2] + * => [fn-1 fn] + * + * Let A = [fn-2 fn-1], and B = [0 1] + * [1 2] + */ + + std::vector> pell_recurrence = {{0, 1}, {1, 2}}, + pell_base_case = { + {2, 2}}; // `f0 = 2, f1 = 2` + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + pell_recurrence, pell_base_case, 15) == 551614LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + pell_recurrence, pell_base_case, 23) == 636562078LL); + + /* + * Example 4: Custom recurrence relation: + * Now the recurrence is of the form `a*f(n-1) + b*(fn-2) + ... + c` + * where `c` is the constant + * `f(n) = 2* f(n-1) + f(n-2) + 7; f(0) = f(1) = 2, c = 7` + * + * [1 0 1] + * [7, fn-2, fn-1] [0 0 1] + * [0 1 2] + * = [7, (fn-1), fn-2 + 2*fn-1) + 7] + * + * => [7, fn-1, fn] + * :: Series will be 2, 2, 13, 35, 90, 222, 541, 1311, 3170, 7658, 18493, + * 44651, 107802, 260262, 628333, 1516935, 362210, 8841362, 21344941, + * 51531251 + * + * Let A = [7, fn-2, fn-1], and B = [1 0 1] + * [0 0 1] + * [0 1 2] + */ + + std::vector> + custom_recurrence = {{1, 0, 1}, {0, 0, 1}, {0, 1, 2}}, + custom_base_case = {{7, 2, 2}}; // `c = 7, f0 = 2, f1 = 2` + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + custom_recurrence, custom_base_case, 10, 1) == 18493LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + custom_recurrence, custom_base_case, 19, 1) == 51531251LL); + + /* + * Example 5: Sum fibonacci sequence + * The following matrix evaluates the sum of first n fibonacci terms in + * O(27. log2(n)) time. + * `f(n) = f(n-1) + f(n-2); f(0) = 0, f(1) = 1` + * + * [1 0 0] + * [s(f, n-1), fn-2, fn-1] [1 0 1] + * [1 1 1] + * => [(s(f, n-1)+f(n-2)+f(n-1)), (fn-1), f(n-2)+f(n-1)] + * + * => [s(f, n-1)+f(n), fn-1, fn] + * + * => [s(f, n), fn-1, fn] + * + * Sum of first 20 fibonacci series: + * 0, 1, 2, 4, 7, 12, 20, 33, 54, 88, 143, 232, 376, 609, 986, 1596, 2583, + * 4180, 6764 + * f0 f1 s(f,1) + * Let A = [0 1 1], and B = [0 1 1] + * [1 1 1] + * [0 0 1] + */ + + std::vector> sum_fibo_recurrence = {{0, 1, 1}, + {1, 1, 1}, + {0, 0, 1}}, + sum_fibo_base_case = { + {0, 1, 1}}; // `f0 = 0, f1 = 1` + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + sum_fibo_recurrence, sum_fibo_base_case, 13, 1) == 609LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + sum_fibo_recurrence, sum_fibo_base_case, 16, 1) == 2583LL); + /* + * Example 6: [Tribonacci sum series](https://oeis.org/A000073) + * [0 0 1 1] + * [fn-3 fn-2 fn-1 s(f, n-1)] [1 0 1 1] + * [0 1 1 1] + * [0 0 0 1] + * + * = [fn-2, fn-1, fn-3 + fn-2 + fn-1, (fn-3 + fn-2 + fn-1 + s(f, n-1))] + * + * => [fn-2, fn-1, fn, fn + s(f, n-1)] + * + * => [fn-2, fn-1, fn, s(f, n)] + * + * Sum of the series is: 0, 0, 1, 2, 4, 8, 15, 28, 52, 96, 177, 326, 600, + * 1104, 2031, 3736, 6872, 12640, 23249, 42762 + * + * Let A = [fn-3 fn-2 fn-1 s(f, n-1)], and + * [0 0 1 1] + * B = [1 0 1 1] + * [0 1 1 1] + * [0 0 0 1] + * + * Since, A.B....(n-2 times) = [fn-2 fn-1 fn] + * we will have multiply B with itself n-2 times to obtain the required + * value + */ + + std::vector> tribonacci_sum = {{0, 0, 1, 1}, + {1, 0, 1, 1}, + {0, 1, 1, 1}, + {0, 0, 0, 1}}, + trib_sum_base_case = {{0, 0, 1, 1}}; + // `f0 = 0, f1 = 0, f2 = 1, s = 1` + + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + tribonacci_sum, trib_sum_base_case, 18, 1) == 23249LL); + assert(math::linear_recurrence_matrix::get_nth_term_of_recurrence_series( + tribonacci_sum, trib_sum_base_case, 19, 1) == 42762LL); +} + +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + test(); // run self-test implementations + return 0; +}