pub fn levenshtein_distance(a: &str, b: &str) -> usize {
if a.is_empty() {
return b.chars().count();
} else if b.is_empty() {
return a.chars().count();
}
compute_distance(a, b)
}
fn compute_distance(a: &str, b: &str) -> usize {
let b_len = b.chars().count();
let mut prev_row = (0..=b_len).collect::<Vec<usize>>();
let mut curr_row = vec![0; b_len + 1];
for (i, ca) in a.chars().enumerate() {
curr_row[0] = i + 1;
for (j, cb) in b.chars().enumerate() {
let cost = if ca == cb { 0 } else { 1 };
curr_row[j + 1] = (prev_row[j + 1] + 1)
.min(curr_row[j] + 1)
.min(prev_row[j] + cost);
}
prev_row.copy_from_slice(&curr_row);
}
prev_row[b_len]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
assert_eq!(levenshtein_distance("", ""), 0);
assert_eq!(levenshtein_distance("", "abc"), 3);
assert_eq!(levenshtein_distance("abc", ""), 3);
}
#[test]
fn test_basic_cases() {
assert_eq!(levenshtein_distance("kitten", "sitting"), 3);
assert_eq!(levenshtein_distance("sunday", "saturday"), 3);
assert_eq!(levenshtein_distance("gumbo", "gambol"), 2);
assert_eq!(levenshtein_distance("abc", "abc"), 0);
assert_eq!(levenshtein_distance("flaw", "lawn"), 2);
}
#[test]
fn test_unicode() {
assert_eq!(levenshtein_distance("cafe", "coffee"), 3);
assert_eq!(levenshtein_distance("café", "cafe"), 1);
}
}