Regex Match

R
"""
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()