From 40d369d83cb1c60b6dc6e7c2f3fc9af658a53589 Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Thu, 29 Aug 2024 22:43:30 -0300 Subject: [PATCH 1/6] Adding WildCardMatcher code --- .../PatternMatching/WildCardMatcher.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Algorithms/Strings/PatternMatching/WildCardMatcher.cs diff --git a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs new file mode 100644 index 00000000..fc3edafc --- /dev/null +++ b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs @@ -0,0 +1,59 @@ +using System; + +namespace Algorithms.Strings.PatternMatching; + +public static class WildCardMatcher +{ + public static bool MatchPattern(string inputString, string pattern) + { + if (pattern.Length > 0 & pattern[0] == '*') + { + throw new ArgumentException("Pattern cannot start with *"); + } + + var inputLength = inputString.Length + 1; + var patternLength = pattern.Length + 1; + + // DP 2d matrix, where dp[i, j] is true if the first i characters in the input string match the first j characters in the pattern + // This DP is initialized to all falses, as it is the default value for a boolean. + var dp = new bool[inputLength, patternLength]; + + // Empty string and empty pattern are a match + dp[0, 0] = true; + + // Since the empty string can only match a pattern that has a * in it, we need to initialize the first row of the DP matrix + for (var j = 1; j < patternLength; j++) + { + if (pattern[j - 1] == '*') + { + dp[0, j] = dp[0, j - 2]; + } + } + + // Now using bottom-up approach to find for all remaining lenghts of input and pattern + for (var i = 1; i < inputLength; i++) + { + for (var j = 1; j < patternLength; j++) + { + // If the characters match or the pattern has a ., then the result is the same as the previous characters + if (inputString[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') + { + dp[i, j] = dp[i - 1, j - 1]; + } + else if (pattern[j - 1] == '*') + { + if (dp[i, j - 2]) + { + dp[i, j] = true; + } + else if (inputString[i - 1] == pattern[j - 2] || pattern[j - 2] == '.') + { + dp[i, j] = dp[i - 1, j]; + } + } + } + } + + return dp[inputLength - 1, patternLength - 1]; + } +} From 1562c6bf30d6a2d00e8f0597e517dc09c03d9939 Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Thu, 29 Aug 2024 23:00:27 -0300 Subject: [PATCH 2/6] Add short-circuiting operator to first conditional --- Algorithms/Strings/PatternMatching/WildCardMatcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs index fc3edafc..4c0f2973 100644 --- a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs +++ b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs @@ -6,7 +6,7 @@ public static class WildCardMatcher { public static bool MatchPattern(string inputString, string pattern) { - if (pattern.Length > 0 & pattern[0] == '*') + if (pattern.Length > 0 && pattern[0] == '*') { throw new ArgumentException("Pattern cannot start with *"); } @@ -35,7 +35,7 @@ public static bool MatchPattern(string inputString, string pattern) { for (var j = 1; j < patternLength; j++) { - // If the characters match or the pattern has a ., then the result is the same as the previous characters + // If the characters match or the pattern has a ., then the result is the same as the previous positions. if (inputString[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') { dp[i, j] = dp[i - 1, j - 1]; From 093eb3701df333327f6be929b3d98241b541a64e Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Thu, 29 Aug 2024 23:00:58 -0300 Subject: [PATCH 3/6] Add tests to WildCardMatcher --- .../PatternMatching/WildCardMatcherTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Algorithms.Tests/Strings/PatternMatching/WildCardMatcherTests.cs diff --git a/Algorithms.Tests/Strings/PatternMatching/WildCardMatcherTests.cs b/Algorithms.Tests/Strings/PatternMatching/WildCardMatcherTests.cs new file mode 100644 index 00000000..5361557a --- /dev/null +++ b/Algorithms.Tests/Strings/PatternMatching/WildCardMatcherTests.cs @@ -0,0 +1,36 @@ +using Algorithms.Strings.PatternMatching; +using NUnit.Framework; + +namespace Algorithms.Tests.Strings.PatternMatching; + +public static class WildCardMatcherTests +{ + [TestCase("aab", "c*a*b", true)] + [TestCase("aaa", "aa", false)] + [TestCase("aaa", "a.a", true)] + [TestCase("aaab", "aa*", false)] + [TestCase("aaab", ".*", true)] + [TestCase("a", "bbbb", false)] + [TestCase("", "bbbb", false)] + [TestCase("a", "", false)] + [TestCase("", "", true)] + public static void MatchPattern(string inputString, string pattern, bool expected) + { + // Act + var result = WildCardMatcher.MatchPattern(inputString, pattern); + + // Assert + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public static void MatchPatternThrowsArgumentException() + { + // Arrange + var inputString = "abc"; + var pattern = "*abc"; + + // Assert + Assert.Throws(() => WildCardMatcher.MatchPattern(inputString, pattern)); + } +} From a2e05c983a588c24bdccdd3d8d24e97af6de49f2 Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Thu, 29 Aug 2024 23:02:06 -0300 Subject: [PATCH 4/6] Update List of Algorithms --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fd4f1942..ef5a1cfd 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ find more than one implementation for the same objective but using different alg * [Rabin Karp](./Algorithms/Strings/PatternMatching/RabinKarp.cs) * [Boyer Moore](./Algorithms/Strings/PatternMatching/BoyerMoore.cs) * [Knuth–Morris–Pratt Search](./Algorithms/Strings/PatternMatching/KnuthMorrisPrattSearcher.cs) + * [WildCard Pattern Matching](./Algorithms/Strings/PatternMatching/WildCardMatcher.cs) * [Z-block substring search](./Algorithms/Strings/PatternMatching/ZblockSubstringSearch.cs) * [Longest Consecutive Character](./Algorithms/Strings/GeneralStringAlgorithms.cs) * [Palindrome Checker](./Algorithms/Strings/Palindrome.cs) From 39fc55a4bc5979219949feae9b69a39f99a90d44 Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Fri, 30 Aug 2024 14:36:04 -0300 Subject: [PATCH 5/6] Add documentation to the class --- .../Strings/PatternMatching/WildCardMatcher.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs index 4c0f2973..b0a315cd 100644 --- a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs +++ b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs @@ -2,8 +2,25 @@ namespace Algorithms.Strings.PatternMatching; +/// +/// Implentation of regular expression matching with support for '.' and '*'. +/// '.' Matches any single character. +/// '*' Matches zero or more of the preceding element. +/// The matching should cover the entire input string (not partial). +/// public static class WildCardMatcher { + /// + /// Using bottom-up dynamic programming for matching the input string with the pattern. + /// + /// Time complexity: O(n*m), where n is the length of the input string and m is the length of the pattern. + /// + /// Constrain: The pattern cannot start with '*'. + /// + /// The input string to match. + /// The pattern to match. + /// True if the input string matches the pattern, false otherwise. + /// Thrown when the pattern starts with '*'. public static bool MatchPattern(string inputString, string pattern) { if (pattern.Length > 0 && pattern[0] == '*') From 8362a502e98b6979b9c11e8504a5b34c784808f1 Mon Sep 17 00:00:00 2001 From: RicardoRibeiroRodrigues Date: Fri, 30 Aug 2024 15:32:18 -0300 Subject: [PATCH 6/6] Refactor code to address codacy comments --- .../PatternMatching/WildCardMatcher.cs | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs index b0a315cd..cf15ce21 100644 --- a/Algorithms/Strings/PatternMatching/WildCardMatcher.cs +++ b/Algorithms/Strings/PatternMatching/WildCardMatcher.cs @@ -52,25 +52,46 @@ public static bool MatchPattern(string inputString, string pattern) { for (var j = 1; j < patternLength; j++) { - // If the characters match or the pattern has a ., then the result is the same as the previous positions. - if (inputString[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') - { - dp[i, j] = dp[i - 1, j - 1]; - } - else if (pattern[j - 1] == '*') - { - if (dp[i, j - 2]) - { - dp[i, j] = true; - } - else if (inputString[i - 1] == pattern[j - 2] || pattern[j - 2] == '.') - { - dp[i, j] = dp[i - 1, j]; - } - } + MatchRemainingLenghts(inputString, pattern, dp, i, j); } } return dp[inputLength - 1, patternLength - 1]; } + + // Helper method to match the remaining lengths of the input string and the pattern + // This method is called for all i and j where i > 0 and j > 0 + private static void MatchRemainingLenghts(string inputString, string pattern, bool[,] dp, int i, int j) + { + // If the characters match or the pattern has a ., then the result is the same as the previous positions. + if (inputString[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') + { + dp[i, j] = dp[i - 1, j - 1]; + } + else if (pattern[j - 1] == '*') + { + MatchForZeroOrMore(inputString, pattern, dp, i, j); + } + else + { + // If the characters do not match, then the result is false, which is the default value. + } + } + + // Helper method to match for the "*" pattern. + private static void MatchForZeroOrMore(string inputString, string pattern, bool[,] dp, int i, int j) + { + if (dp[i, j - 2]) + { + dp[i, j] = true; + } + else if (inputString[i - 1] == pattern[j - 2] || pattern[j - 2] == '.') + { + dp[i, j] = dp[i - 1, j]; + } + else + { + // Leave the default value of false + } + } }