blob: 3ae492b38a9a2ab32c6fa9f4fdccc0d70f24c9a3 [file] [log] [blame]
//! Longest common subsequence
use crate::{Algorithm, Result};
use alloc::vec;
use alloc::vec::Vec;
/// The length of the [Longest common subsequence].
///
/// It differs from the [`LCSStr`](crate::LCSStr). Unlike substrings, subsequences are not required
/// to occupy consecutive positions within the original sequences.
///
/// [Longest common subsequence]: https://en.wikipedia.org/wiki/Longest_common_subsequence
#[derive(Default)]
pub struct LCSSeq {}
impl Algorithm<usize> for LCSSeq {
fn for_vec<E: Eq>(&self, s1: &[E], s2: &[E]) -> Result<usize> {
let l1 = s1.len();
let l2 = s2.len();
let mut lengths = vec![vec![0; l2 + 1]; l1 + 1];
for (i, char1) in s1.iter().enumerate() {
for (j, char2) in s2.iter().enumerate() {
lengths[i + 1][j + 1] = if char1 == char2 {
lengths[i][j] + 1
} else {
lengths[i + 1][j].max(lengths[i][j + 1])
};
}
}
let mut result = Vec::<&E>::new();
let mut i = l1;
let mut j = l2;
while i != 0 && j != 0 {
if lengths[i][j] == lengths[i - 1][j] {
i -= 1;
} else if lengths[i][j] == lengths[i][j - 1] {
j -= 1;
} else {
assert!(s1[i - 1] == s2[j - 1]);
result.push(&s1[i - 1]);
i -= 1;
j -= 1;
}
}
// val: Some(result.into_iter().rev().collect::<String>())
Result {
abs: result.len(),
is_distance: false,
max: l1.max(l2),
len1: l1,
len2: l2,
}
}
}
#[cfg(test)]
mod tests {
use crate::str::lcsseq;
use assert2::assert;
use proptest::prelude::*;
use rstest::rstest;
#[rstest]
#[case("", "", 0)]
#[case("", "abcd", 0)]
#[case("abcd", "", 0)]
#[case("ab", "cd", 0)]
#[case("abcd", "abcd", 4)] // "abcd"
#[case("test", "text", 3)] // "tet"
#[case("thisisatest", "testing123testing", 7)] // "tsitest"
#[case("abcd", "c", 1)] // "c"
#[case("abcd", "d", 1)] // "d"
#[case("abcd", "e", 0)] // ""
#[case("abcdefghi", "acegi", 5)] // "acegi"
#[case("abcdgh", "aedfhr", 3)] // "adh"
#[case("aggtab", "gxtxayb", 4)] // "gtab"
#[case("你好,世界", "再见世界", 2)] // "世界"
fn function_str(#[case] s1: &str, #[case] s2: &str, #[case] exp: usize) {
assert!(lcsseq(s1, s2) == exp);
}
proptest! {
#[test]
fn prop(s1 in ".*", s2 in ".*") {
let res = lcsseq(&s1, &s2);
let res2 = lcsseq(&s2, &s1);
prop_assert_eq!(res, res2);
prop_assert!(res <= s1.len() || res <= s2.len());
}
}
}