diff --git a/docs/source/pydatastructs/strings/algorithms.rst b/docs/source/pydatastructs/strings/algorithms.rst index aec29a31a..e5da44730 100644 --- a/docs/source/pydatastructs/strings/algorithms.rst +++ b/docs/source/pydatastructs/strings/algorithms.rst @@ -1,4 +1,5 @@ Algorithms ========== -.. autofunction:: pydatastructs.find \ No newline at end of file +.. autofunction:: pydatastructs.find +.. autofunction:: pydatastructs.manacher \ No newline at end of file diff --git a/pydatastructs/strings/__init__.py b/pydatastructs/strings/__init__.py index 33930b426..d19be2c42 100644 --- a/pydatastructs/strings/__init__.py +++ b/pydatastructs/strings/__init__.py @@ -12,7 +12,8 @@ __all__.extend(trie.__all__) from .algorithms import ( - find + find, + manacher ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/strings/algorithms.py b/pydatastructs/strings/algorithms.py index 1e26b9411..0eb8d804a 100644 --- a/pydatastructs/strings/algorithms.py +++ b/pydatastructs/strings/algorithms.py @@ -4,7 +4,8 @@ Backend, raise_if_backend_is_not_python) __all__ = [ - 'find' + 'find', + 'manacher' ] PRIME_NUMBER, MOD = 257, 1000000007 @@ -83,6 +84,57 @@ def find(text, query, algorithm, **kwargs): %(algorithm)) return getattr(algorithms, func)(text, query) +def manacher(text: str) -> str: + """ + Finds the longest palindromic substring in the given text using Manacher's algorithm. + + Parameters + ========== + text: str + The input string in which to find the longest palindromic substring. + + Returns + ======= + str + The longest palindromic substring found in the input text. + + Examples + ======== + >>> manacher("abacdfgdcaba") + 'aba' + >>> manacher("forgeeksskeegfor") + 'geeksskeeg' + """ + if not text: + return "" + + processed_text = "#" + "#".join(text) + "#" + n = len(processed_text) + lps = [0] * n + center = 0 + right = 0 + max_len = 0 + max_center = 0 + + for i in range(n): + if i < right: + lps[i] = min(right - i, lps[2 * center - i]) + + while (i + lps[i] + 1 < n and i - lps[i] - 1 >= 0 and + processed_text[i + lps[i] + 1] == processed_text[i - lps[i] - 1]): + lps[i] += 1 + + if lps[i] > max_len: + max_len = lps[i] + max_center = i + + if i + lps[i] > right: + center = i + right = i + lps[i] + + start = (max_center - max_len) // 2 + end = start + max_len + return text[start:end] def _knuth_morris_pratt(text, query): if len(text) == 0 or len(query) == 0: diff --git a/pydatastructs/strings/tests/test_algorithms.py b/pydatastructs/strings/tests/test_algorithms.py index 37622cf80..85679312d 100644 --- a/pydatastructs/strings/tests/test_algorithms.py +++ b/pydatastructs/strings/tests/test_algorithms.py @@ -1,4 +1,4 @@ -from pydatastructs.strings import find +from pydatastructs.strings import find, manacher import random, string @@ -14,6 +14,27 @@ def test_bm(): def test_zf(): _test_common_string_matching('z_function') +def test_manacher(): + """ + Test cases for Manacher's Algorithm to find the longest palindromic substring. + """ + test_cases = [ + # (input_text, expected_longest_palindrome) + ("", ""), # Empty string + ("a", "a"), # Single character + ("abacdfgdcaba", "aba"), # Odd-length palindrome + ("abba", "abba"), # Even-length palindrome + ("forgeeksskeegfor", "geeksskeeg"), # Long palindrome + ("abcde", "a"), # No palindrome longer than 1 + ("racecar", "racecar"), # Full string is a palindrome + ("abacaba", "abacaba"), # Overlapping palindromes + ("abacabacabb", "bacabacab"), # Complex case + ] + + for text, expected in test_cases: + result = manacher(text) + assert result == expected, f"Failed for input: {text}. Expected: {expected}, Got: {result}" + def _test_common_string_matching(algorithm): true_text_pattern_dictionary = { "Knuth-Morris-Pratt": "-Morris-",