use crate::utils::normalize;
#[inline]
fn myers_32(a: &[u8], b: &[u8]) -> u32 {
let n = a.len();
let m = b.len();
let lst = 1u32 << (n - 1);
let mut peq = [0u32; 128];
for i in 0..n {
peq[a[i] as usize] |= 1u32 << i;
}
let mut pv = !0u32;
let mut mv = 0u32;
let mut sc = n as u32;
for j in 0..m {
let mut eq = peq[b[j] as usize];
let xv = eq | mv;
eq |= ((eq & pv).wrapping_add(pv)) ^ pv;
mv |= !(eq | pv);
pv &= eq;
if mv & lst != 0 { sc += 1; }
if pv & lst != 0 { sc -= 1; }
mv = (mv << 1) | 1;
pv = (pv << 1) | !(xv | mv);
mv &= xv;
}
sc
}
fn myers_x(a: &[u8], b: &[u8]) -> u32 {
let n = a.len();
let m = b.len();
let word_size = 32usize;
let hsize = (n + word_size - 1) / word_size;
let vsize = (m + word_size - 1) / word_size;
let mut phc = vec![!0u32; hsize];
let mut mhc = vec![0u32; hsize];
let mut peq = [0u32; 128];
for block in 0..vsize - 1 {
let mut mv = 0u32;
let mut pv = !0u32;
let start = block * word_size;
let end = start + word_size;
for &c in &peq { let _ = c; } for k in start..end {
peq[b[k] as usize] |= 1u32 << (k - start);
}
for i in 0..n {
let eq = peq[a[i] as usize];
let block_i = i / word_size;
let bit_i = i % word_size;
let pb = (phc[block_i] >> bit_i) & 1;
let mb = (mhc[block_i] >> bit_i) & 1;
let xv = eq | mv;
let xh = ((((eq | mb) & pv).wrapping_add(pv)) ^ pv) | eq | mb;
let mut ph = mv | !(xh | pv);
let mut mh = pv & xh;
if ((ph >> 31) ^ pb) != 0 { phc[block_i] ^= 1u32 << bit_i; }
if ((mh >> 31) ^ mb) != 0 { mhc[block_i] ^= 1u32 << bit_i; }
ph = (ph << 1) | pb;
mh = (mh << 1) | mb;
pv = mh | !(xv | ph);
mv = ph & xv;
}
for k in start..end {
peq[b[k] as usize] = 0;
}
}
let mut mv = 0u32;
let mut pv = !0u32;
let start = (vsize - 1) * word_size;
let vlen = m - start;
for k in start..m {
peq[b[k] as usize] |= 1u32 << (k - start);
}
let mut score = m as u32;
for i in 0..n {
let eq = peq[a[i] as usize];
let block_i = i / word_size;
let bit_i = i % word_size;
let pb = (phc[block_i] >> bit_i) & 1;
let mb = (mhc[block_i] >> bit_i) & 1;
let xv = eq | mv;
let xh = ((((eq | mb) & pv).wrapping_add(pv)) ^ pv) | eq | mb;
let mut ph = mv | !(xh | pv);
let mut mh = pv & xh;
if vlen > 0 {
score += (ph >> (vlen - 1)) & 1;
score -= (mh >> (vlen - 1)) & 1;
}
if ((ph >> 31) ^ pb) != 0 { phc[block_i] ^= 1u32 << bit_i; }
if ((mh >> 31) ^ mb) != 0 { mhc[block_i] ^= 1u32 << bit_i; }
ph = (ph << 1) | pb;
mh = (mh << 1) | mb;
pv = mh | !(xv | ph);
mv = ph & xv;
}
score
}
pub fn levenshtein(a: &str, b: &str) -> u32 {
let a_bytes = a.as_bytes();
let b_bytes = b.as_bytes();
let a_len = a_bytes.len();
let b_len = b_bytes.len();
if a_len == 0 { return b_len as u32; }
if b_len == 0 { return a_len as u32; }
if a_len > b_len {
return levenshtein_inner(b_bytes, a_bytes);
}
levenshtein_inner(a_bytes, b_bytes)
}
#[inline]
fn levenshtein_inner(short: &[u8], long: &[u8]) -> u32 {
if short.len() <= 32 {
myers_32(short, long)
} else {
myers_x(short, long)
}
}
pub fn levenshtein_normalized(a: &str, b: &str) -> f64 {
let max_len = a.len().max(b.len()) as u32;
normalize(levenshtein(a, b), max_len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_levenshtein_basic() {
assert_eq!(levenshtein("", ""), 0);
assert_eq!(levenshtein("", "abc"), 3);
assert_eq!(levenshtein("abc", ""), 3);
assert_eq!(levenshtein("kitten", "sitting"), 3);
assert_eq!(levenshtein("saturday", "sunday"), 3);
assert_eq!(levenshtein("test", "text"), 1);
assert_eq!(levenshtein("abc", "abc"), 0);
}
#[test]
fn test_levenshtein_long() {
let a = "The quick brown fox jumps over the lazy dog and runs away very far from home";
let b = "The quick brown dog jumps over the lazy fox and runs very very far away from home";
let dist = levenshtein(a, b);
assert!(dist > 0 && dist < 30, "levenshtein long: {}", dist);
}
#[test]
fn test_levenshtein_symmetry() {
assert_eq!(levenshtein("abc", "xyz"), levenshtein("xyz", "abc"));
}
#[test]
fn test_levenshtein_normalized() {
assert_eq!(levenshtein_normalized("", ""), 1.0);
assert!((levenshtein_normalized("kitten", "sitting") - 0.5714).abs() < 0.01);
}
}