#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiffRun {
pub start: u64,
pub len: u64,
}
impl DiffRun {
pub const fn end(self) -> u64 {
self.start + self.len
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffSummary {
pub len_a: u64,
pub len_b: u64,
pub compared: u64,
pub differing: u64,
pub first: Option<u64>,
pub runs: Vec<DiffRun>,
}
impl DiffSummary {
pub const fn identical(&self) -> bool {
self.len_a == self.len_b && self.differing == 0
}
}
pub fn compare(a: &[u8], b: &[u8], gap: u64) -> DiffSummary {
let compared = a.len().min(b.len());
let mut runs: Vec<DiffRun> = Vec::new();
let mut differing = 0u64;
for offset in 0..compared {
if a[offset] == b[offset] {
continue;
}
differing += 1;
let offset = offset as u64;
match runs.last_mut() {
Some(last) if offset <= last.end().saturating_add(gap) => {
last.len = offset - last.start + 1;
}
_ => runs.push(DiffRun {
start: offset,
len: 1,
}),
}
}
DiffSummary {
len_a: a.len() as u64,
len_b: b.len() as u64,
compared: compared as u64,
differing,
first: runs.first().map(|run| run.start),
runs,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_inputs_produce_no_runs() {
let summary = compare(b"abcd", b"abcd", 0);
assert!(summary.identical());
assert_eq!(summary.differing, 0);
assert_eq!(summary.first, None);
assert!(summary.runs.is_empty());
}
#[test]
fn a_length_difference_alone_is_not_identical() {
let summary = compare(b"abcd", b"abcdef", 0);
assert!(!summary.identical());
assert_eq!(summary.compared, 4);
assert_eq!(summary.differing, 0);
assert_eq!(summary.first, None);
assert_eq!((summary.len_a, summary.len_b), (4, 6));
}
#[test]
fn reports_the_first_difference_and_each_byte_at_gap_zero() {
let summary = compare(b"abcde", b"aXcYe", 0);
assert_eq!(summary.first, Some(1));
assert_eq!(summary.differing, 2);
assert_eq!(
summary.runs,
[DiffRun { start: 1, len: 1 }, DiffRun { start: 3, len: 1 },]
);
}
#[test]
fn coalesces_spans_separated_by_at_most_the_gap() {
let summary = compare(b"abcde", b"aXcYe", 1);
assert_eq!(summary.differing, 2);
assert_eq!(summary.runs, [DiffRun { start: 1, len: 3 }]);
let wide = compare(b"abcde", b"XbcdY", 1);
assert_eq!(
wide.runs,
[DiffRun { start: 0, len: 1 }, DiffRun { start: 4, len: 1 },]
);
let merged = compare(b"abcde", b"XbcdY", 3);
assert_eq!(merged.runs, [DiffRun { start: 0, len: 5 }]);
}
#[test]
fn a_run_of_adjacent_differences_merges_at_any_gap() {
let summary = compare(&[0, 0, 0, 0], &[0, 1, 2, 3], 0);
assert_eq!(summary.runs, [DiffRun { start: 1, len: 3 }]);
assert_eq!(summary.differing, 3);
}
#[test]
fn only_the_common_prefix_is_compared() {
let summary = compare(b"abc", b"abcZZZZ", 0);
assert_eq!(summary.compared, 3);
assert_eq!(summary.differing, 0);
assert_eq!(summary.runs, []);
}
#[test]
fn empty_inputs_compare_cleanly() {
let summary = compare(&[], &[], 0);
assert!(summary.identical());
assert_eq!(summary.compared, 0);
}
}