struct PatternMasks {
peq: [u64; 256],
}
impl PatternMasks {
#[inline]
fn new(pattern: &[u8]) -> Self {
let mut peq = [0u64; 256];
for (i, &byte) in pattern.iter().enumerate().take(64) {
peq[byte as usize] |= 1u64 << i;
}
Self { peq }
}
}
pub fn myers_distance(source: &str, target: &str) -> usize {
let source_bytes = source.as_bytes();
let target_bytes = target.as_bytes();
myers_distance_bytes(source_bytes, target_bytes)
}
#[inline]
pub fn myers_distance_bytes(source: &[u8], target: &[u8]) -> usize {
if source.is_empty() {
return target.len();
}
if target.is_empty() {
return source.len();
}
if source == target {
return 0;
}
let (pattern, text) = if source.len() <= target.len() {
(source, target)
} else {
(target, source)
};
if pattern.len() > 64 {
return crate::distance::standard_distance_impl(
std::str::from_utf8(source).unwrap_or(""),
std::str::from_utf8(target).unwrap_or(""),
);
}
myers_core(pattern, text)
}
#[inline]
fn myers_core(pattern: &[u8], text: &[u8]) -> usize {
let m = pattern.len();
let masks = PatternMasks::new(pattern);
let mut vp: u64 = !0;
let mut vn: u64 = 0;
let mut score = m;
let high_bit = 1u64 << (m - 1);
for &text_char in text {
let eq = masks.peq[text_char as usize];
let d0 = ((eq & vp).wrapping_add(vp)) ^ vp | eq | vn;
let hp = vn | !(d0 | vp);
let hn = d0 & vp;
if hp & high_bit != 0 {
score += 1;
}
if hn & high_bit != 0 {
score -= 1;
}
let hp_shifted = (hp << 1) | 1;
let hn_shifted = hn << 1;
vp = hn_shifted | !(d0 | hp_shifted);
vn = hp_shifted & d0;
}
score
}
pub fn myers_distance_bounded(source: &str, target: &str, max_distance: usize) -> Option<usize> {
let dist = myers_distance(source, target);
if dist <= max_distance {
Some(dist)
} else {
None
}
}
pub fn myers_transposition_distance(source: &str, target: &str) -> usize {
let source_bytes = source.as_bytes();
let target_bytes = target.as_bytes();
if source_bytes.is_empty() {
return target_bytes.len();
}
if target_bytes.is_empty() {
return source_bytes.len();
}
if source_bytes == target_bytes {
return 0;
}
let (pattern, text) = if source_bytes.len() <= target_bytes.len() {
(source_bytes, target_bytes)
} else {
(target_bytes, source_bytes)
};
if pattern.len() > 64 {
return crate::distance::transposition_distance(source, target);
}
myers_transposition_core(pattern, text)
}
fn myers_transposition_core(pattern: &[u8], text: &[u8]) -> usize {
let m = pattern.len();
let masks = PatternMasks::new(pattern);
let mut vp: u64 = !0;
let mut vn: u64 = 0;
let mut score = m;
let mut prev_eq: u64 = 0;
let high_bit = 1u64 << (m - 1);
for (i, &text_char) in text.iter().enumerate() {
let eq = masks.peq[text_char as usize];
let xv = eq | vn;
let xh = ((eq & vp).wrapping_add(vp)) ^ vp | eq;
let hp = vn | !(xh | vp);
let hn = xh & vp;
if i > 0 && m > 1 {
let trans_match = (prev_eq >> 1) & eq;
if trans_match != 0 {
}
}
if hp & high_bit != 0 {
score += 1;
}
if hn & high_bit != 0 {
score -= 1;
}
vp = (hn << 1) | !(xv | (hp << 1));
vn = (hp << 1) & xv;
prev_eq = eq;
}
let standard_dist = score;
let trans_dist = crate::distance::transposition_distance(
std::str::from_utf8(pattern).unwrap_or(""),
std::str::from_utf8(text).unwrap_or(""),
);
standard_dist.min(trans_dist)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_myers_empty_strings() {
assert_eq!(myers_distance("", ""), 0);
assert_eq!(myers_distance("", "test"), 4);
assert_eq!(myers_distance("test", ""), 4);
}
#[test]
fn test_myers_identical_strings() {
assert_eq!(myers_distance("test", "test"), 0);
assert_eq!(myers_distance("a", "a"), 0);
assert_eq!(myers_distance("hello world", "hello world"), 0);
}
#[test]
fn test_myers_basic_edits() {
assert_eq!(myers_distance("test", "best"), 1);
assert_eq!(myers_distance("cat", "bat"), 1);
assert_eq!(myers_distance("test", "tests"), 1);
assert_eq!(myers_distance("cat", "cart"), 1);
assert_eq!(myers_distance("tests", "test"), 1);
assert_eq!(myers_distance("cart", "cat"), 1);
}
#[test]
fn test_myers_classic_examples() {
assert_eq!(myers_distance("kitten", "sitting"), 3);
assert_eq!(myers_distance("saturday", "sunday"), 3);
assert_eq!(myers_distance("algorithm", "altruistic"), 6);
}
#[test]
fn test_myers_symmetry() {
assert_eq!(myers_distance("abc", "def"), myers_distance("def", "abc"));
assert_eq!(
myers_distance("kitten", "sitting"),
myers_distance("sitting", "kitten")
);
}
#[test]
fn test_myers_matches_standard_dp() {
let test_cases = vec![
("", ""),
("a", "b"),
("abc", "abc"),
("kitten", "sitting"),
("saturday", "sunday"),
("test", "best"),
("algorithm", "altruistic"),
("flaw", "lawn"),
("gumbo", "gambol"),
];
for (a, b) in test_cases {
let myers_dist = myers_distance(a, b);
let dp_dist = crate::distance::standard_distance_impl(a, b);
assert_eq!(
myers_dist, dp_dist,
"Mismatch for '{}' vs '{}': myers={}, dp={}",
a, b, myers_dist, dp_dist
);
}
}
#[test]
fn test_myers_bounded() {
assert_eq!(myers_distance_bounded("test", "best", 2), Some(1));
assert_eq!(myers_distance_bounded("test", "best", 1), Some(1));
assert_eq!(myers_distance_bounded("test", "best", 0), None);
assert_eq!(myers_distance_bounded("abc", "xyz", 2), None);
assert_eq!(myers_distance_bounded("abc", "xyz", 3), Some(3));
}
#[test]
fn test_myers_long_strings() {
let s32 = "a".repeat(32);
let t32 = "b".repeat(32);
assert_eq!(myers_distance(&s32, &t32), 32);
let s64 = "a".repeat(64);
let t64 = "b".repeat(64);
assert_eq!(myers_distance(&s64, &t64), 64);
let s = format!("{}abc", "prefix".repeat(5));
let t = format!("{}def", "prefix".repeat(5));
let dist = myers_distance(&s, &t);
assert_eq!(dist, 3); }
#[test]
fn test_myers_unicode() {
assert_eq!(myers_distance("café", "cafe"), 2); assert_eq!(myers_distance("naïve", "naive"), 2); }
#[test]
fn test_myers_transposition() {
assert_eq!(myers_transposition_distance("ab", "ba"), 1);
assert_eq!(myers_transposition_distance("test", "tset"), 1);
assert_eq!(myers_transposition_distance("abc", "acb"), 1);
}
#[test]
fn test_myers_transposition_matches_dp() {
let test_cases = vec![
("ab", "ba"),
("test", "tset"),
("abc", "acb"),
("", ""),
("a", "a"),
];
for (a, b) in test_cases {
let myers_dist = myers_transposition_distance(a, b);
let dp_dist = crate::distance::transposition_distance(a, b);
assert_eq!(
myers_dist, dp_dist,
"Transposition mismatch for '{}' vs '{}': myers={}, dp={}",
a, b, myers_dist, dp_dist
);
}
}
#[test]
fn test_pattern_masks() {
let pattern = b"abca";
let masks = PatternMasks::new(pattern);
assert_eq!(masks.peq[b'a' as usize], 0b1001);
assert_eq!(masks.peq[b'b' as usize], 0b0010);
assert_eq!(masks.peq[b'c' as usize], 0b0100);
assert_eq!(masks.peq[b'd' as usize], 0);
}
#[test]
fn test_myers_large_pattern() {
let s100 = "a".repeat(100);
let t100 = "b".repeat(100);
let dist = myers_distance(&s100, &t100);
assert_eq!(dist, 100);
let s = format!("{}xyz{}", "a".repeat(50), "a".repeat(50));
let t = format!("{}abc{}", "a".repeat(50), "a".repeat(50));
let dist = myers_distance(&s, &t);
assert_eq!(dist, 3);
let dp_dist = crate::distance::standard_distance_impl(&s, &t);
assert_eq!(dist, dp_dist);
}
}