51 lines
1.4 KiB
Python
51 lines
1.4 KiB
Python
"""Knuth-Morris-Pratt substring search.
|
|
|
|
Provides:
|
|
- build_pi(pattern): prefix (failure) function, O(m)
|
|
- search(text, pattern): all start indices, O(n+m)
|
|
- search_count(text, pattern): number of (possibly overlapping) occurrences
|
|
|
|
Correctness notes:
|
|
* Empty pattern matches at every position (0..len(text)) per Python str.find semantics? We return [] for empty pattern to avoid O(n) noise; callers wanting positions can handle separately. Actually we return list(range(len(text)+1)) to match str.find('') behaviour? Tests pin: empty pattern -> [] (documented).
|
|
* Overlapping occurrences are reported.
|
|
"""
|
|
|
|
from typing import List
|
|
|
|
|
|
def build_pi(pattern: str) -> List[int]:
|
|
m = len(pattern)
|
|
pi = [0] * m
|
|
k = 0
|
|
for i in range(1, m):
|
|
while k > 0 and pattern[i] != pattern[k]:
|
|
k = pi[k - 1]
|
|
if pattern[i] == pattern[k]:
|
|
k += 1
|
|
pi[i] = k
|
|
return pi
|
|
|
|
|
|
def search(text: str, pattern: str) -> List[int]:
|
|
if not pattern:
|
|
return []
|
|
n, m = len(text), len(pattern)
|
|
if m > n:
|
|
return []
|
|
pi = build_pi(pattern)
|
|
res: List[int] = []
|
|
k = 0
|
|
for i in range(n):
|
|
while k > 0 and text[i] != pattern[k]:
|
|
k = pi[k - 1]
|
|
if text[i] == pattern[k]:
|
|
k += 1
|
|
if k == m:
|
|
res.append(i - m + 1)
|
|
k = pi[k - 1]
|
|
return res
|
|
|
|
|
|
def search_count(text: str, pattern: str) -> int:
|
|
return len(search(text, pattern))
|