-
-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathManacher.nim
92 lines (83 loc) · 3.09 KB
/
Manacher.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
## Manacher's algorithm
## --------------------
##
## Determine the longest palindrome in a string in linear time in its length
## with Manacher's algorithm.
## Inspired from:
## https://github.com/jilljenn/tryalgo/blob/master/tryalgo/manacher.py
## https://en.wikipedia.org/wiki/Longest_palindromic_substring
##
## :time complexity: O(n) where n is the string's length.
import std/[strutils, sequtils, sets]
{.push raises: [].}
runnableExamples:
let example1 = "cabbbab"
doAssert(manacherString(example1) == "abbba")
doAssert(manacherIndex(example1) == 1 .. 5)
doAssert(manacherLength(example1) == 5)
func manacherIndex*(s: string): HSlice[int, int] {.raises: [ValueError].} =
## Longest palindrome in a string by Manacher
## :param s: string, lowercase ascii, no whitespace
## :returns: indexes i,j such that s\[i:j\] is the longest palindrome in s
## :time complexity: O(n) where n is the string's length.
## All the indexes refer to an intermediate string t
## of the form "^#a#b#a#a#$" for s="abaa"
if s.len == 0:
raise newException(ValueError, "Empty string")
let extraSymbols = toHashSet(['$', '^', '#'])
let letters = toHashSet(s.toLowerAscii)
assert disjoint(extraSymbols, letters) # Forbidden letters
if s == "":
return 0 .. 1
let s = "^#" & join(s, "#") & "#$"
var
center = 1
distance = 1
p = repeat(0, len(s)) # Palindrome radii for each index in s
for index in 2 ..< len(s)-1:
# reflect index with respect to center
let mirror = 2 * center - index # = center - (index - center)
p[index] = max(0, min(distance - index, p[mirror]))
# grow palindrome centered in i
while s[index + 1 + p[index]] == s[index - 1 - p[index]]:
p[index] += 1
# adjust center if necessary
if index + p[index] > distance:
center = index
distance = index + p[index]
# find the argmax index in p
var
j = maxIndex(p)
k = p[j]
return (j - k) div 2 ..< (j + k) div 2 # extract solution
func manacherString*(s: string): string {.raises: [ValueError].} =
## Returns the greatest palindromic substring in `s`.
return s[manacherIndex(s)]
func manacherLength*(s: string): int {.raises: [ValueError].} =
## Returns the length of the greatest palindromic substring in `s`.
let
res = manacherIndex(s)
(i, j) = (res.a, res.b)
return j - i + 1
when isMainModule:
import std/unittest
suite "Manacher's algorithm":
test "Simple palindrome":
check manacherIndex("abbbab") == 0 .. 4
check manacherLength("abbbab") == 5
check manacherString("abbbab") == "abbba"
test "Single letter palindrome":
check manacherIndex("abcab") == 0 .. 0
check manacherLength("abcab") == 1
check manacherString("abcab") == "a"
test "Palindrome is full string":
check manacherIndex("telet") == 0 .. 4
check manacherLength("telet") == 5
check manacherString("telet") == "telet"
test "Empty string":
doAssertRaises(ValueError):
discard manacherIndex("")
doAssertRaises(ValueError):
discard manacherLength("")
doAssertRaises(ValueError):
discard manacherString("")