textdistance 1.1.1

Lots of algorithms to compare how similar two sequences are
Documentation
//! 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());
        }
    }
}