Shortest Palindrome

//! This module provides functions for finding the shortest palindrome
//! that can be formed by adding characters to the left of a given string.
//! References
//!
//! - [KMP](https://www.scaler.com/topics/data-structures/kmp-algorithm/)
//! - [Prefix Functions and KPM](https://oi-wiki.org/string/kmp/)

/// Finds the shortest palindrome that can be formed by adding characters
/// to the left of the given string `s`.
///
/// # Arguments
///
/// * `s` - A string slice that holds the input string.
///
/// # Returns
///
/// Returns a new string that is the shortest palindrome, formed by adding
/// the necessary characters to the beginning of `s`.
pub fn shortest_palindrome(s: &str) -> String {
    if s.is_empty() {
        return "".to_string();
    }

    let original_chars: Vec<char> = s.chars().collect();
    let suffix_table = compute_suffix(&original_chars);

    let mut reversed_chars: Vec<char> = s.chars().rev().collect();
    // The prefix of the original string matches the suffix of the reversed string.
    let prefix_match = compute_prefix_match(&original_chars, &reversed_chars, &suffix_table);

    reversed_chars.append(&mut original_chars[prefix_match[original_chars.len() - 1]..].to_vec());
    reversed_chars.iter().collect()
}

/// Computes the suffix table used for the KMP (Knuth-Morris-Pratt) string
/// matching algorithm.
///
/// # Arguments
///
/// * `chars` - A slice of characters for which the suffix table is computed.
///
/// # Returns
///
/// Returns a vector of `usize` representing the suffix table. Each element
/// at index `i` indicates the longest proper suffix which is also a proper
/// prefix of the substring `chars[0..=i]`.
pub fn compute_suffix(chars: &[char]) -> Vec<usize> {
    let mut suffix = vec![0; chars.len()];
    for i in 1..chars.len() {
        let mut j = suffix[i - 1];
        while j > 0 && chars[j] != chars[i] {
            j = suffix[j - 1];
        }
        suffix[i] = j + if chars[j] == chars[i] { 1 } else { 0 };
    }
    suffix
}

/// Computes the prefix matches of the original string against its reversed
/// version using the suffix table.
///
/// # Arguments
///
/// * `original` - A slice of characters representing the original string.
/// * `reversed` - A slice of characters representing the reversed string.
/// * `suffix` - A slice containing the suffix table computed for the original string.
///
/// # Returns
///
/// Returns a vector of `usize` where each element at index `i` indicates the
/// length of the longest prefix of `original` that matches a suffix of
/// `reversed[0..=i]`.
pub fn compute_prefix_match(original: &[char], reversed: &[char], suffix: &[usize]) -> Vec<usize> {
    let mut match_table = vec![0; original.len()];
    match_table[0] = if original[0] == reversed[0] { 1 } else { 0 };
    for i in 1..original.len() {
        let mut j = match_table[i - 1];
        while j > 0 && reversed[i] != original[j] {
            j = suffix[j - 1];
        }
        match_table[i] = j + if reversed[i] == original[j] { 1 } else { 0 };
    }
    match_table
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::string::is_palindrome;

    macro_rules! test_shortest_palindrome {
        ($($name:ident: $inputs:expr,)*) => {
            $(
                #[test]
                fn $name() {
                    let (input, expected) = $inputs;
                    assert!(is_palindrome(expected));
                    assert_eq!(shortest_palindrome(input), expected);
                    assert_eq!(shortest_palindrome(expected), expected);
                }
            )*
        }
    }

    test_shortest_palindrome! {
        empty: ("", ""),
        extend_left_1: ("aacecaaa", "aaacecaaa"),
        extend_left_2: ("abcd", "dcbabcd"),
        unicode_1: ("അ", "അ"),
        unicode_2: ("a牛", "牛a牛"),
        single_char: ("x", "x"),
        already_palindrome: ("racecar", "racecar"),
        extend_left_3: ("abcde", "edcbabcde"),
        extend_left_4: ("abca", "acbabca"),
        long_string: ("abcdefg", "gfedcbabcdefg"),
        repetitive: ("aaaaa", "aaaaa"),
        complex: ("abacdfgdcaba", "abacdgfdcabacdfgdcaba"),
    }
}