Skip to main content

delta_struct/
seq.rs

1//! Positional diffing, behind the `ordered` field type.
2//!
3//! A field marked `#[delta_struct(field_type = "ordered")]` is diffed with
4//! Myers' algorithm and represented as a [`SeqDelta`] — a minimal edit script
5//! rather than the membership-only add/remove pair that `unordered` produces.
6//! Reach for it when a field's order carries meaning and you would rather send
7//! two splices than the whole sequence.
8//!
9//! The derive emits calls to [`diff`] and [`apply`]; you only need this module
10//! directly to inspect or construct a delta by hand.
11
12use similar::algorithms::{myers, DiffHook, Replace};
13use std::hash::Hash;
14use std::iter::FromIterator;
15use std::ops::Range;
16
17/// A positional diff between two sequences: an ordered edit script.
18///
19/// Splices are sorted by [`Splice::at`] and never overlap, and every `at`
20/// indexes the *old* sequence rather than the partially-rebuilt one. Holding
21/// to old coordinates is what lets [`apply`] run in a single forward pass
22/// without the index-shifting hazards an edit script usually brings.
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct SeqDelta<T> {
26    /// The edit script, in ascending order of [`Splice::at`].
27    pub splices: Vec<Splice<T>>,
28}
29
30impl<T> SeqDelta<T> {
31    /// Whether the two sequences were identical, and so nothing needs sending.
32    pub fn is_empty(&self) -> bool {
33        self.splices.is_empty()
34    }
35}
36
37impl<T> Default for SeqDelta<T> {
38    fn default() -> Self {
39        SeqDelta {
40            splices: Vec::new(),
41        }
42    }
43}
44
45/// One edit: drop `remove` items starting at `at`, then put `insert` in their
46/// place.
47///
48/// A pure insertion has `remove == 0`, a pure deletion has an empty `insert`,
49/// and `at` counts positions in the old sequence.
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct Splice<T> {
53    /// Where the edit starts, as an index into the old sequence.
54    pub at: usize,
55    /// How many old items the edit drops.
56    pub remove: usize,
57    /// The items to put in their place.
58    pub insert: Vec<T>,
59}
60
61/// Records the edit script as index ranges, so nothing is cloned or owned
62/// until [`diff`] materializes the inserts from `new`.
63#[derive(Default)]
64struct RangeHook {
65    ops: Vec<(usize, usize, Range<usize>)>,
66}
67
68impl DiffHook for RangeHook {
69    type Error = std::convert::Infallible;
70
71    fn equal(
72        &mut self,
73        _old_index: usize,
74        _new_index: usize,
75        _len: usize,
76    ) -> Result<(), Self::Error> {
77        Ok(())
78    }
79
80    fn delete(
81        &mut self,
82        old_index: usize,
83        old_len: usize,
84        new_index: usize,
85    ) -> Result<(), Self::Error> {
86        self.ops.push((old_index, old_len, new_index..new_index));
87        Ok(())
88    }
89
90    fn insert(
91        &mut self,
92        old_index: usize,
93        new_index: usize,
94        new_len: usize,
95    ) -> Result<(), Self::Error> {
96        self.ops
97            .push((old_index, 0, new_index..new_index + new_len));
98        Ok(())
99    }
100
101    fn replace(
102        &mut self,
103        old_index: usize,
104        old_len: usize,
105        new_index: usize,
106        new_len: usize,
107    ) -> Result<(), Self::Error> {
108        self.ops
109            .push((old_index, old_len, new_index..new_index + new_len));
110        Ok(())
111    }
112}
113
114/// Computes a minimal edit script turning `old` into `new`.
115///
116/// Returns an empty [`SeqDelta`] when the two are identical. Items need
117/// `Hash + Eq` rather than the `PartialEq` an `unordered` field asks for —
118/// that is what Myers' implementation in `similar` requires to index the
119/// sequences, and it is why a sequence of floats cannot be an `ordered` field.
120///
121/// ```
122/// use delta_struct::seq::{diff, Splice};
123///
124/// let delta = diff(vec![1, 2, 3, 4], vec![1, 9, 3, 4]);
125/// assert_eq!(
126///     delta.splices,
127///     vec![Splice { at: 1, remove: 1, insert: vec![9] }],
128/// );
129/// ```
130pub fn diff<C, I>(old: C, new: C) -> SeqDelta<I>
131where
132    C: IntoIterator<Item = I>,
133    I: Hash + Eq,
134{
135    let old: Vec<I> = old.into_iter().collect();
136    let new: Vec<I> = new.into_iter().collect();
137
138    // `Replace` coalesces an adjacent delete and insert into the single
139    // `replace` call that maps onto one splice.
140    let mut hook = Replace::new(RangeHook::default());
141    match myers::diff(&mut hook, &old[..], 0..old.len(), &new[..], 0..new.len()) {
142        Ok(()) => {}
143        Err(never) => match never {},
144    }
145    let ops = hook.into_inner().ops;
146
147    // The recorded ranges are ascending and non-overlapping in new coordinates
148    // too, so the inserted items can be pulled out of `new` in one pass rather
149    // than indexed out (which would demand `Clone`).
150    let mut new = new.into_iter();
151    let mut cursor = 0;
152    let splices = ops
153        .into_iter()
154        .map(|(at, remove, range)| {
155            new.by_ref().take(range.start - cursor).for_each(drop);
156            let insert = new.by_ref().take(range.end - range.start).collect();
157            cursor = range.end;
158            Splice { at, remove, insert }
159        })
160        .collect();
161
162    SeqDelta { splices }
163}
164
165/// Applies an edit script to `target` in place.
166///
167/// Because splice positions are old-coordinates and monotonically increasing,
168/// this walks the old sequence once and rebuilds it; no random access, and no
169/// index arithmetic that could drift as edits land.
170///
171/// A [`SeqDelta`] produced by [`diff`] always upholds the sorted,
172/// non-overlapping invariant. One that was hand-built or arrived over a wire
173/// might not, so out-of-order or overlong splices are clamped rather than
174/// allowed to panic; the result in that case is unspecified but the call
175/// still returns.
176///
177/// ```
178/// use delta_struct::seq::{apply, diff};
179///
180/// let delta = diff(vec![1, 2, 3, 4], vec![1, 9, 3, 4]);
181/// let mut target = vec![1, 2, 3, 4];
182/// apply(&mut target, delta);
183/// assert_eq!(target, vec![1, 9, 3, 4]);
184/// ```
185pub fn apply<C, I>(target: &mut C, delta: SeqDelta<I>)
186where
187    C: IntoIterator<Item = I> + FromIterator<I>,
188{
189    let old = std::mem::replace(target, std::iter::empty().collect());
190    let mut old = old.into_iter();
191    let mut out: Vec<I> = Vec::new();
192    let mut cursor = 0;
193    for Splice { at, remove, insert } in delta.splices {
194        out.extend(old.by_ref().take(at.saturating_sub(cursor)));
195        old.by_ref().take(remove).for_each(drop);
196        out.extend(insert);
197        cursor = at + remove;
198    }
199    out.extend(old);
200    *target = out.into_iter().collect();
201}