From ba296e1e8923e09d539e0dad1931b279d5b7f09b Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 12:50:45 +0430 Subject: [PATCH 01/11] Add recursive solution to regex_match.py --- dynamic_programming/regex_match.py | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 dynamic_programming/regex_match.py diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py new file mode 100644 index 000000000000..b390d6eaef15 --- /dev/null +++ b/dynamic_programming/regex_match.py @@ -0,0 +1,53 @@ +""" +Regex matching check if a text matches wildcard pattern or not. +Pattern: + '.' Matches any single character. + '*' Matches zero or more of the preceding element. +""" + + +def recursive_match(text: str, pattern: str) -> bool: + """ + Recursive matching algorithm. + + Time complexity: O(2 ^ (m + n)), where m is the length of text and n is the length of pattern. + Space complexity: Recursion depth is O(m + n). + + :param text: Text to match. + :param pattern: Pattern to match. + :return: True if text matches pattern, False otherwise. + + >>> recursive_match('abc', 'a.c') + True + >>> recursive_match('abc', 'af*.c') + True + >>> recursive_match('abc', 'a.c*') + True + >>> recursive_match('abc', 'a.c*d') + False + """ + if not text and not pattern: + return True + + if text and not pattern: + return False + + if not text and pattern and pattern[-1] != '*': + return False + + if not text and pattern and pattern[-1] == '*': + return recursive_match(text, pattern[:-2]) + + if text[-1] == pattern[-1] or pattern[-1] == '.': + return recursive_match(text[:-1], pattern[:-1]) + + if pattern[-1] == '*': + return recursive_match(text[:-1], pattern) or recursive_match(text, pattern[:-2]) + + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 0cd88af4ff41118bc20b9971affb00b60b92dd5a Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 12:52:38 +0430 Subject: [PATCH 02/11] Add dp solution to regex_match.py --- dynamic_programming/regex_match.py | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index b390d6eaef15..dc76eedebe60 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -25,6 +25,8 @@ def recursive_match(text: str, pattern: str) -> bool: True >>> recursive_match('abc', 'a.c*d') False + >>> recursive_match('aa', '.*') + True """ if not text and not pattern: return True @@ -47,6 +49,51 @@ def recursive_match(text: str, pattern: str) -> bool: return False +def dp_match(text: str, pattern: str) -> bool: + """ + Dynamic programming matching algorithm. + + Time complexity: O(m * n), where m is the length of text and n is the length of pattern. + Space complexity: O(m * n). + + :param text: Text to match. + :param pattern: Pattern to match. + :return: True if text matches pattern, False otherwise. + + >>> dp_match('abc', 'a.c') + True + >>> dp_match('abc', 'af*.c') + True + >>> dp_match('abc', 'a.c*') + True + >>> dp_match('abc', 'a.c*d') + False + >>> dp_match('aa', '.*') + True + """ + m = len(text) + n = len(pattern) + dp = [[False for _ in range(n + 1)] for _ in range(m + 1)] + dp[0][0] = True + + for i in range(1, m + 1): + dp[i][0] = False + + for j in range(1, n + 1): + dp[0][j] = pattern[j - 1] == '*' and dp[0][j - 2] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if pattern[j - 1] == '.' or pattern[j - 1] == text[i - 1]: + dp[i][j] = dp[i - 1][j - 1] + elif pattern[j - 1] == '*': + dp[i][j] = dp[i][j - 2] or (dp[i - 1][j] and (pattern[j - 2] == '.' or pattern[j - 2] == text[i - 1])) + else: + dp[i][j] = False + + return dp[m][n] + + if __name__ == "__main__": import doctest From 8d65acf76064ba8aaf87fe8087f2f4e10f053e76 Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 12:55:28 +0430 Subject: [PATCH 03/11] Add link to regex_match.py --- dynamic_programming/regex_match.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index dc76eedebe60..bb2ed2d3a882 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -3,6 +3,8 @@ Pattern: '.' Matches any single character. '*' Matches zero or more of the preceding element. +More info: + https://medium.com/trick-the-interviwer/regular-expression-matching-9972eb74c03 """ From e71795c2e3f8d4ed4b862d5978cec35bc40b6113 Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 16:42:34 +0430 Subject: [PATCH 04/11] Minor edit --- dynamic_programming/regex_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index bb2ed2d3a882..316328d0448f 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -1,5 +1,5 @@ """ -Regex matching check if a text matches wildcard pattern or not. +Regex matching check if a text matches pattern or not. Pattern: '.' Matches any single character. '*' Matches zero or more of the preceding element. From 6e424f3c07471a58ade297c23abb5c4ad2c6a0b5 Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 18:04:22 +0430 Subject: [PATCH 05/11] Minor change --- dynamic_programming/regex_match.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index 316328d0448f..24c5e1923eaa 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -36,17 +36,19 @@ def recursive_match(text: str, pattern: str) -> bool: if text and not pattern: return False - if not text and pattern and pattern[-1] != '*': + if not text and pattern and pattern[-1] != "*": return False - if not text and pattern and pattern[-1] == '*': + if not text and pattern and pattern[-1] == "*": return recursive_match(text, pattern[:-2]) - if text[-1] == pattern[-1] or pattern[-1] == '.': + if text[-1] == pattern[-1] or pattern[-1] == ".": return recursive_match(text[:-1], pattern[:-1]) - if pattern[-1] == '*': - return recursive_match(text[:-1], pattern) or recursive_match(text, pattern[:-2]) + if pattern[-1] == "*": + return recursive_match(text[:-1], pattern) or recursive_match( + text, pattern[:-2] + ) return False @@ -82,14 +84,14 @@ def dp_match(text: str, pattern: str) -> bool: dp[i][0] = False for j in range(1, n + 1): - dp[0][j] = pattern[j - 1] == '*' and dp[0][j - 2] + dp[0][j] = pattern[j - 1] == "*" and dp[0][j - 2] for i in range(1, m + 1): for j in range(1, n + 1): - if pattern[j - 1] == '.' or pattern[j - 1] == text[i - 1]: + if pattern[j - 1] == "." or pattern[j - 1] == text[i - 1]: dp[i][j] = dp[i - 1][j - 1] - elif pattern[j - 1] == '*': - dp[i][j] = dp[i][j - 2] or (dp[i - 1][j] and (pattern[j - 2] == '.' or pattern[j - 2] == text[i - 1])) + elif pattern[j - 1] == "*": + dp[i][j] = dp[i][j - 2] or ((pattern[j - 2] == "." or pattern[j - 2] == text[i - 1]) and dp[i - 1][j]) else: dp[i][j] = False From d224cfd1df0c6cbb45bc7581c14de3d4ad4d451b Mon Sep 17 00:00:00 2001 From: AmirMohammad Hosseini Nasab Date: Fri, 12 Aug 2022 18:08:52 +0430 Subject: [PATCH 06/11] Minor change --- dynamic_programming/regex_match.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index 24c5e1923eaa..8f8c8c9e54f6 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -12,8 +12,8 @@ def recursive_match(text: str, pattern: str) -> bool: """ Recursive matching algorithm. - Time complexity: O(2 ^ (m + n)), where m is the length of text and n is the length of pattern. - Space complexity: Recursion depth is O(m + n). + Time complexity: O(2 ^ (|text| + |pattern|)) + Space complexity: Recursion depth is O(|text| + |pattern|). :param text: Text to match. :param pattern: Pattern to match. @@ -57,8 +57,8 @@ def dp_match(text: str, pattern: str) -> bool: """ Dynamic programming matching algorithm. - Time complexity: O(m * n), where m is the length of text and n is the length of pattern. - Space complexity: O(m * n). + Time complexity: O(|text| * |pattern|) + Space complexity: O(|text| * |pattern|) :param text: Text to match. :param pattern: Pattern to match. @@ -91,7 +91,9 @@ def dp_match(text: str, pattern: str) -> bool: if pattern[j - 1] == "." or pattern[j - 1] == text[i - 1]: dp[i][j] = dp[i - 1][j - 1] elif pattern[j - 1] == "*": - dp[i][j] = dp[i][j - 2] or ((pattern[j - 2] == "." or pattern[j - 2] == text[i - 1]) and dp[i - 1][j]) + dp[i][j] = dp[i][j - 2] + if pattern[j - 2] == "." or pattern[j - 2] == text[i - 1]: + dp[i][j] |= dp[i - 1][j] else: dp[i][j] = False From c2bd6bf9a2fd88ddd2a563a39538ea4ffb7aa846 Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:07:25 +0330 Subject: [PATCH 07/11] Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng --- dynamic_programming/regex_match.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index 8f8c8c9e54f6..efe7cce9e3bb 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -36,11 +36,8 @@ def recursive_match(text: str, pattern: str) -> bool: if text and not pattern: return False - if not text and pattern and pattern[-1] != "*": - return False - - if not text and pattern and pattern[-1] == "*": - return recursive_match(text, pattern[:-2]) + if not text: + return pattern[-1] == "*" and recursive_match(text, pattern[:-2]) if text[-1] == pattern[-1] or pattern[-1] == ".": return recursive_match(text[:-1], pattern[:-1]) From 7271e3d2a51e9a7177967fa40e68a20fbbaba2d2 Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:07:37 +0330 Subject: [PATCH 08/11] Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng --- dynamic_programming/regex_match.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index efe7cce9e3bb..6e5a3805aba6 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -30,11 +30,8 @@ def recursive_match(text: str, pattern: str) -> bool: >>> recursive_match('aa', '.*') True """ - if not text and not pattern: - return True - - if text and not pattern: - return False + if not pattern: + return not text if not text: return pattern[-1] == "*" and recursive_match(text, pattern[:-2]) From 4456366da0c3ca3e94eb0a2574c32977bc21a0ce Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:13:58 +0330 Subject: [PATCH 09/11] Fix ruff formatting in if statements --- dynamic_programming/regex_match.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index 6e5a3805aba6..c1c3326f60d1 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -82,11 +82,11 @@ def dp_match(text: str, pattern: str) -> bool: for i in range(1, m + 1): for j in range(1, n + 1): - if pattern[j - 1] == "." or pattern[j - 1] == text[i - 1]: + if pattern[j - 1] in {".", text[i - 1]}: dp[i][j] = dp[i - 1][j - 1] elif pattern[j - 1] == "*": dp[i][j] = dp[i][j - 2] - if pattern[j - 2] == "." or pattern[j - 2] == text[i - 1]: + if pattern[j - 2] in {".", text[i - 1]}: dp[i][j] |= dp[i - 1][j] else: dp[i][j] = False From 8c5aef0497aee2e176e1cd832f7f5362ac659a3c Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:02:30 +0330 Subject: [PATCH 10/11] Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng --- dynamic_programming/regex_match.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index c1c3326f60d1..ac0a3bd2a3d0 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -74,8 +74,6 @@ def dp_match(text: str, pattern: str) -> bool: dp = [[False for _ in range(n + 1)] for _ in range(m + 1)] dp[0][0] = True - for i in range(1, m + 1): - dp[i][0] = False for j in range(1, n + 1): dp[0][j] = pattern[j - 1] == "*" and dp[0][j - 2] From 475b5b5184bbf5ec91bdcd5a4afe937223ec5536 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:32:56 +0000 Subject: [PATCH 11/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- dynamic_programming/regex_match.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py index ac0a3bd2a3d0..200a882831c0 100644 --- a/dynamic_programming/regex_match.py +++ b/dynamic_programming/regex_match.py @@ -74,7 +74,6 @@ def dp_match(text: str, pattern: str) -> bool: dp = [[False for _ in range(n + 1)] for _ in range(m + 1)] dp[0][0] = True - for j in range(1, n + 1): dp[0][j] = pattern[j - 1] == "*" and dp[0][j - 2]