1use yo_common::{Code, Error, Result};
22
23pub const LCS_MAX_CELLS: usize = 64 * 1024 * 1024;
31
32const NO_MEMORY: &str = "Insufficient memory, failed allocating transient memory for LCS";
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct Match {
41 pub a: (u32, u32),
43 pub b: (u32, u32),
45 pub len: u32,
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct Idx {
52 pub matches: Vec<Match>,
55 pub len: usize,
58}
59
60struct Table<'a> {
62 cells: Vec<u32>,
63 a: &'a [u8],
64 b: &'a [u8],
65}
66
67impl<'a> Table<'a> {
68 fn build(a: &'a [u8], b: &'a [u8]) -> Result<Table<'a>> {
69 let (alen, blen) = (a.len(), b.len());
70 let cells = alen
71 .checked_add(1)
72 .and_then(|r| blen.checked_add(1).and_then(|c| r.checked_mul(c)))
73 .filter(|&n| n <= LCS_MAX_CELLS)
74 .ok_or_else(|| Error::new(Code::Full, NO_MEMORY))?;
75
76 let stride = blen + 1;
77 let mut cells = vec![0u32; cells];
78 for i in 1..=alen {
79 for j in 1..=blen {
80 let v = if a[i - 1] == b[j - 1] {
81 cells[(i - 1) * stride + (j - 1)] + 1
82 } else {
83 cells[(i - 1) * stride + j].max(cells[i * stride + (j - 1)])
84 };
85 cells[i * stride + j] = v;
86 }
87 }
88 Ok(Table { cells, a, b })
89 }
90
91 #[inline]
92 fn at(&self, i: usize, j: usize) -> u32 {
93 self.cells[i * (self.b.len() + 1) + j]
94 }
95
96 #[inline]
98 fn total(&self) -> usize {
99 self.at(self.a.len(), self.b.len()) as usize
100 }
101}
102
103pub fn len(a: &[u8], b: &[u8]) -> Result<usize> {
107 Ok(Table::build(a, b)?.total())
108}
109
110pub fn string(a: &[u8], b: &[u8]) -> Result<Vec<u8>> {
112 let t = Table::build(a, b)?;
113 let mut out = vec![0u8; t.total()];
114 walk(&t, 0, &mut out, &mut Vec::new());
115 Ok(out)
116}
117
118pub fn idx(a: &[u8], b: &[u8], minmatchlen: u32) -> Result<Idx> {
125 let t = Table::build(a, b)?;
126 let mut matches = Vec::new();
127 let mut sink = vec![0u8; t.total()];
128 walk(&t, minmatchlen, &mut sink, &mut matches);
129 Ok(Idx {
130 matches,
131 len: t.total(),
132 })
133}
134
135fn walk(t: &Table<'_>, minmatchlen: u32, out: &mut [u8], matches: &mut Vec<Match>) {
142 let (alen, blen) = (t.a.len(), t.b.len());
143 let (mut i, mut j) = (alen, blen);
144 let mut idx = t.total();
145
146 let (mut a_start, mut a_end) = (alen, 0usize);
149 let (mut b_start, mut b_end) = (0usize, 0usize);
150
151 while i > 0 && j > 0 {
152 let mut emit = false;
153 if t.a[i - 1] == t.b[j - 1] {
154 out[idx - 1] = t.a[i - 1];
155
156 if a_start == alen {
157 a_start = i - 1;
158 a_end = i - 1;
159 b_start = j - 1;
160 b_end = j - 1;
161 } else if a_start == i && b_start == j {
162 a_start -= 1;
164 b_start -= 1;
165 } else {
166 emit = true;
167 }
168 if a_start == 0 || b_start == 0 {
171 emit = true;
172 }
173 idx -= 1;
174 i -= 1;
175 j -= 1;
176 } else {
177 if t.at(i - 1, j) > t.at(i, j - 1) {
179 i -= 1;
180 } else {
181 j -= 1;
182 }
183 if a_start != alen {
184 emit = true;
185 }
186 }
187
188 if emit {
189 let run = (a_end - a_start + 1) as u32;
190 if minmatchlen == 0 || run >= minmatchlen {
191 matches.push(Match {
192 a: (a_start as u32, a_end as u32),
193 b: (b_start as u32, b_end as u32),
194 len: run,
195 });
196 }
197 a_start = alen;
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
209 fn the_documented_example_comes_out_the_same() {
210 let a = b"ohmytext";
211 let b = b"mynewtext";
212 assert_eq!(string(a, b).unwrap(), b"mytext");
213 assert_eq!(len(a, b).unwrap(), 6);
214
215 let got = idx(a, b, 0).unwrap();
216 assert_eq!(got.len, 6);
217 assert_eq!(
218 got.matches,
219 vec![
220 Match {
221 a: (4, 7),
222 b: (5, 8),
223 len: 4
224 },
225 Match {
226 a: (2, 3),
227 b: (0, 1),
228 len: 2
229 },
230 ]
231 );
232 }
233
234 #[test]
235 fn minmatchlen_drops_the_short_runs_and_leaves_the_length_alone() {
236 let got = idx(b"ohmytext", b"mynewtext", 4).unwrap();
237 assert_eq!(got.matches.len(), 1);
238 assert_eq!(got.matches[0].len, 4);
239 assert_eq!(got.len, 6);
241 }
242
243 #[test]
244 fn an_empty_string_shares_nothing_with_anything() {
245 assert_eq!(string(b"", b"abc").unwrap(), b"");
246 assert_eq!(string(b"abc", b"").unwrap(), b"");
247 assert_eq!(string(b"", b"").unwrap(), b"");
248 assert_eq!(len(b"", b"abc").unwrap(), 0);
249 assert!(idx(b"", b"abc", 0).unwrap().matches.is_empty());
250 }
251
252 #[test]
253 fn two_identical_strings_are_one_run() {
254 let got = idx(b"hello", b"hello", 0).unwrap();
255 assert_eq!(got.len, 5);
256 assert_eq!(
257 got.matches,
258 vec![Match {
259 a: (0, 4),
260 b: (0, 4),
261 len: 5
262 }]
263 );
264 assert_eq!(string(b"hello", b"hello").unwrap(), b"hello");
265 }
266
267 #[test]
268 fn two_strings_with_nothing_in_common_share_nothing() {
269 assert_eq!(string(b"abc", b"xyz").unwrap(), b"");
270 assert_eq!(len(b"abc", b"xyz").unwrap(), 0);
271 assert!(idx(b"abc", b"xyz", 0).unwrap().matches.is_empty());
272 }
273
274 #[test]
275 fn a_run_of_one_is_still_a_run() {
276 let got = idx(b"abc", b"axc", 0).unwrap();
277 assert_eq!(got.len, 2);
278 assert_eq!(
279 got.matches,
280 vec![
281 Match {
282 a: (2, 2),
283 b: (2, 2),
284 len: 1
285 },
286 Match {
287 a: (0, 0),
288 b: (0, 0),
289 len: 1
290 },
291 ]
292 );
293 assert_eq!(string(b"abc", b"axc").unwrap(), b"ac");
294 }
295
296 #[test]
297 fn every_run_lands_where_it_says_it_does() {
298 let a = &b"the quick brown fox"[..];
301 let b = &b"a quick red fox jumps"[..];
302 let got = idx(a, b, 0).unwrap();
303 for m in &got.matches {
304 let (s, e) = (m.a.0 as usize, m.a.1 as usize);
305 let (t, u) = (m.b.0 as usize, m.b.1 as usize);
306 assert_eq!(&a[s..=e], &b[t..=u], "{m:?} does not match");
307 assert_eq!(m.len as usize, e - s + 1, "{m:?} has the wrong length");
308 }
309 let mut joined = Vec::new();
312 for m in got.matches.iter().rev() {
313 joined.extend_from_slice(&a[m.a.0 as usize..=m.a.1 as usize]);
314 }
315 assert_eq!(joined, string(a, b).unwrap());
316 }
317
318 #[test]
319 fn a_table_that_will_not_fit_is_an_error_and_not_a_kill() {
320 let big = vec![b'x'; LCS_MAX_CELLS];
321 let e = len(&big, b"y").unwrap_err();
322 assert_eq!(e.code(), Code::Full);
323 assert_eq!(e.message(), NO_MEMORY);
324 }
325}