use std::hash::Hash; use std::ops::{Index, Range}; use std::time::Instant; use crate::algorithms::{diff_deadline, diff_slices_deadline, Capture, Replace}; use crate::{Algorithm, DiffOp}; /// Creates a diff between old and new with the given algorithm capturing the ops. /// /// This is like [`diff`](crate::algorithms::diff) but instead of using an /// arbitrary hook this will always use [`Replace`] + [`Capture`] and return the /// captured [`DiffOp`]s. pub fn capture_diff( alg: Algorithm, old: &Old, old_range: Range, new: &New, new_range: Range, ) -> Vec where Old: Index + ?Sized, New: Index + ?Sized, Old::Output: Hash + Eq + Ord, New::Output: PartialEq + Hash + Eq + Ord, { capture_diff_deadline(alg, old, old_range, new, new_range, None) } /// Creates a diff between old and new with the given algorithm capturing the ops. /// /// Works like [`capture_diff`] but with an optional deadline. pub fn capture_diff_deadline( alg: Algorithm, old: &Old, old_range: Range, new: &New, new_range: Range, deadline: Option, ) -> Vec where Old: Index + ?Sized, New: Index + ?Sized, Old::Output: Hash + Eq + Ord, New::Output: PartialEq + Hash + Eq + Ord, { let mut d = Replace::new(Capture::new()); diff_deadline(alg, &mut d, old, old_range, new, new_range, deadline).unwrap(); d.into_inner().into_ops() } /// Creates a diff between old and new with the given algorithm capturing the ops. pub fn capture_diff_slices(alg: Algorithm, old: &[T], new: &[T]) -> Vec where T: Eq + Hash + Ord, { capture_diff_slices_deadline(alg, old, new, None) } /// Creates a diff between old and new with the given algorithm capturing the ops. /// /// Works like [`capture_diff_slices`] but with an optional deadline. pub fn capture_diff_slices_deadline( alg: Algorithm, old: &[T], new: &[T], deadline: Option, ) -> Vec where T: Eq + Hash + Ord, { let mut d = Replace::new(Capture::new()); diff_slices_deadline(alg, &mut d, old, new, deadline).unwrap(); d.into_inner().into_ops() } /// Return a measure of similarity in the range `0..=1`. /// /// A ratio of `1.0` means the two sequences are a complete match, a /// ratio of `0.0` would indicate completely distinct sequences. The input /// is the sequence of diff operations and the length of the old and new /// sequence. pub fn get_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::(); let len = old_len + new_len; if len == 0 { 1.0 } else { 2.0 * matches as f32 / len as f32 } } /// Isolate change clusters by eliminating ranges with no changes. /// /// This will leave holes behind in long periods of equal ranges so that /// you can build things like unified diffs. pub fn group_diff_ops(mut ops: Vec, n: usize) -> Vec> { 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 { // End the current group and start a new one whenever // there is a large range with no changes. 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 }