//! Myers' diff algorithm. //! //! * time: `O((N+M)D)` //! * space `O(N+M)` //! //! See [the original article by Eugene W. Myers](http://www.xmailserver.org/diff2.pdf) //! describing it. use std::cmp::{max, min}; use std::ops::{Index, Range}; use crate::algorithms::DiffHook; /// Myers' diff algorithm. /// /// Diff `old`, between indices `old_range` and `new` between indices `new_range`. pub fn diff( d: &mut D, old: &Old, old_range: Range, new: &New, new_range: Range, ) -> Result<(), D::Error> where Old: Index + ?Sized, New: Index + ?Sized, D: DiffHook, New::Output: PartialEq, { diff_offsets( d, old, old_range.start, old_range.end, new, new_range.start, new_range.end, )?; d.finish() } /// Shortcut for diffing slices. pub fn diff_slices(d: &mut D, old: &[T], new: &[T]) -> Result<(), D::Error> where D: DiffHook, T: PartialEq, { diff(d, old, 0..old.len(), new, 0..new.len()) } fn modulo(a: isize, b: usize) -> usize { a.rem_euclid(b as isize) as usize } pub(crate) fn diff_offsets( diff: &mut D, old: &Old, old_current: usize, old_end: usize, new: &New, new_current: usize, new_end: usize, ) -> Result<(), D::Error> where D: DiffHook + ?Sized, Old: Index + ?Sized, New: Index + ?Sized, New::Output: PartialEq, { #![allow(clippy::many_single_char_names)] if old_end > old_current && new_end > new_current { let old_span = old_end - old_current; let new_span = new_end - new_current; let total_span = (old_span + new_span) as isize; let vec_size = (2 * min(old_span, new_span) + 2) as usize; let w = old_span as isize - new_span as isize; let mut vec_down = vec![0; vec_size as usize]; let mut vec_up = vec![0; vec_size as usize]; for i in 0..=(total_span / 2 + total_span % 2) { for &inverse in &[true, false][..] { let (v1, v2) = if inverse { (&mut vec_down, &mut vec_up) } else { (&mut vec_up, &mut vec_down) }; let j_start = -(i - 2 * max(0, i - new_span as isize)); let j_end = i - 2 * max(0, i - old_span as isize) + 1; for j in (j_start..j_end).step_by(2) { let mut a: usize = if j == -i || j != i && v1[modulo(j - 1, vec_size)] < v1[modulo(j + 1, vec_size)] { v1[modulo(j + 1, vec_size)] } else { v1[modulo(j - 1, vec_size)] + 1 }; let mut b = (a as isize - j) as usize; let (s, t) = (a, b); while a < old_span && b < new_span && { let (e_i, f_i) = if inverse { (a, b) } else { (old_span - a - 1, new_span - b - 1) }; new[new_current + f_i] == old[old_current + e_i] } { a += 1; b += 1; } v1[modulo(j, vec_size)] = a; let bound = if inverse { i - 1 } else { i }; if (total_span % 2 == 1) == inverse && w - j >= -bound && w - j <= bound && v1[modulo(j, vec_size)] + v2[modulo(w - j, vec_size)] >= old_span { let (x, y, u, v) = if inverse { (s, t, a, b) } else { (old_span - a, new_span - b, old_span - s, new_span - t) }; if i + bound > 1 || (x != u && y != v) { diff_offsets( diff, old, old_current, old_current + x, new, new_current, new_current + y, )?; if x != u { diff.equal(old_current + x, new_current + y, u - x)?; } diff_offsets( diff, old, old_current + u, old_end, new, new_current + v, new_end, )?; return Ok(()); } else if new_span > old_span { diff.equal(old_current, new_current, old_span)?; diff.insert( old_current + old_span, new_current + old_span, new_span - old_span, )?; return Ok(()); } else if new_span < old_span { diff.equal(old_current, new_current, new_span)?; diff.delete( old_current + new_span, old_span - new_span, new_current + new_span, )?; return Ok(()); } else { return Ok(()); } } } } } } else if old_end > old_current { diff.delete(old_current, old_end - old_current, new_current)? } else if new_end > new_current { diff.insert(old_current, new_current, new_end - new_current)? } Ok(()) } #[test] fn test_modulo() { assert_eq!(modulo(-11, 10), 9); assert_eq!(modulo(23, 7), 2); assert_eq!(modulo(-12, 6), 0); } #[test] fn test_diff() { let a: &[usize] = &[0, 1, 2, 3, 4]; let b: &[usize] = &[0, 1, 2, 9, 4]; let mut d = crate::algorithms::Replace::new(crate::algorithms::Capture::new()); diff_slices(&mut d, a, b).unwrap(); insta::assert_debug_snapshot!(d.into_inner().ops()); } #[test] fn test_contiguous() { let a: &[usize] = &[0, 1, 2, 3, 4, 4, 4, 5]; let b: &[usize] = &[0, 1, 2, 8, 9, 4, 4, 7]; let mut d = crate::algorithms::Replace::new(crate::algorithms::Capture::new()); diff_slices(&mut d, a, b).unwrap(); insta::assert_debug_snapshot!(d.into_inner().ops()); } #[test] fn test_pat() { let a: &[usize] = &[0, 1, 3, 4, 5]; let b: &[usize] = &[0, 1, 4, 5, 8, 9]; let mut d = crate::algorithms::Capture::new(); diff_slices(&mut d, a, b).unwrap(); insta::assert_debug_snapshot!(d.ops()); }