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}