use alloc::vec;
use alloc::vec::Vec;
use core::hash::Hash;
use core::ops::{Index, Range};
use crate::algorithms::{Capture, Compact, Replace, diff_deadline};
use crate::deadline_support::Instant;
use crate::{Algorithm, DiffOp};
pub fn capture_diff<Old, New>(
alg: Algorithm,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
) -> Vec<DiffOp>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
capture_diff_deadline(alg, old, old_range, new, new_range, None)
}
pub fn capture_diff_deadline<Old, New>(
alg: Algorithm,
old: &Old,
old_range: Range<usize>,
new: &New,
new_range: Range<usize>,
deadline: Option<Instant>,
) -> Vec<DiffOp>
where
Old: Index<usize> + ?Sized,
New: Index<usize> + ?Sized,
Old::Output: Hash + Eq,
New::Output: PartialEq<Old::Output> + Hash + Eq,
{
let mut d = Compact::new(Replace::new(Capture::new()), old, new);
diff_deadline(alg, &mut d, old, old_range, new, new_range, deadline).unwrap();
d.into_inner().into_inner().into_ops()
}
pub fn capture_diff_slices<T>(alg: Algorithm, old: &[T], new: &[T]) -> Vec<DiffOp>
where
T: Eq + Hash,
{
capture_diff_slices_deadline(alg, old, new, None)
}
pub fn capture_diff_slices_deadline<T>(
alg: Algorithm,
old: &[T],
new: &[T],
deadline: Option<Instant>,
) -> Vec<DiffOp>
where
T: Eq + Hash,
{
capture_diff_deadline(alg, old, 0..old.len(), new, 0..new.len(), deadline)
}
pub fn diff_ratio(ops: &[DiffOp], old_len: usize, new_len: usize) -> f32 {
let matches = ops
.iter()
.map(|op| {
if let DiffOp::Equal { len, .. } = *op {
len
} else {
0
}
})
.sum::<usize>();
let len = old_len + new_len;
if len == 0 {
1.0
} else {
2.0 * matches as f32 / len as f32
}
}
pub fn group_diff_ops(mut ops: Vec<DiffOp>, n: usize) -> Vec<Vec<DiffOp>> {
if ops.is_empty() {
return vec![];
}
let mut pending_group = Vec::new();
let mut rv = Vec::new();
if let Some(DiffOp::Equal {
old_index,
new_index,
len,
}) = ops.first_mut()
{
let offset = (*len).saturating_sub(n);
*old_index += offset;
*new_index += offset;
*len -= offset;
}
if let Some(DiffOp::Equal { len, .. }) = ops.last_mut() {
*len -= (*len).saturating_sub(n);
}
for op in ops.into_iter() {
if let DiffOp::Equal {
old_index,
new_index,
len,
} = op
{
if len > n * 2 {
pending_group.push(DiffOp::Equal {
old_index,
new_index,
len: n,
});
rv.push(pending_group);
let offset = len.saturating_sub(n);
pending_group = vec![DiffOp::Equal {
old_index: old_index + offset,
new_index: new_index + offset,
len: len - offset,
}];
continue;
}
}
pending_group.push(op);
}
match &pending_group[..] {
&[] | &[DiffOp::Equal { .. }] => {}
_ => rv.push(pending_group),
}
rv
}
#[test]
fn test_non_string_iter_change() {
use crate::ChangeTag;
let old = vec![1, 2, 3];
let new = vec![1, 2, 4];
let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
let changes: Vec<_> = ops
.iter()
.flat_map(|x| x.iter_changes(&old, &new))
.map(|x| (x.tag(), x.value()))
.collect();
assert_eq!(
changes,
vec![
(ChangeTag::Equal, 1),
(ChangeTag::Equal, 2),
(ChangeTag::Delete, 3),
(ChangeTag::Insert, 4),
]
);
}
#[test]
fn test_myers_compacts_adjacent_deletes_issue_80() {
let a: Vec<u8> = vec![0, 1, 0, 0, 0, 1, 2];
let b: Vec<u8> = vec![1, 0, 1];
let ops = capture_diff_slices(Algorithm::Myers, &a, &b);
let delete_lengths = ops
.iter()
.filter_map(|op| match op {
DiffOp::Delete { old_len, .. } => Some(*old_len),
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(delete_lengths, vec![1, 2, 1]);
}
#[test]
fn test_myers_unbalanced_regressions() {
{
let mut old = (0..3_000u32).collect::<Vec<_>>();
let mut new = (0..2_999u32).collect::<Vec<_>>();
old[2_999] = 1_000_000;
new.push(2_000_000);
new.extend((0..100_000u32).map(|i| 3_000_000 + i));
let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
assert_eq!(
ops,
vec![
DiffOp::Equal {
old_index: 0,
new_index: 0,
len: 2_999,
},
DiffOp::Replace {
old_index: 2_999,
old_len: 1,
new_index: 2_999,
new_len: 100_001,
},
]
);
}
{
let mut old = (0..3_008u32).collect::<Vec<_>>();
let mut new = (0..3_000u32).collect::<Vec<_>>();
for i in 0..8 {
old[3_000 + i] = 1_000_000 + i as u32;
}
new.extend((0..100_000u32).map(|i| 2_000_000 + i));
new[3_000 + 50_000] = 1_000_000;
let ops = capture_diff_slices(Algorithm::Myers, &old, &new);
assert!(ops.iter().any(|op| {
matches!(
op,
DiffOp::Equal {
old_index: 3_000,
new_index: 53_000,
len: 1,
}
)
}));
}
}