#[cfg(test)]
use alloc::vec;
use alloc::vec::Vec;
use core::hash::Hash;
use core::ops::{Index, Range};
use crate::types::MapType;
use crate::algorithms::utils::{common_prefix_len, common_suffix_len, is_empty_range};
use crate::algorithms::{DiffHook, IdentifyDistinct, NoFinishHook, myers, preflight};
use crate::deadline_support::{Instant, deadline_exceeded};
const MAX_CHAIN_LENGTH: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Anchor {
old_start: usize,
new_start: usize,
len: usize,
}
enum SearchResult {
Anchor(Anchor),
None,
Fallback,
}
pub fn diff<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
) -> Result<(), D::Error>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
D: DiffHook,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
diff_deadline(d, old, old_range, new, new_range, None)
}
pub fn diff_deadline<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> Result<(), D::Error>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
D: DiffHook,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
diff_deadline_impl(d, old, old_range, new, new_range, deadline, true, false)
}
pub fn diff_deadline_raw<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> Result<(), D::Error>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
D: DiffHook,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
diff_deadline_impl(d, old, old_range, new, new_range, deadline, false, true)
}
fn diff_deadline_impl<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
run_preflight: bool,
use_raw_myers: bool,
) -> Result<(), D::Error>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
D: DiffHook,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
if run_preflight
&& preflight::maybe_emit_disjoint_fast_path(
d,
old,
old_range.clone(),
new,
new_range.clone(),
deadline,
)?
{
return Ok(());
}
let h = IdentifyDistinct::<usize>::new(old, old_range, new, new_range);
diff_deadline_int(
d,
h.old_lookup(),
h.old_range(),
h.new_lookup(),
h.new_range(),
deadline,
use_raw_myers,
)
}
fn diff_deadline_int<Old, New, D>(
d: &mut D,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
use_raw_myers: bool,
) -> Result<(), D::Error>
where
Old: Index<usize, Output = usize> + ?Sized,
New: Index<usize, Output = usize> + ?Sized,
D: DiffHook,
{
let mut no_finish_d = NoFinishHook::new(d);
diff_impl(
&mut no_finish_d,
old,
old_range,
new,
new_range,
deadline,
use_raw_myers,
)?;
no_finish_d.into_inner().finish()
}
fn diff_impl<Old, New, D>(
d: &mut D,
old: &Old,
mut old_range: Range<usize>,
new: &New,
mut new_range: Range<usize>,
deadline: Option<Instant>,
use_raw_myers: bool,
) -> Result<(), D::Error>
where
Old: Index<usize, Output = usize> + ?Sized,
New: Index<usize, Output = usize> + ?Sized,
D: DiffHook,
{
if is_empty_range(&old_range) && is_empty_range(&new_range) {
return Ok(());
}
if is_empty_range(&old_range) {
d.insert(old_range.start, new_range.start, new_range.len())?;
return Ok(());
}
if is_empty_range(&new_range) {
d.delete(old_range.start, old_range.len(), new_range.start)?;
return Ok(());
}
let prefix = common_prefix_len(old, old_range.clone(), new, new_range.clone());
if prefix > 0 {
d.equal(old_range.start, new_range.start, prefix)?;
old_range.start += prefix;
new_range.start += prefix;
}
let suffix = common_suffix_len(old, old_range.clone(), new, new_range.clone());
let suffix_old_start = old_range.end - suffix;
let suffix_new_start = new_range.end - suffix;
old_range.end -= suffix;
new_range.end -= suffix;
if is_empty_range(&old_range) {
if !is_empty_range(&new_range) {
d.insert(old_range.start, new_range.start, new_range.len())?;
}
} else if is_empty_range(&new_range) {
d.delete(old_range.start, old_range.len(), new_range.start)?;
} else {
match find_anchor(old, old_range.clone(), new, new_range.clone(), deadline) {
SearchResult::Anchor(anchor) => {
let left_old = old_range.start..anchor.old_start;
let left_new = new_range.start..anchor.new_start;
diff_impl(d, old, left_old, new, left_new, deadline, use_raw_myers)?;
d.equal(anchor.old_start, anchor.new_start, anchor.len)?;
let right_old = (anchor.old_start + anchor.len)..old_range.end;
let right_new = (anchor.new_start + anchor.len)..new_range.end;
diff_impl(d, old, right_old, new, right_new, deadline, use_raw_myers)?;
}
SearchResult::None => {
d.delete(old_range.start, old_range.len(), new_range.start)?;
d.insert(old_range.start, new_range.start, new_range.len())?;
}
SearchResult::Fallback => {
let mut myers_hook = NoFinishHook::new(&mut *d);
if use_raw_myers {
myers::diff_deadline_raw(
&mut myers_hook,
old,
old_range.clone(),
new,
new_range.clone(),
deadline,
)?;
} else {
myers::diff_deadline(
&mut myers_hook,
old,
old_range.clone(),
new,
new_range.clone(),
deadline,
)?;
}
}
}
}
if suffix > 0 {
d.equal(suffix_old_start, suffix_new_start, suffix)?;
}
Ok(())
}
fn find_anchor<Old, New>(
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> SearchResult
where
Old: Index<usize, Output = usize> + ?Sized,
New: Index<usize, Output = usize> + ?Sized,
{
if deadline_exceeded(deadline) {
return SearchResult::Fallback;
}
let mut old_positions = MapType::<usize, Vec<usize>>::new();
let mut old_counts = MapType::<usize, usize>::new();
for old_idx in old_range.clone() {
let value = old[old_idx];
old_positions.entry(value).or_default().push(old_idx);
*old_counts.entry(value).or_insert(0) += 1;
}
let mut has_common = false;
let mut best: Option<Anchor> = None;
let mut best_count = usize::MAX;
for new_idx in new_range.clone() {
if deadline_exceeded(deadline) {
return SearchResult::Fallback;
}
let value = new[new_idx];
let Some(candidates) = old_positions.get(&value) else {
continue;
};
has_common = true;
if candidates.len() > MAX_CHAIN_LENGTH {
continue;
}
for &old_idx in candidates {
let mut old_start = old_idx;
let mut new_start = new_idx;
let mut old_end = old_idx;
let mut new_end = new_idx;
let mut min_count = candidates.len();
while old_start > old_range.start
&& new_start > new_range.start
&& old[old_start - 1] == new[new_start - 1]
{
old_start -= 1;
new_start -= 1;
let cnt = old_counts[&old[old_start]];
if cnt < min_count {
min_count = cnt;
}
}
while old_end + 1 < old_range.end
&& new_end + 1 < new_range.end
&& old[old_end + 1] == new[new_end + 1]
{
old_end += 1;
new_end += 1;
let cnt = old_counts[&old[old_end]];
if cnt < min_count {
min_count = cnt;
}
}
let len = old_end - old_start + 1;
let candidate = Anchor {
old_start,
new_start,
len,
};
let is_better = match best {
None => true,
Some(current) => {
candidate.len > current.len
|| (candidate.len == current.len
&& (min_count < best_count
|| (min_count == best_count
&& (candidate.old_start < current.old_start
|| (candidate.old_start == current.old_start
&& candidate.new_start < current.new_start)))))
}
};
if is_better {
best = Some(candidate);
best_count = min_count;
}
}
}
if let Some(anchor) = best {
return SearchResult::Anchor(anchor);
}
if has_common {
SearchResult::Fallback
} else {
SearchResult::None
}
}
#[test]
fn test_diff() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
let a: &[usize] = &[0, 1, 2, 3, 4];
let b: &[usize] = &[0, 1, 2, 9, 4];
assert_eq!(
capture_diff_slices(Algorithm::Histogram, a, b),
vec![
DiffOp::Equal {
old_index: 0,
new_index: 0,
len: 3,
},
DiffOp::Replace {
old_index: 3,
old_len: 1,
new_index: 3,
new_len: 1,
},
DiffOp::Equal {
old_index: 4,
new_index: 4,
len: 1,
}
]
);
}
#[test]
fn test_issue44_swapped_regression() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
let a: &[usize] = &[0, 1, 4, 5, 8, 9];
let b: &[usize] = &[0, 1, 3, 4, 5];
assert_eq!(
capture_diff_slices(Algorithm::Histogram, a, b),
vec![
DiffOp::Equal {
old_index: 0,
new_index: 0,
len: 2,
},
DiffOp::Insert {
old_index: 2,
new_index: 2,
new_len: 1,
},
DiffOp::Equal {
old_index: 2,
new_index: 3,
len: 2,
},
DiffOp::Delete {
old_index: 4,
old_len: 2,
new_index: 5,
},
]
);
}
#[test]
fn test_prefers_low_frequency_anchor_over_noise() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
let old = [1, 1, 1, 42, 2, 2, 2];
let new = [1, 1, 42, 2, 2, 2, 2];
let ops = capture_diff_slices(Algorithm::Histogram, &old, &new);
let equal_len = ops
.iter()
.map(|op| match *op {
DiffOp::Equal { len, .. } => len,
_ => 0,
})
.sum::<usize>();
assert_eq!(equal_len, 6);
}
#[test]
fn test_finish_called() {
struct HasRunFinish(bool);
impl DiffHook for HasRunFinish {
type Error = ();
fn finish(&mut self) -> Result<(), Self::Error> {
self.0 = true;
Ok(())
}
}
let mut d = HasRunFinish(false);
let slice = &[1, 2];
let slice2 = &[1, 2, 3];
diff(&mut d, slice, 0..slice.len(), slice2, 0..slice2.len()).unwrap();
assert!(d.0);
let mut d = HasRunFinish(false);
let slice = &[1, 2];
diff(&mut d, slice, 0..slice.len(), slice, 0..slice.len()).unwrap();
assert!(d.0);
let mut d = HasRunFinish(false);
let slice: &[u8] = &[];
diff(&mut d, slice, 0..slice.len(), slice, 0..slice.len()).unwrap();
assert!(d.0);
}
#[test]
fn test_subrange_regression() {
use crate::DiffOp;
use crate::algorithms::Capture;
let a: &[usize] = &[99, 0, 1, 4, 5, 8, 9, 88];
let b: &[usize] = &[77, 0, 1, 3, 4, 5, 66];
let mut d = Capture::new();
diff(&mut d, a, 1..7, b, 1..6).unwrap();
assert_eq!(
d.into_ops(),
vec![
DiffOp::Equal {
old_index: 1,
new_index: 1,
len: 2,
},
DiffOp::Insert {
old_index: 3,
new_index: 3,
new_len: 1,
},
DiffOp::Equal {
old_index: 3,
new_index: 4,
len: 2,
},
DiffOp::Delete {
old_index: 5,
old_len: 2,
new_index: 6,
},
]
);
}
#[test]
fn test_empty_sides() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
assert_eq!(
capture_diff_slices(Algorithm::Histogram, &[] as &[u8], &[1u8, 2]),
vec![DiffOp::Insert {
old_index: 0,
new_index: 0,
new_len: 2,
}]
);
assert_eq!(
capture_diff_slices(Algorithm::Histogram, &[1u8, 2], &[] as &[u8]),
vec![DiffOp::Delete {
old_index: 0,
old_len: 2,
new_index: 0,
}]
);
assert!(capture_diff_slices(Algorithm::Histogram, &[] as &[u8], &[] as &[u8]).is_empty());
}
#[test]
fn test_identical_input() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
assert_eq!(
capture_diff_slices(Algorithm::Histogram, &[1, 2, 3], &[1, 2, 3]),
vec![DiffOp::Equal {
old_index: 0,
new_index: 0,
len: 3,
}]
);
}
#[test]
fn test_no_common_elements() {
use crate::{Algorithm, DiffOp, capture_diff_slices};
assert_eq!(
capture_diff_slices(Algorithm::Histogram, &[1, 2, 3], &[4, 5]),
vec![DiffOp::Replace {
old_index: 0,
old_len: 3,
new_index: 0,
new_len: 2,
}]
);
}
#[cfg(feature = "std")]
#[test]
fn test_deadline_fallback() {
use std::ops::Index;
use std::time::Duration;
use crate::algorithms::{Capture, Replace};
let a = (0..64).collect::<Vec<_>>();
let mut b = a.clone();
b[10] = 999;
b[40] = 999;
struct SlowIndex<'a>(&'a [usize]);
impl Index<usize> for SlowIndex<'_> {
type Output = usize;
fn index(&self, index: usize) -> &Self::Output {
std::thread::sleep(Duration::from_millis(1));
&self.0[index]
}
}
let slow_a = SlowIndex(&a);
let slow_b = SlowIndex(&b);
let mut d = Replace::new(Capture::new());
diff_deadline(
&mut d,
&slow_a,
0..a.len(),
&slow_b,
0..b.len(),
Some(Instant::now() + Duration::from_millis(5)),
)
.unwrap();
let ops = d.into_inner().into_ops();
assert!(!ops.is_empty());
}
#[test]
fn test_cross_type_lookup_compatibility() {
use crate::{Algorithm, DiffOp, capture_diff};
use std::hash::{Hash, Hasher};
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
struct A(u32);
impl Hash for A {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
struct B(u32);
impl Hash for B {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialEq<A> for B {
fn eq(&self, other: &A) -> bool {
self.0 == other.0
}
}
let old = [A(1), A(2), A(3), A(4)];
let new = [B(1), B(2), B(9), B(4)];
let ops = capture_diff(Algorithm::Histogram, &old, 0..old.len(), &new, 0..new.len());
let equal_len = ops
.iter()
.map(|op| match op {
DiffOp::Equal { len, .. } => *len,
_ => 0,
})
.sum::<usize>();
assert_eq!(equal_len, 3);
}
#[test]
fn test_deterministic_tie_breaking() {
use crate::{Algorithm, capture_diff_slices};
let old = [1, 2, 1, 2, 3, 4, 3, 4];
let new = [1, 1, 2, 2, 3, 3, 4, 4];
let first = capture_diff_slices(Algorithm::Histogram, &old, &new);
for _ in 0..8 {
let next = capture_diff_slices(Algorithm::Histogram, &old, &new);
assert_eq!(first, next);
}
}