From 5a73c665356c268fbeac07efedcf423bc1ad04e9 Mon Sep 17 00:00:00 2001 From: Rahul Date: Sat, 4 Apr 2026 05:04:26 +0530 Subject: [PATCH 1/2] docs, test: fit edit_distance.cpp to contributing guidelines --- dynamic_programming/edit_distance.cpp | 178 +++++++++++++++++--------- 1 file changed, 119 insertions(+), 59 deletions(-) diff --git a/dynamic_programming/edit_distance.cpp b/dynamic_programming/edit_distance.cpp index 02dae8ca90e..8855dd2dfb3 100644 --- a/dynamic_programming/edit_distance.cpp +++ b/dynamic_programming/edit_distance.cpp @@ -14,73 +14,133 @@ #include #include #include +#include // Required for automated testing using namespace std; -int min(int x, int y, int z) { return min(min(x, y), z); } +namespace dynamic_programming { + int min(int x, int y, int z) { return std::min(std::min(x, y), z); } -/* A Naive recursive C++ program to find - * minimum number of operations to convert - * str1 to str2. - * O(3^m) - */ -int editDist(string str1, string str2, int m, int n) { - if (m == 0) - return n; - if (n == 0) - return m; - - // If last characters are same then continue - // for the rest of them. - if (str1[m - 1] == str2[n - 1]) - return editDist(str1, str2, m - 1, n - 1); - - // If last not same, then 3 possibilities - // a.Insert b.Remove c. Replace - // Get min of three and continue for rest. - return 1 + min(editDist(str1, str2, m, n - 1), - editDist(str1, str2, m - 1, n), - editDist(str1, str2, m - 1, n - 1)); -} + /* A Naive recursive C++ program to find + * minimum number of operations to convert + * str1 to str2. + * O(3^m) + */ -/* A DP based program - * O(m x n) - */ -int editDistDP(string str1, string str2, int m, int n) { - // Create Table for SubProblems - std::vector > dp(m + 1, std::vector(n + 1)); - - // Fill d[][] in bottom up manner - for (int i = 0; i <= m; i++) { - for (int j = 0; j <= n; j++) { - // If str1 empty. Then add all of str2 - if (i == 0) - dp[i][j] = j; - - // If str2 empty. Then add all of str1 - else if (j == 0) - dp[i][j] = i; - - // If character same. Recur for remaining - else if (str1[i - 1] == str2[j - 1]) - dp[i][j] = dp[i - 1][j - 1]; - - else - dp[i][j] = 1 + min(dp[i][j - 1], // Insert - dp[i - 1][j], // Remove - dp[i - 1][j - 1] // Replace - ); + /** + * @brief Recursive implementation to find edit distance + * @param str1 first string + * @param str2 second string + * @param m length of first string + * @param n length of second string + * @returns minimum number of edits + */ + int editDist(string str1, string str2, int m, int n) { + if (m == 0) + return n; + if (n == 0) + return m; + + // If last characters are same then continue + // for the rest of them. + if (str1[m - 1] == str2[n - 1]) + return editDist(str1, str2, m - 1, n - 1); + + // If last not same, then 3 possibilities + // a.Insert b.Remove c. Replace + // Get min of three and continue for rest. + return 1 + min(editDist(str1, str2, m, n - 1), + editDist(str1, str2, m - 1, n), + editDist(str1, str2, m - 1, n - 1)); + } + + /* A DP based program + * O(m x n) + */ + int editDistDP(string str1, string str2, int m, int n) { + // Create Table for SubProblems + std::vector > dp(m + 1, std::vector(n + 1)); + + // Fill d[][] in bottom up manner + for (int i = 0; i <= m; i++) { + for (int j = 0; j <= n; j++) { + // If str1 empty. Then add all of str2 + if (i == 0) + dp[i][j] = j; + + // If str2 empty. Then add all of str1 + else if (j == 0) + dp[i][j] = i; + + // If character same. Recur for remaining + else if (str1[i - 1] == str2[j - 1]) + dp[i][j] = dp[i - 1][j - 1]; + + else + dp[i][j] = 1 + min(dp[i][j - 1], // Insert + dp[i - 1][j], // Remove + dp[i - 1][j - 1] // Replace + ); + } } + + return dp[m][n]; } - return dp[m][n]; -} +/** + * @brief Test function for edit distance with multiple test cases + */ + void testEditDistance() { + // Test case 1: Identical strings + assert(editDistDP("hello", "hello", 5, 5) == 0); + // Test case 2: Empty strings + assert(editDistDP("", "", 0, 0) == 0); + // Test case 3: One empty string + assert(editDistDP("abc", "", 3, 0) == 3); + assert(editDistDP("", "xyz", 0, 3) == 3); + // Test case 4: Simple replacement + assert(editDistDP("cat", "bat", 3, 3) == 1); + // Test case 5: Simple insertion + assert(editDistDP("cat", "cart", 3, 4) == 1); + // Test case 6: Simple deletion + assert(editDistDP("cart", "cat", 4, 3) == 1); + // Test case 7: Multiple operations + assert(editDistDP("sunday", "saturday", 6, 8) == 3); + // Test case 8: Different lengths + assert(editDistDP("kitten", "sitting", 6, 7) == 3); + // Test case 9: Case sensitivity + assert(editDistDP("Hello", "hello", 5, 5) == 1); + // Test case 10: Longer strings + assert(editDistDP("intention", "execution", 9, 9) == 5); -int main() { - string str1 = "sunday"; - string str2 = "saturday"; + std::cout << "All extended test cases passed!" << std::endl; + } +} // namespace dynamic_programming - cout << editDist(str1, str2, str1.length(), str2.length()) << endl; - cout << editDistDP(str1, str2, str1.length(), str2.length()) << endl; +/** + * @brief Self-test implementations + * @returns void + */ +static void test() { + std::string str1 = "sunday"; + std::string str2 = "saturday"; + + // Test the recursive approach + assert(dynamic_programming::editDist(str1, str2, str1.length(), str2.length()) == 3); + + // Test the Dynamic Programming approach + assert(dynamic_programming::editDistDP(str1, str2, str1.length(), str2.length()) == 3); - return 0; + // Call your new extended test suite + dynamic_programming::testEditDistance(); + + std::cout << "All tests have successfully passed!\n"; } + +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + test(); // run self-test implementations + return 0; +} \ No newline at end of file From f93cbec47dc1ee2804d1ac04abdb03a4471ca261 Mon Sep 17 00:00:00 2001 From: Rahul Date: Sat, 4 Apr 2026 06:18:53 +0530 Subject: [PATCH 2/2] feat: optimize space complexity to O(n) and add Doxygen documentation --- dynamic_programming/edit_distance.cpp | 116 ++++++++++++++++---------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/dynamic_programming/edit_distance.cpp b/dynamic_programming/edit_distance.cpp index 8855dd2dfb3..8c3a718511f 100644 --- a/dynamic_programming/edit_distance.cpp +++ b/dynamic_programming/edit_distance.cpp @@ -1,3 +1,9 @@ +/** + * @file + * @brief [Edit Distance](https://en.wikipedia.org/wiki/Edit_distance) algorithm implementation. + * @author [Rahul Paul](https://github.com/rahulpaul-07) + */ + /* Given two strings str1 & str2 * and below operations that can * be performed on str1. Find @@ -15,10 +21,11 @@ #include #include #include // Required for automated testing -using namespace std; +#include +#include namespace dynamic_programming { - int min(int x, int y, int z) { return std::min(std::min(x, y), z); } + /* A Naive recursive C++ program to find * minimum number of operations to convert @@ -34,7 +41,7 @@ namespace dynamic_programming { * @param n length of second string * @returns minimum number of edits */ - int editDist(string str1, string str2, int m, int n) { + int editDist(const std::string& str1, const std::string& str2, int m, int n) { if (m == 0) return n; if (n == 0) @@ -48,42 +55,52 @@ namespace dynamic_programming { // If last not same, then 3 possibilities // a.Insert b.Remove c. Replace // Get min of three and continue for rest. - return 1 + min(editDist(str1, str2, m, n - 1), - editDist(str1, str2, m - 1, n), - editDist(str1, str2, m - 1, n - 1)); + return 1 + std::min({editDist(str1, str2, m, n - 1), + editDist(str1, str2, m - 1, n), + editDist(str1, str2, m - 1, n - 1)}); } - /* A DP based program - * O(m x n) + /** + * @brief Space-optimized edit distance (O(n) space) + * @param str1 first string + * @param str2 second string + * @returns minimum number of edits + * @throws std::invalid_argument if input is too large (Security Guard) */ - int editDistDP(string str1, string str2, int m, int n) { - // Create Table for SubProblems - std::vector > dp(m + 1, std::vector(n + 1)); - - // Fill d[][] in bottom up manner - for (int i = 0; i <= m; i++) { - for (int j = 0; j <= n; j++) { - // If str1 empty. Then add all of str2 - if (i == 0) - dp[i][j] = j; - - // If str2 empty. Then add all of str1 - else if (j == 0) - dp[i][j] = i; - - // If character same. Recur for remaining - else if (str1[i - 1] == str2[j - 1]) - dp[i][j] = dp[i - 1][j - 1]; - - else - dp[i][j] = 1 + min(dp[i][j - 1], // Insert - dp[i - 1][j], // Remove - dp[i - 1][j - 1] // Replace - ); + int editDistDP(const std::string& str1, const std::string& str2) { + size_t m = str1.length(); + size_t n = str2.length(); + + // 1. Security Gate: Prevent Denial of Service (DoS) via memory exhaustion + const size_t MAX_ALLOWED = 10000; + if (m > MAX_ALLOWED || n > MAX_ALLOWED) { + throw std::invalid_argument("Input string length exceeds the safety limit of 10,000."); + } + + // 2. Memory optimization: Ensure 'n' is the smaller length + if (m < n) return editDistDP(str2, str1); + + // 3. O(n) Space: Only two rows needed + std::vector prev(n + 1); + std::vector curr(n + 1); + + // Initialize base cases for the first row (empty str1) + for (size_t j = 0; j <= n; j++) prev[j] = static_cast(j); + + for (size_t i = 1; i <= m; i++) { + curr[0] = static_cast(i); // Base case for the first column (empty str2) + for (size_t j = 1; j <= n; j++) { + if (str1[i - 1] == str2[j - 1]) { + curr[j] = prev[j - 1]; // Characters match, no edit needed + } else { + // Min of Insert (curr[j-1]), Remove (prev[j]), Replace (prev[j-1]) + curr[j] = 1 + std::min({curr[j - 1], prev[j], prev[j - 1]}); + } } + prev = curr; // Move current row to previous for next iteration } - return dp[m][n]; + return prev[n]; } /** @@ -91,27 +108,34 @@ namespace dynamic_programming { */ void testEditDistance() { // Test case 1: Identical strings - assert(editDistDP("hello", "hello", 5, 5) == 0); + assert(editDistDP("hello", "hello") == 0); // Test case 2: Empty strings - assert(editDistDP("", "", 0, 0) == 0); + assert(editDistDP("", "") == 0); // Test case 3: One empty string - assert(editDistDP("abc", "", 3, 0) == 3); - assert(editDistDP("", "xyz", 0, 3) == 3); + assert(editDistDP("abc", "") == 3); + assert(editDistDP("", "xyz") == 3); // Test case 4: Simple replacement - assert(editDistDP("cat", "bat", 3, 3) == 1); + assert(editDistDP("cat", "bat") == 1); // Test case 5: Simple insertion - assert(editDistDP("cat", "cart", 3, 4) == 1); + assert(editDistDP("cat", "cart") == 1); // Test case 6: Simple deletion - assert(editDistDP("cart", "cat", 4, 3) == 1); + assert(editDistDP("cart", "cat") == 1); // Test case 7: Multiple operations - assert(editDistDP("sunday", "saturday", 6, 8) == 3); + assert(editDistDP("sunday", "saturday") == 3); // Test case 8: Different lengths - assert(editDistDP("kitten", "sitting", 6, 7) == 3); + assert(editDistDP("kitten", "sitting") == 3); // Test case 9: Case sensitivity - assert(editDistDP("Hello", "hello", 5, 5) == 1); + assert(editDistDP("Hello", "hello") == 1); // Test case 10: Longer strings - assert(editDistDP("intention", "execution", 9, 9) == 5); - + assert(editDistDP("intention", "execution") == 5); + // Test 11: Security Gate Test + try { + std::string big(10001, 'a'); + editDistDP(big, "test"); + assert(false); // Should not reach here + } catch (const std::invalid_argument& e) { + std::cout << "Security Test Passed: Correctly blocked oversized input." << std::endl; + } std::cout << "All extended test cases passed!" << std::endl; } } // namespace dynamic_programming @@ -128,7 +152,7 @@ static void test() { assert(dynamic_programming::editDist(str1, str2, str1.length(), str2.length()) == 3); // Test the Dynamic Programming approach - assert(dynamic_programming::editDistDP(str1, str2, str1.length(), str2.length()) == 3); + assert(dynamic_programming::editDistDP(str1, str2) == 3); // Call your new extended test suite dynamic_programming::testEditDistance();