const STACK_CAP: usize = 32;
#[inline]
pub fn levenshtein(source: &[u8], target: &[u8]) -> usize {
let (short, long) = if source.len() <= target.len() {
(source, target)
} else {
(target, source)
};
let s_len = short.len();
let l_len = long.len();
if s_len == 0 {
return l_len;
}
if s_len < STACK_CAP {
let mut row = [0usize; STACK_CAP];
for j in 0..=s_len {
row[j] = j;
}
for i in 1..=l_len {
let mut prev_diag = row[0];
row[0] = i;
for j in 1..=s_len {
let old = row[j];
let cost = if short[j - 1] == long[i - 1] { 0 } else { 1 };
row[j] = (row[j - 1] + 1).min(row[j] + 1).min(prev_diag + cost);
prev_diag = old;
}
}
return row[s_len];
}
let mut row: Vec<usize> = (0..=s_len).collect();
for i in 1..=l_len {
let mut prev_diag = row[0];
row[0] = i;
for j in 1..=s_len {
let old = row[j];
let cost = if short[j - 1] == long[i - 1] { 0 } else { 1 };
row[j] = (row[j - 1] + 1).min(row[j] + 1).min(prev_diag + cost);
prev_diag = old;
}
}
row[s_len]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_slices() {
assert_eq!(levenshtein(&[1, 2, 3], &[1, 2, 3]), 0);
}
#[test]
fn both_empty() {
assert_eq!(levenshtein(&[], &[]), 0);
}
#[test]
fn one_empty() {
assert_eq!(levenshtein(&[], &[1, 2, 3]), 3);
assert_eq!(levenshtein(&[1, 2, 3], &[]), 3);
}
#[test]
fn single_substitution() {
assert_eq!(levenshtein(&[1, 2, 3], &[1, 9, 3]), 1);
}
#[test]
fn single_insertion() {
assert_eq!(levenshtein(&[1, 2, 3], &[1, 2, 9, 3]), 1);
}
#[test]
fn single_deletion() {
assert_eq!(levenshtein(&[1, 2, 3], &[1, 3]), 1);
}
#[test]
fn completely_different() {
assert_eq!(levenshtein(&[1, 2, 3], &[4, 5, 6]), 3);
}
#[test]
fn argument_order_irrelevant() {
let a = &[1, 2, 3, 4];
let b = &[1, 9, 3];
assert_eq!(levenshtein(a, b), levenshtein(b, a));
}
#[test]
fn single_element_slices() {
assert_eq!(levenshtein(&[1], &[1]), 0);
assert_eq!(levenshtein(&[1], &[2]), 1);
}
#[test]
fn multiple_edits_stack_path() {
assert_eq!(levenshtein(&[1, 2, 3, 4], &[1, 9, 3, 8]), 2);
assert_eq!(levenshtein(&[1, 2, 3, 4, 5], &[1, 9, 8, 7, 5]), 3);
assert_eq!(levenshtein(&[1, 2, 3], &[1, 2, 3, 4, 5]), 2);
}
#[test]
fn stack_boundary_at_cap_minus_one() {
let a: Vec<u8> = (0..31).collect();
let b: Vec<u8> = (0..31).collect();
assert_eq!(levenshtein(&a, &b), 0);
let mut c = a.clone();
c[0] = 255;
c[30] = 255;
assert_eq!(levenshtein(&a, &c), 2);
}
#[test]
fn heap_fallback_path() {
let a: Vec<u8> = (0..33).collect();
let b: Vec<u8> = (0..33).collect();
assert_eq!(levenshtein(&a, &b), 0);
let mut c = a.clone();
c[16] = 255;
assert_eq!(levenshtein(&a, &c), 1);
}
#[test]
fn heap_path_multiple_edits() {
let a: Vec<u8> = (0..35).collect();
let mut b = a.clone();
b[0] = 255;
b[17] = 255;
b[34] = 255;
assert_eq!(levenshtein(&a, &b), 3);
}
#[test]
fn asymmetric_lengths_exercises_both_roles() {
let short = &[1, 2, 3];
let long = &[1, 2, 3, 4, 5, 6];
assert_eq!(levenshtein(short, long), 3);
assert_eq!(levenshtein(long, short), 3);
}
#[test]
fn stack_boundary_exact_cap() {
let a: Vec<u8> = (0..32).collect();
let mut b: Vec<u8> = (0..32).collect();
b[0] = 255;
b[31] = 255;
assert_eq!(levenshtein(&a, &b), 2);
}
#[test]
fn deletion_heavy_stack() {
assert_eq!(levenshtein(&[1, 2, 3, 4, 5], &[5]), 4);
}
#[test]
fn insertion_heavy_stack() {
assert_eq!(levenshtein(&[5], &[1, 2, 3, 4, 5]), 4);
}
#[test]
fn mixed_ops_known_distance() {
let kitten = &[11, 9, 20, 20, 5, 14]; let sitting = &[19, 9, 20, 20, 9, 14, 7]; assert_eq!(levenshtein(kitten, sitting), 3);
}
#[test]
fn heap_deletion_heavy() {
let a: Vec<u8> = (0..40).collect();
let b: Vec<u8> = vec![39]; assert_eq!(levenshtein(&a, &b), 39);
}
}