use crate::error::{SeqError, SeqResult};
#[derive(Debug, Clone)]
pub struct Manacher {
pub start: usize,
pub length: usize,
pub radii: Vec<usize>,
}
impl Manacher {
pub fn longest<'a>(&self, input: &'a [u8]) -> &'a [u8] {
&input[self.start..self.start + self.length]
}
pub fn palindrome_count(&self) -> usize {
self.radii.iter().map(|&r| r.div_ceil(2)).sum()
}
}
pub fn manacher(input: &[u8]) -> SeqResult<Manacher> {
if input.is_empty() {
return Err(SeqError::EmptyInput);
}
let n = input.len();
const GAP: u16 = 0; const LEFT_GUARD: u16 = 1;
const RIGHT_GUARD: u16 = 0xFFFF;
let trans_len = 2 * n + 1;
let mut t: Vec<u16> = Vec::with_capacity(trans_len + 2);
t.push(LEFT_GUARD);
for &byte in input {
t.push(GAP);
t.push(u16::from(byte) + 2);
}
t.push(GAP);
t.push(RIGHT_GUARD);
let mut p = vec![0usize; trans_len];
let mut center: isize = 0; let mut right: isize = 0;
for i in 0..trans_len as isize {
if i < right {
let mirror = 2 * center - i;
let bound = (right - i) as usize;
p[i as usize] = bound.min(p[mirror as usize]);
}
loop {
let radius = p[i as usize] as isize;
let left_pos = i - radius - 1 + 1; let right_pos = i + radius + 1 + 1; if t[left_pos as usize] == t[right_pos as usize] {
p[i as usize] += 1;
} else {
break;
}
}
if i + p[i as usize] as isize > right {
center = i;
right = i + p[i as usize] as isize;
}
}
let mut best_len = 0usize;
let mut best_center = 0usize;
for (i, &radius) in p.iter().enumerate() {
if radius > best_len {
best_len = radius;
best_center = i;
}
}
let start = (best_center - best_len) / 2;
Ok(Manacher {
start,
length: best_len,
radii: p,
})
}
pub fn longest_palindrome_str(input: &str) -> SeqResult<String> {
let m = manacher(input.as_bytes())?;
let slice = m.longest(input.as_bytes());
std::str::from_utf8(slice)
.map(|s| s.to_owned())
.map_err(|e| {
SeqError::InvalidObservation(format!("palindrome split a UTF-8 boundary: {e}"))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn brute_force(input: &[u8]) -> (usize, usize, usize) {
let n = input.len();
let mut best_len = 0usize;
let mut best_start = 0usize;
let mut count = 0usize;
let is_pal = |lo: usize, hi: usize| -> bool {
let (mut a, mut b) = (lo, hi);
while a < b {
if input[a] != input[b] {
return false;
}
a += 1;
b -= 1;
}
true
};
for lo in 0..n {
for hi in lo..n {
if is_pal(lo, hi) {
count += 1;
let len = hi - lo + 1;
if len > best_len {
best_len = len;
best_start = lo;
}
}
}
}
(best_len, best_start, count)
}
fn random_bytes(rng: &mut crate::handle::LcgRng, alphabet: &[u8], len: usize) -> Vec<u8> {
(0..len)
.map(|_| alphabet[rng.next_usize(alphabet.len())])
.collect()
}
#[test]
fn babad_returns_length_three() {
let m = manacher(b"babad").expect("non-empty");
assert_eq!(m.length, 3);
let got = m.longest(b"babad");
assert!(got == b"bab" || got == b"aba", "got {got:?}");
assert_eq!(got, b"bab");
}
#[test]
fn cbbd_returns_bb() {
let m = manacher(b"cbbd").expect("non-empty");
assert_eq!(m.length, 2);
assert_eq!(m.longest(b"cbbd"), b"bb");
}
#[test]
fn whole_string_palindrome() {
for s in [b"racecar".as_slice(), b"abba", b"a", b"level"] {
let m = manacher(s).expect("non-empty");
assert_eq!(m.length, s.len());
assert_eq!(m.longest(s), s);
}
}
#[test]
fn single_char() {
let m = manacher(b"z").expect("non-empty");
assert_eq!(m.length, 1);
assert_eq!(m.longest(b"z"), b"z");
assert_eq!(m.palindrome_count(), 1);
}
#[test]
fn all_same_char() {
let m = manacher(b"aaaa").expect("non-empty");
assert_eq!(m.length, 4);
assert_eq!(m.longest(b"aaaa"), b"aaaa");
assert_eq!(m.palindrome_count(), 10);
}
#[test]
fn even_and_odd_palindromes() {
let odd = manacher(b"xabax").expect("non-empty");
assert_eq!(odd.length, 5);
assert_eq!(odd.longest(b"xabax"), b"xabax");
let even = manacher(b"xabbax").expect("non-empty");
assert_eq!(even.length, 6);
assert_eq!(even.longest(b"xabbax"), b"xabbax");
let none = manacher(b"abcde").expect("non-empty");
assert_eq!(none.length, 1);
assert_eq!(none.palindrome_count(), 5);
}
#[test]
fn radius_array_matches_brute_force() {
let mut rng = crate::handle::LcgRng::new(123);
for &alphabet in &[b"ab".as_slice(), b"abc"] {
for _ in 0..400 {
let len = 1 + rng.next_usize(24);
let s = random_bytes(&mut rng, alphabet, len);
let m = manacher(&s).expect("non-empty");
let (bf_len, _bf_start, bf_count) = brute_force(&s);
assert_eq!(m.length, bf_len, "length mismatch for {s:?}");
let got = m.longest(&s);
assert_eq!(got.len(), bf_len);
let rev: Vec<u8> = got.iter().rev().copied().collect();
assert_eq!(got, rev.as_slice(), "returned slice is a palindrome");
assert_eq!(m.palindrome_count(), bf_count, "count mismatch for {s:?}");
}
}
}
#[test]
fn palindrome_count_hand_checked() {
let cases: &[(&[u8], usize)] = &[
(b"abc", 3), (b"aaa", 6), (b"aba", 4), (b"abba", 6), (b"", 0), ];
for &(s, expected) in cases {
if s.is_empty() {
assert!(matches!(manacher(s), Err(SeqError::EmptyInput)));
continue;
}
let m = manacher(s).expect("non-empty");
assert_eq!(m.palindrome_count(), expected, "for {s:?}");
let (_, _, bf) = brute_force(s);
assert_eq!(m.palindrome_count(), bf, "vs brute force for {s:?}");
}
}
#[test]
fn empty_input_errors() {
assert!(matches!(manacher(b""), Err(SeqError::EmptyInput)));
assert!(matches!(
longest_palindrome_str(""),
Err(SeqError::EmptyInput)
));
}
#[test]
fn str_wrapper() {
assert_eq!(longest_palindrome_str("babad").expect("ok"), "bab");
assert_eq!(longest_palindrome_str("cbbd").expect("ok"), "bb");
assert_eq!(longest_palindrome_str("racecar").expect("ok"), "racecar");
}
}