#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditOp<'a> {
Keep(&'a str),
Delete(&'a str),
Insert(&'a str),
}
#[must_use]
pub fn myers_script<'a>(a: &[&'a str], b: &[&'a str]) -> Vec<EditOp<'a>> {
let n = a.len();
let m = b.len();
let max = n + m;
let off = max + 1;
let at = |k: isize| -> usize { (off as isize + k) as usize };
let mut v = vec![0usize; 2 * max + 3];
let mut trace: Vec<Vec<usize>> = Vec::new();
let mut found = false;
let mut d: isize = 0;
while d <= max as isize && !found {
trace.push(v.clone());
let mut k = -d;
while k <= d {
let mut x = if k == -d || (k != d && v[at(k - 1)] < v[at(k + 1)]) {
v[at(k + 1)]
} else {
v[at(k - 1)] + 1
};
let mut y = (x as isize - k) as usize;
while x < n && y < m && a[x] == b[y] {
x += 1;
y += 1;
}
v[at(k)] = x;
if x >= n && y >= m {
found = true;
break;
}
k += 2;
}
d += 1;
}
let mut ops: Vec<EditOp<'a>> = Vec::new();
let mut x = n;
let mut y = m;
for (d, vd) in trace.iter().enumerate().rev() {
let d = d as isize;
let k = x as isize - y as isize;
let prev_k = if k == -d || (k != d && vd[at(k - 1)] < vd[at(k + 1)]) {
k + 1
} else {
k - 1
};
let prev_x = vd[at(prev_k)];
let prev_y = prev_x as isize - prev_k;
while x > prev_x && y as isize > prev_y {
x -= 1;
y -= 1;
ops.push(EditOp::Keep(a[x]));
}
if d > 0 {
if x == prev_x {
ops.push(EditOp::Insert(b[prev_y as usize]));
} else {
ops.push(EditOp::Delete(a[prev_x]));
}
}
x = prev_x;
y = prev_y as usize;
}
ops.reverse();
ops
}
#[cfg(test)]
mod tests {
use super::*;
fn render(ops: &[EditOp<'_>]) -> String {
ops.iter()
.map(|op| match op {
EditOp::Keep(l) => format!(" {l}"),
EditOp::Delete(l) => format!("-{l}"),
EditOp::Insert(l) => format!("+{l}"),
})
.collect::<Vec<_>>()
.join("|")
}
#[test]
fn textbook_tie_case() {
let a: Vec<&str> = "a\nb\nc\na\nb\nb\na".split('\n').collect();
let b: Vec<&str> = "c\nb\na\nb\na\nc".split('\n').collect();
assert_eq!(render(&myers_script(&a, &b)), "-a|-b| c|+b| a| b|-b| a|+c");
}
#[test]
fn degenerate_sides() {
assert!(myers_script(&[], &[]).is_empty());
assert_eq!(render(&myers_script(&[], &["x"])), "+x");
assert_eq!(render(&myers_script(&["x"], &[])), "-x");
assert_eq!(render(&myers_script(&["x", "y"], &["x", "y"])), " x| y");
assert_eq!(
render(&myers_script(&["x", "y", "z"], &["x", "Y", "z"])),
" x|-y|+Y| z"
);
}
}