use std::cmp::min;
use std::mem::swap;
pub struct Levenshtein {
addrm_weight: usize,
subst_weight: usize,
}
impl Levenshtein {
pub fn new (addrm_weight: usize, subst_weight: usize)
-> Self
{
assert!(addrm_weight > 0);
assert!(subst_weight > 0);
Levenshtein { addrm_weight, subst_weight }
}
pub fn distance<T: PartialEq, U: AsRef<[T]>> (&self, a: U, b: U)
-> usize
{
let mut a = a.as_ref();
let mut b = b.as_ref();
if a == b { return 0; }
if a.len() > b.len() { swap (&mut a, &mut b); }
if a.len() == 0 { return b.len(); }
let mut cache: Vec<_> = (1 ..= a.len()).map (|x| x * self.addrm_weight).collect();
let mut result = 0;
for (i, bi) in b.iter().enumerate() {
result = (i+1) * self.addrm_weight;
let mut up = i * self.addrm_weight;
for (aj, c) in a.iter().zip (cache.iter_mut()) {
let diag = if bi == aj { up } else { up + self.subst_weight };
up = *c;
result = min (min (result + self.addrm_weight,
up + self.addrm_weight),
diag);
*c = result;
}
}
return result;
}
}
impl Default for Levenshtein {
fn default() -> Self {
Levenshtein { addrm_weight: 1, subst_weight: 1 }
}
}
pub fn distance<T: PartialEq, U: AsRef<[T]>> (a: U, b: U) -> usize
{
Levenshtein::default().distance (a, b)
}
#[cfg (test)]
mod tests {
use super::distance;
#[test]
fn identical_strings_should_have_zero_distance() {
assert_eq!(distance ("abc", "abc"), 0);
}
#[test]
fn insertions_should_increase_the_distance() {
assert_eq!(distance ("abc", "abcc"), 1);
assert_eq!(distance ("abc", "aabc"), 1);
assert_eq!(distance ("abc", "abbc"), 1);
}
#[test]
fn deletions_should_increase_the_distance() {
assert_eq!(distance ("abcd", "abc"), 1);
assert_eq!(distance ("aabc", "abc"), 1);
assert_eq!(distance ("abbc", "abc"), 1);
}
#[test]
fn bug_insert_at_beginning_of_longest_sequence() {
assert_eq!(distance ("abcdefg", "xabc"), 5);
}
#[test]
fn substitutions_should_increase_the_distance() {
assert_eq!(distance ("abc", "xbc"), 1);
assert_eq!(distance ("abc", "axc"), 1);
assert_eq!(distance ("abc", "abx"), 1);
}
#[test]
fn should_work_on_integer_slices() {
assert_eq!(distance (vec![0, 1, 2], vec![0, 1, 2]), 0);
assert_eq!(distance (vec![0, 1, 2], vec![0, 0, 1, 2]), 1);
assert_eq!(distance (vec![0, 1, 2], vec![1, 2]), 1);
assert_eq!(distance (vec![0, 1, 2], vec![3, 1, 2]), 1);
}
#[test]
fn should_work_on_sentences() {
assert_eq!(
distance (
"The quick brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>(),
"The quick brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>()),
0);
assert_eq!(
distance (
"The quick brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>(),
"The quick brown fox jumps over the very lazy dog".split (' ').collect::<Vec<_>>()),
1);
assert_eq!(
distance (
"The quick brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>(),
"The brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>()),
1);
assert_eq!(
distance (
"The quick brown fox jumps over the lazy dog".split (' ').collect::<Vec<_>>(),
"The quick brown cat jumps over the lazy dog".split (' ').collect::<Vec<_>>()),
1);
}
}