From f24ab2c60dabb11c37667c5899c39713e84fc871 Mon Sep 17 00:00:00 2001 From: Amir Hosseini <19665344+itsamirhn@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:07:41 +0330 Subject: [PATCH] Add: Two Regex match algorithm (Recursive & DP) (#6321) * Add recursive solution to regex_match.py * Add dp solution to regex_match.py * Add link to regex_match.py * Minor edit * Minor change * Minor change * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * Fix ruff formatting in if statements * Update dynamic_programming/regex_match.py Co-authored-by: Tianyi Zheng * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Tianyi Zheng Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- dynamic_programming/regex_match.py | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 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 000000000..200a88283 --- /dev/null +++ b/dynamic_programming/regex_match.py @@ -0,0 +1,97 @@ +""" +Regex matching check if a text matches pattern or not. +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 +""" + + +def recursive_match(text: str, pattern: str) -> bool: + """ + Recursive matching algorithm. + + Time complexity: O(2 ^ (|text| + |pattern|)) + Space complexity: Recursion depth is O(|text| + |pattern|). + + :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 + >>> recursive_match('aa', '.*') + True + """ + if not pattern: + return not text + + 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]) + + if pattern[-1] == "*": + return recursive_match(text[:-1], pattern) or recursive_match( + text, pattern[:-2] + ) + + return False + + +def dp_match(text: str, pattern: str) -> bool: + """ + Dynamic programming matching algorithm. + + Time complexity: O(|text| * |pattern|) + Space complexity: O(|text| * |pattern|) + + :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 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] 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] in {".", text[i - 1]}: + dp[i][j] |= dp[i - 1][j] + else: + dp[i][j] = False + + return dp[m][n] + + +if __name__ == "__main__": + import doctest + + doctest.testmod()