Skip to main content

java_diff_utils_rs/algorithm/myers/
myers.rs

1use super::path_node::PathNode;
2use crate::algorithm::change::Change;
3use crate::algorithm::diff_algorithm_listener::DiffAlgorithmListener;
4use crate::algorithm::DiffAlgorithm;
5use crate::patch::delta_type::DeltaType;
6
7#[derive(Default)]
8pub struct DiffWorkspace {
9    arena: Vec<PathNode>,
10    diagonal: Vec<Option<usize>>,
11}
12
13impl DiffWorkspace {
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    pub fn clear(&mut self) {
19        self.arena.clear();
20        self.diagonal.fill(None);
21    }
22}
23
24pub struct MyersDiff<T> {
25    equalizer: Option<Box<dyn Fn(&T, &T) -> bool>>,
26}
27
28impl<T> Default for MyersDiff<T> {
29    fn default() -> Self {
30        Self { equalizer: None }
31    }
32}
33
34impl<T> MyersDiff<T> {
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    pub fn with_equalizer<F>(equalizer: F) -> Self
40    where
41        F: Fn(&T, &T) -> bool + 'static,
42    {
43        Self {
44            equalizer: Some(Box::new(equalizer)),
45        }
46    }
47}
48
49impl<T: PartialEq> DiffAlgorithm<T> for MyersDiff<T> {
50    fn diff_with_listener(
51        &self,
52        source: &[T],
53        target: &[T],
54        listener: &mut dyn DiffAlgorithmListener,
55    ) -> Vec<Change> {
56        if let Some(ref eq) = self.equalizer {
57            compute_diff_with_listener(source, target, eq, listener)
58        } else {
59            compute_diff_with_listener(source, target, |a, b| a == b, listener)
60        }
61    }
62}
63
64pub fn compute_diff<T: PartialEq>(source: &[T], target: &[T]) -> Vec<Change> {
65    compute_diff_with(source, target, |a, b| a == b)
66}
67
68pub fn compute_diff_with<T, F>(source: &[T], target: &[T], equalizer: F) -> Vec<Change>
69where
70    F: Fn(&T, &T) -> bool,
71{
72    let mut ws = DiffWorkspace::new();
73    compute_diff_with_workspace_and_listener(source, target, equalizer, &mut ws, None)
74}
75
76pub fn compute_diff_with_listener<T, F>(
77    source: &[T],
78    target: &[T],
79    equalizer: F,
80    listener: &mut dyn DiffAlgorithmListener,
81) -> Vec<Change>
82where
83    F: Fn(&T, &T) -> bool,
84{
85    let mut ws = DiffWorkspace::new();
86    compute_diff_with_workspace_and_listener(source, target, equalizer, &mut ws, Some(listener))
87}
88
89pub fn compute_diff_with_workspace<T, F>(
90    source: &[T],
91    target: &[T],
92    equalizer: F,
93    ws: &mut DiffWorkspace,
94) -> Vec<Change>
95where
96    F: Fn(&T, &T) -> bool,
97{
98    compute_diff_with_workspace_and_listener(source, target, equalizer, ws, None)
99}
100
101pub fn compute_diff_with_workspace_and_listener<T, F>(
102    source: &[T],
103    target: &[T],
104    equalizer: F,
105    ws: &mut DiffWorkspace,
106    mut listener: Option<&mut dyn DiffAlgorithmListener>,
107) -> Vec<Change>
108where
109    F: Fn(&T, &T) -> bool,
110{
111    if let Some(ref mut l) = listener {
112        l.diff_start();
113    }
114
115    if source.is_empty() && target.is_empty() {
116        if let Some(ref mut l) = listener {
117            l.diff_end();
118        }
119        return Vec::new();
120    }
121
122    ws.clear();
123
124    // Re-borrow listener using as_deref_mut()
125    let head_idx = build_path(source, target, &equalizer, ws, listener.as_deref_mut());
126
127    let result = if let Some(idx) = head_idx {
128        build_revision(&ws.arena, idx)
129    } else {
130        Vec::new()
131    };
132
133    if let Some(ref mut l) = listener {
134        l.diff_end();
135    }
136
137    result
138}
139
140pub fn build_path<'a, T, F>(
141    orig: &[T],
142    rev: &[T],
143    equalizer: &F,
144    ws: &mut DiffWorkspace,
145    // Explicit anonymous lifetime decouple on the trait object reference!
146    mut listener: Option<&'a mut (dyn DiffAlgorithmListener + '_)>,
147) -> Option<usize>
148where
149    F: Fn(&T, &T) -> bool,
150{
151    let n = orig.len();
152    let m = rev.len();
153    let max = n + m + 1;
154    let size = 1 + 2 * max;
155    let middle = max;
156
157    ws.arena.clear();
158    ws.arena.reserve(max * 2);
159
160    if ws.diagonal.len() < size {
161        ws.diagonal.resize(size, None);
162    } else {
163        ws.diagonal.fill(None);
164    }
165
166    ws.arena.push(PathNode {
167        i: 0,
168        j: -1,
169        is_snake: true,
170        is_bootstrap: true,
171        prev: None,
172    });
173    ws.diagonal[middle + 1] = Some(0);
174
175    for d in 0..max {
176        let d_isize = d as isize;
177
178        // Emit progress step once per edit distance iteration to match Java parity
179        if let Some(ref mut l) = listener {
180            l.path_node(d, max, d);
181        }
182
183        for k in (-d_isize..=d_isize).step_by(2) {
184            let kmiddle = (middle as isize + k) as usize;
185            let kplus = kmiddle + 1;
186            let kminus = kmiddle - 1;
187
188            let (i_start, prev_idx) = if k == -d_isize {
189                let p = ws.diagonal[kplus].unwrap_or(0);
190                (ws.arena[p].i, p)
191            } else if k != d_isize {
192                let pm = ws.diagonal[kminus];
193                let pp = ws.diagonal[kplus];
194
195                match (pm, pp) {
196                    (Some(pm), Some(pp)) => {
197                        if ws.arena[pm].i < ws.arena[pp].i {
198                            (ws.arena[pp].i, pp)
199                        } else {
200                            (ws.arena[pm].i + 1, pm)
201                        }
202                    }
203                    (None, Some(pp)) => (ws.arena[pp].i, pp),
204                    (Some(pm), None) => (ws.arena[pm].i + 1, pm),
205                    (None, None) => (0, 0),
206                }
207            } else {
208                let p = ws.diagonal[kminus].unwrap_or(0);
209                (ws.arena[p].i + 1, p)
210            };
211
212            let mut i = i_start;
213            let mut j = i as isize - k;
214
215            let collapsed_prev = PathNode::previous_snake(&ws.arena, prev_idx);
216
217            let node_idx = ws.arena.len();
218            ws.arena.push(PathNode {
219                i,
220                j,
221                is_snake: false,
222                is_bootstrap: false,
223                prev: collapsed_prev,
224            });
225
226            while i < n && j >= 0 && (j as usize) < m && equalizer(&orig[i], &rev[j as usize]) {
227                i += 1;
228                j += 1;
229            }
230
231            let final_node_idx = if i != ws.arena[node_idx].i {
232                let snake_idx = ws.arena.len();
233                ws.arena.push(PathNode {
234                    i,
235                    j,
236                    is_snake: true,
237                    is_bootstrap: false,
238                    prev: Some(node_idx),
239                });
240                snake_idx
241            } else {
242                node_idx
243            };
244
245            ws.diagonal[kmiddle] = Some(final_node_idx);
246
247            if i >= n && j >= 0 && (j as usize) >= m {
248                return Some(final_node_idx);
249            }
250        }
251    }
252
253    None
254}
255
256fn build_revision(arena: &[PathNode], head_idx: usize) -> Vec<Change> {
257    let mut raw_changes = Vec::new();
258    let mut curr_idx = Some(head_idx);
259
260    if let Some(idx) = curr_idx {
261        if arena[idx].is_snake {
262            curr_idx = arena[idx].prev;
263        }
264    }
265
266    loop {
267        let idx = match curr_idx {
268            Some(i) => i,
269            None => break,
270        };
271        let node = &arena[idx];
272
273        let prev_idx = match node.prev {
274            Some(p) => p,
275            None => break,
276        };
277
278        if arena[prev_idx].j < 0 {
279            break;
280        }
281
282        let i = node.i;
283        let j = node.j.max(0) as usize;
284
285        let path_idx = prev_idx;
286        let path_node = &arena[path_idx];
287        let ianchor = path_node.i;
288        let janchor = path_node.j.max(0) as usize;
289
290        let delta_type = match (ianchor == i, janchor == j) {
291            (true, false) => DeltaType::Insert,
292            (false, true) => DeltaType::Delete,
293            _ => DeltaType::Change,
294        };
295
296        raw_changes.push(Change {
297            delta_type,
298            start_original: ianchor,
299            end_original: i,
300            start_revised: janchor,
301            end_revised: j,
302        });
303
304        curr_idx = if arena[path_idx].is_snake {
305            arena[path_idx].prev
306        } else {
307            Some(path_idx)
308        };
309    }
310
311    raw_changes.reverse();
312    raw_changes
313}