Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Shorted Palindrome Implementation #833

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 84 additions & 37 deletions src/string/shortest_palindrome.rs
Original file line number Diff line number Diff line change
@@ -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<char> = s.chars().collect();
let suffix = raw_suffix_function(&p_chars);
let original_chars: Vec<char> = s.chars().collect();
let suffix_table = compute_suffix(&original_chars);

let mut s_chars: Vec<char> = 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<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);

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<usize> {
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<usize> {
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<usize> {
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<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];
}
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"),
}
}