From 1315034a44b3c12942e5a7bb6da334cd72a359dd Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sun, 27 Oct 2024 09:58:30 +0700 Subject: [PATCH] ref: improve shorted palindrome --- src/string/shortest_palindrome.rs | 121 +++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 37 deletions(-) diff --git a/src/string/shortest_palindrome.rs b/src/string/shortest_palindrome.rs index 80f52395194..f72a97119dd 100644 --- a/src/string/shortest_palindrome.rs +++ b/src/string/shortest_palindrome.rs @@ -1,72 +1,119 @@ -/* -The function shortest_palindrome expands the given string to shortest palindrome by adding a shortest prefix. -KMP. Source:https://www.scaler.com/topics/data-structures/kmp-algorithm/ -Prefix Functions and KPM. Source:https://oi-wiki.org/string/kmp/ -*/ +//! 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 p_chars: Vec = s.chars().collect(); - let suffix = raw_suffix_function(&p_chars); + let original_chars: Vec = s.chars().collect(); + let suffix_table = compute_suffix(&original_chars); - let mut s_chars: Vec = s.chars().rev().collect(); - // The prefix of the original string matches the suffix of the flipped string. - let dp = invert_suffix_function(&p_chars, &s_chars, &suffix); + let mut reversed_chars: Vec = 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); - s_chars.append(&mut p_chars[dp[p_chars.len() - 1]..p_chars.len()].to_vec()); - s_chars.iter().collect() + reversed_chars.append(&mut original_chars[prefix_match[original_chars.len() - 1]..].to_vec()); + reversed_chars.iter().collect() } -pub fn raw_suffix_function(p_chars: &[char]) -> Vec { - let mut suffix = vec![0; p_chars.len()]; - for i in 1..p_chars.len() { +/// 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 { + let mut suffix = vec![0; chars.len()]; + for i in 1..chars.len() { let mut j = suffix[i - 1]; - while j > 0 && p_chars[j] != p_chars[i] { + while j > 0 && chars[j] != chars[i] { j = suffix[j - 1]; } - suffix[i] = j + if p_chars[j] == p_chars[i] { 1 } else { 0 }; + suffix[i] = j + if chars[j] == chars[i] { 1 } else { 0 }; } suffix } -pub fn invert_suffix_function(p_chars: &[char], s_chars: &[char], suffix: &[usize]) -> Vec { - let mut dp = vec![0; p_chars.len()]; - dp[0] = if p_chars[0] == s_chars[0] { 1 } else { 0 }; - for i in 1..p_chars.len() { - let mut j = dp[i - 1]; - while j > 0 && s_chars[i] != p_chars[j] { +/// 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 { + 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]; } - dp[i] = j + if s_chars[i] == p_chars[j] { 1 } else { 0 }; + match_table[i] = j + if reversed[i] == original[j] { 1 } else { 0 }; } - dp + match_table } #[cfg(test)] mod tests { - use crate::string::shortest_palindrome; + use super::*; + use crate::string::is_palindrome; + macro_rules! test_shortest_palindrome { - ($($name:ident: $inputs:expr,)*) => { - $( - #[test] - fn $name() { - use crate::string::is_palindrome; - let (s, expected) = $inputs; - assert!(is_palindrome(expected)); - assert_eq!(shortest_palindrome(s), expected); - assert_eq!(shortest_palindrome(expected), expected); - } - )* + ($($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"), } }