1const CELLS: usize = 1 << 20;
29
30pub fn aligned(a: &[&str], b: &[&str]) -> Vec<(usize, usize)> {
32 let mut out = Vec::new();
33 let mut work = vec![(0, a.len(), 0, b.len())];
34 while let Some((mut a0, mut a1, mut b0, mut b1)) = work.pop() {
35 while a0 < a1 && b0 < b1 && a[a0] == b[b0] {
36 out.push((a0, b0));
37 a0 += 1;
38 b0 += 1;
39 }
40 while a1 > a0 && b1 > b0 && a[a1 - 1] == b[b1 - 1] {
41 a1 -= 1;
42 b1 -= 1;
43 out.push((a1, b1));
44 }
45 if a0 >= a1 || b0 >= b1 {
46 continue;
47 }
48 let anchors = anchors(&a[a0..a1], &b[b0..b1]);
49 if anchors.is_empty() {
50 if (a1 - a0).saturating_mul(b1 - b0) <= CELLS {
51 out.extend(
52 table(&a[a0..a1], &b[b0..b1]).into_iter().map(|(x, y)| (a0 + x, b0 + y)),
53 );
54 }
55 continue;
56 }
57 let (mut at, mut bt) = (a0, b0);
58 for (x, y) in anchors {
59 let (ax, by) = (a0 + x, b0 + y);
60 work.push((at, ax, bt, by));
61 out.push((ax, by));
62 at = ax + 1;
63 bt = by + 1;
64 }
65 work.push((at, a1, bt, b1));
66 }
67 out.sort_unstable();
68 out
69}
70
71fn anchors(a: &[&str], b: &[&str]) -> Vec<(usize, usize)> {
73 let once_in_a = once(a);
74 let once_in_b = once(b);
75 let mut pairs: Vec<(usize, usize)> = Vec::new();
76 for (x, line) in a.iter().enumerate() {
77 if once_in_a.get(line) == Some(&Some(x)) {
78 if let Some(&Some(y)) = once_in_b.get(line) {
79 pairs.push((x, y));
80 }
81 }
82 }
83 increasing(&pairs)
84}
85
86fn once<'a>(lines: &[&'a str]) -> std::collections::HashMap<&'a str, Option<usize>> {
88 let mut seen: std::collections::HashMap<&str, Option<usize>> = std::collections::HashMap::new();
89 for (at, line) in lines.iter().enumerate() {
90 seen.entry(line).and_modify(|e| *e = None).or_insert(Some(at));
91 }
92 seen
93}
94
95fn increasing(pairs: &[(usize, usize)]) -> Vec<(usize, usize)> {
101 if pairs.is_empty() {
102 return Vec::new();
103 }
104 let mut piles: Vec<usize> = Vec::new();
106 let mut came_from: Vec<Option<usize>> = vec![None; pairs.len()];
107 for (at, &(_, y)) in pairs.iter().enumerate() {
108 let pile = piles.partition_point(|&p| pairs[p].1 < y);
109 came_from[at] = if pile == 0 { None } else { Some(piles[pile - 1]) };
110 if pile == piles.len() {
111 piles.push(at);
112 } else {
113 piles[pile] = at;
114 }
115 }
116 let mut run = Vec::with_capacity(piles.len());
117 let mut at = piles.last().copied();
118 while let Some(i) = at {
119 run.push(pairs[i]);
120 at = came_from[i];
121 }
122 run.reverse();
123 run
124}
125
126fn table(a: &[&str], b: &[&str]) -> Vec<(usize, usize)> {
128 let (rows, cols) = (a.len() + 1, b.len() + 1);
129 let mut best = vec![0u32; rows * cols];
130 for x in (0..a.len()).rev() {
131 for y in (0..b.len()).rev() {
132 best[x * cols + y] = if a[x] == b[y] {
133 best[(x + 1) * cols + y + 1] + 1
134 } else {
135 best[(x + 1) * cols + y].max(best[x * cols + y + 1])
136 };
137 }
138 }
139 let mut out = Vec::new();
140 let (mut x, mut y) = (0, 0);
141 while x < a.len() && y < b.len() {
142 if a[x] == b[y] {
143 out.push((x, y));
144 x += 1;
145 y += 1;
146 } else if best[(x + 1) * cols + y] >= best[x * cols + y + 1] {
147 x += 1;
148 } else {
149 y += 1;
150 }
151 }
152 out
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 fn lines(text: &str) -> Vec<&str> {
160 text.split_whitespace().collect()
161 }
162
163 fn sound(a: &[&str], b: &[&str], pairs: &[(usize, usize)]) {
166 for (n, &(x, y)) in pairs.iter().enumerate() {
167 assert_eq!(a[x], b[y], "pair {n} is not two equal lines");
168 if n > 0 {
169 assert!(pairs[n - 1].0 < x && pairs[n - 1].1 < y, "pair {n} goes backwards");
170 }
171 }
172 }
173
174 #[test]
175 fn two_equal_files_line_up_entirely() {
176 let (a, b) = (lines("one two three"), lines("one two three"));
177 let pairs = aligned(&a, &b);
178 sound(&a, &b, &pairs);
179 assert_eq!(pairs, vec![(0, 0), (1, 1), (2, 2)]);
180 }
181
182 #[test]
183 fn a_line_added_in_the_middle_is_the_only_thing_unmatched() {
184 let (a, b) = (lines("one two five"), lines("one two three five"));
185 let pairs = aligned(&a, &b);
186 sound(&a, &b, &pairs);
187 assert_eq!(pairs, vec![(0, 0), (1, 1), (2, 3)]);
188 }
189
190 #[test]
191 fn nothing_in_common_matches_nothing() {
192 let (a, b) = (lines("one two"), lines("three four"));
193 assert_eq!(aligned(&a, &b), Vec::new());
194 }
195
196 #[test]
197 fn an_empty_side_matches_nothing() {
198 assert_eq!(aligned(&lines("one"), &[]), Vec::new());
199 assert_eq!(aligned(&[], &lines("one")), Vec::new());
200 }
201
202 #[test]
205 fn a_repeated_line_does_not_drag_the_alignment_out_of_order() {
206 let a = lines("#if A x #endif #if B y #endif");
207 let b = lines("#if B y #endif");
208 let pairs = aligned(&a, &b);
209 sound(&a, &b, &pairs);
210 let matched: Vec<&str> = pairs.iter().map(|&(x, _)| a[x]).collect();
211 assert_eq!(matched, vec!["#if", "B", "y", "#endif"]);
212 }
213
214 #[test]
216 fn a_swap_keeps_one_of_the_two() {
217 let (a, b) = (lines("head one two tail"), lines("head two one tail"));
218 let pairs = aligned(&a, &b);
219 sound(&a, &b, &pairs);
220 assert_eq!(pairs.len(), 3);
221 }
222
223 #[test]
224 fn a_file_that_grew_at_both_ends_keeps_its_middle() {
225 let (a, b) = (lines("middle"), lines("before middle after"));
226 let pairs = aligned(&a, &b);
227 sound(&a, &b, &pairs);
228 assert_eq!(pairs, vec![(0, 1)]);
229 }
230
231 #[test]
233 fn a_long_file_with_one_change_in_the_middle() {
234 let left: Vec<String> = (0..5000).map(|n| format!("line {n}")).collect();
235 let mut right = left.clone();
236 right[2500] = "line changed".to_owned();
237 let a: Vec<&str> = left.iter().map(String::as_str).collect();
238 let b: Vec<&str> = right.iter().map(String::as_str).collect();
239 let pairs = aligned(&a, &b);
240 sound(&a, &b, &pairs);
241 assert_eq!(pairs.len(), 4999);
242 }
243
244 #[test]
247 fn a_region_past_the_cap_with_no_anchor_is_left_unmatched() {
248 let side: Vec<String> = (0..2000).map(|n| format!("{}", n % 2)).collect();
249 let other: Vec<String> = (0..2000).map(|n| format!("{}", (n + 1) % 2)).collect();
250 let a: Vec<&str> = side.iter().map(String::as_str).collect();
251 let b: Vec<&str> = other.iter().map(String::as_str).collect();
252 assert!(a.len() * b.len() > CELLS);
253 assert_eq!(aligned(&a, &b), Vec::new());
254 }
255}