1use std::fmt::Debug;
2
3use super::rope::Rope;
4
5const LEVENSHTEIN_CUTOFF: usize = 8;
6const DELETE_COST: usize = 1;
7const REPLACE_COST: usize = 2;
8const INSERT_COST: usize = 2;
9
10pub fn hirschberg<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
11 target: impl IntoIterator<Item = &'target T>,
12 source: impl IntoIterator<Item = &'src T>,
13) -> Option<OrderedArrayLikeDiffRef<'target, T>> {
14 let target = target.into_iter().collect::<Vec<_>>();
15 let source = source.into_iter().collect::<Vec<_>>();
16
17 match hirschberg_impl(
18 &target,
19 &source,
20 Indices {
21 target_start: 0,
22 target_end: target.len(),
23 source_start: 0,
24 source_end: source.len(),
25 },
26 )
27 .collect::<Vec<_>>()
28 {
29 empty if empty.is_empty() => None,
30 mut nonempty => {
31 nonempty.reverse();
32 Some(OrderedArrayLikeDiffRef(nonempty))
33 }
34 }
35}
36
37pub fn levenshtein<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
38 target: impl IntoIterator<Item = &'target T>,
39 source: impl IntoIterator<Item = &'src T>,
40) -> Option<OrderedArrayLikeDiffRef<'target, T>> {
41 let target = target.into_iter().collect::<Vec<_>>();
42 let source = source.into_iter().collect::<Vec<_>>();
43
44 match levenshtein_impl(
45 &target,
46 &source,
47 Indices {
48 target_start: 0,
49 target_end: target.len(),
50 source_start: 0,
51 source_end: source.len(),
52 },
53 )
54 .collect::<Vec<_>>()
55 {
56 empty if empty.is_empty() => None,
57 nonempty => Some(OrderedArrayLikeDiffRef(nonempty)),
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63pub(crate) enum OrderedArrayLikeChangeRef<'a, T> {
64 Replace(&'a T, usize),
65 Insert(&'a T, usize),
66 Delete(usize, Option<usize>),
68 #[allow(unused)]
69 Swap(usize, usize),
70}
71
72#[derive(Debug, Clone)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74pub(crate) enum OrderedArrayLikeChangeOwned<T> {
75 Replace(T, usize),
76 Insert(T, usize),
77 Delete(usize, Option<usize>),
79 Swap(usize, usize),
80}
81
82impl<'a, T: Clone> From<OrderedArrayLikeChangeRef<'a, T>> for OrderedArrayLikeChangeOwned<T> {
83 fn from(value: OrderedArrayLikeChangeRef<'a, T>) -> Self {
84 match value {
85 OrderedArrayLikeChangeRef::Replace(val, idx) => Self::Replace(val.to_owned(), idx),
86 OrderedArrayLikeChangeRef::Insert(val, idx) => Self::Insert(val.to_owned(), idx),
87 OrderedArrayLikeChangeRef::Delete(idx, range) => Self::Delete(idx, range),
88 OrderedArrayLikeChangeRef::Swap(l, r) => Self::Swap(l, r),
89 }
90 }
91}
92
93#[derive(Clone, Copy, Debug)]
94enum ChangeInternal {
95 NoOp(usize),
96 Replace(usize),
97 Insert(usize),
98 Delete(usize),
99}
100
101impl ChangeInternal {
102 fn cost(&self) -> usize {
103 match self {
104 ChangeInternal::NoOp(c) => *c,
105 ChangeInternal::Replace(c) => *c,
106 ChangeInternal::Insert(c) => *c,
107 ChangeInternal::Delete(c) => *c,
108 }
109 }
110}
111
112impl<T> OrderedArrayLikeChangeOwned<T> {
113 fn apply(self, container: &mut Rope<T>) {
114 match self {
115 OrderedArrayLikeChangeOwned::Replace(val, loc) => container[loc] = val,
116 OrderedArrayLikeChangeOwned::Insert(val, loc) => container.insert(loc, val),
117 OrderedArrayLikeChangeOwned::Delete(loc, None) => {
118 container.remove(loc);
119 }
120 OrderedArrayLikeChangeOwned::Delete(l, Some(r)) => {
121 container.drain(l..=r);
122 }
123 OrderedArrayLikeChangeOwned::Swap(l, r) => container.swap(l, r),
124 }
125 }
126}
127
128#[derive(Debug, Copy, Clone)]
129struct Indices {
130 target_start: usize,
131 target_end: usize,
132 source_start: usize,
133 source_end: usize,
134}
135
136#[derive(Clone, Debug)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct OrderedArrayLikeDiffOwned<T>(Vec<OrderedArrayLikeChangeOwned<T>>);
139
140#[derive(Clone, Debug)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize))]
142pub struct OrderedArrayLikeDiffRef<'src, T>(Vec<OrderedArrayLikeChangeRef<'src, T>>);
143
144impl<'src, T: Clone> From<OrderedArrayLikeDiffRef<'src, T>> for OrderedArrayLikeDiffOwned<T> {
145 fn from(value: OrderedArrayLikeDiffRef<'src, T>) -> Self {
146 Self(value.0.into_iter().map(Into::into).collect())
147 }
148}
149
150#[cfg(unused)]
151fn print_table(table: &Vec<Vec<ChangeInternal>>) {
152 for row in table {
153 println!("{:?}", row)
154 }
155 println!("")
156}
157
158#[cfg(unused)]
159fn print_table_2(table: &[Vec<ChangeInternal>; 2]) {
160 for row in table {
161 println!("{:?}", row)
162 }
163 println!("")
164}
165
166#[inline]
167fn create_full_change_table<T: PartialEq>(
168 target: &[&T],
169 source: &[&T],
170) -> Vec<Vec<ChangeInternal>> {
171 let mut table = vec![vec![ChangeInternal::NoOp(0); source.len() + 1]; target.len() + 1];
172
173 for (i, entry) in table.iter_mut().enumerate().skip(1) {
174 entry[0] = ChangeInternal::Insert(i * INSERT_COST);
175 }
176
177 for j in 0..=source.len() {
178 table[0][j] = ChangeInternal::Delete(j * DELETE_COST)
179 }
180
181 for target_index in 1..=target.len() {
183 let target_entry = target[target_index - 1];
184 for source_index in 1..=source.len() {
185 let source_entry = source[source_index - 1];
186
187 if target_entry == source_entry {
188 table[target_index][source_index] =
189 ChangeInternal::NoOp(table[target_index - 1][source_index - 1].cost());
190 continue;
192 }
193
194 let insert = table[target_index - 1][source_index].cost();
195 let delete = table[target_index][source_index - 1].cost();
196 let replace = table[target_index - 1][source_index - 1].cost();
197 let min = insert.min(delete).min(replace);
198
199 if min == replace {
200 table[target_index][source_index] = ChangeInternal::Replace(min + REPLACE_COST);
201 } else if min == delete {
202 table[target_index][source_index] = ChangeInternal::Delete(min + DELETE_COST);
203 } else {
204 table[target_index][source_index] = ChangeInternal::Insert(min + INSERT_COST);
205 }
206 }
207 }
208 table
209}
210
211#[inline]
212fn create_last_change_row<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
213 target: &[&'target T],
214 target_start: usize,
215 target_end: usize,
216 source: &[&'src T],
217 source_start: usize,
218 source_end: usize,
219) -> Vec<ChangeInternal> {
220 let source_len = source_start.abs_diff(source_end);
221 let rev = target_start > target_end || source_start > source_end;
222
223 debug_assert_eq!(
224 target_start <= target_end,
225 source_start <= source_end,
226 "\ntarget start: {}\ntarget end: {}\nsource start: {}\nsource end: {}",
227 target_start,
228 target_end,
229 source_start,
230 source_end
231 );
232
233 let mut table = std::array::from_fn::<_, 2, _>(|_| {
234 Vec::from_iter((0..(source_len + 1)).map(|i| ChangeInternal::Delete(i * DELETE_COST)))
235 });
236
237 let mut target_forward = target_start..target_end;
238 let mut target_rev = (target_end..target_start).rev();
239
240 #[allow(clippy::type_complexity)]
241 let (target_range, source_range): (
242 &mut dyn Iterator<Item = usize>,
243 Box<dyn Fn() -> Box<dyn Iterator<Item = usize>>>,
244 ) = match rev {
245 true => (
246 &mut target_rev,
247 Box::new(|| Box::new((source_end..source_start).rev())),
248 ),
249 false => (
250 &mut target_forward,
251 Box::new(|| Box::new(source_start..source_end)),
252 ),
253 };
254
255 for target_index in target_range {
256 let target_entry = target[target_index];
257 table[1][0] = ChangeInternal::Insert(table[0][0].cost() + INSERT_COST); for (prev, source_index) in (source_range()).enumerate() {
259 let source_entry = source[source_index];
260 let curr = prev + 1;
261 if target_entry == source_entry {
262 table[1][curr] = ChangeInternal::NoOp(table[0][prev].cost());
263 continue;
265 }
266
267 let insert = table[1][prev].cost();
268 let delete = table[0][curr].cost();
269 let replace = table[0][prev].cost();
270
271 let min = insert.min(delete).min(replace);
272
273 if min == replace {
274 table[1][curr] = ChangeInternal::Replace(min + REPLACE_COST);
275 } else if min == delete {
276 table[1][curr] = ChangeInternal::Delete(min + DELETE_COST);
277 } else {
278 table[1][curr] = ChangeInternal::Insert(min + INSERT_COST);
279 }
280 }
281 table.swap(0, 1);
282 }
283
284 let [ret, ..] = table;
285 ret
286}
287
288fn hirschberg_impl<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
289 target: &[&'target T],
290 source: &[&'src T],
291 Indices {
292 target_start,
293 target_end,
294 source_start,
295 source_end,
296 }: Indices,
297) -> Box<dyn DoubleEndedIterator<Item = OrderedArrayLikeChangeRef<'target, T>> + 'target> {
298 let indices = Indices {
299 target_start,
300 target_end,
301 source_start,
302 source_end,
303 };
304 match (target_start == target_end, source_start == source_end) {
306 (true, true) => return Box::new(std::iter::empty()),
307 (true, false) => {
308 return Box::new(std::iter::once(OrderedArrayLikeChangeRef::Delete(
309 source_start,
310 Some(source_end - 1),
311 )));
312 }
313 (false, true) => {
314 let iter: Box<dyn Iterator<Item = _>> = Box::new(
315 target[target_start..target_end]
316 .iter()
317 .copied()
318 .enumerate()
319 .map(|(i, v)| {
320 let idx = source_end + i;
321 OrderedArrayLikeChangeRef::Insert(v, idx)
322 })
323 .rev(),
324 );
325
326 return Box::new(iter.collect::<Vec<_>>().into_iter());
327 }
328 (false, false)
329 if target_start
330 .abs_diff(target_end)
331 .min(source_start.abs_diff(source_end))
332 <= LEVENSHTEIN_CUTOFF =>
333 {
334 let lev = levenshtein_impl(target, source, indices);
335 return Box::new(lev.rev());
336 }
337 _ => (),
338 }
339
340 let target_split_index = target_start + ((target_end - target_start) / 2);
341 let left = create_last_change_row(
342 target,
343 target_start,
344 target_split_index,
345 source,
346 source_start,
347 source_end,
348 );
349
350 let right = create_last_change_row(
351 target,
352 target_end,
353 target_split_index,
354 source,
355 source_end,
356 source_start,
357 );
358
359 let source_split_index = left
360 .into_iter()
361 .zip(right.into_iter().rev())
362 .map(|(l, r)| l.cost() + r.cost())
363 .enumerate()
364 .min_by_key(|(_, v)| *v)
365 .map(|(idx, _)| source_start + idx)
366 .unwrap();
367
368 let left = hirschberg_impl(
369 target,
370 source,
371 Indices {
372 target_end: target_split_index,
373 source_end: source_split_index,
374 ..indices
375 },
376 );
377
378 let right = hirschberg_impl(
379 target,
380 source,
381 Indices {
382 target_start: target_split_index,
383 source_start: source_split_index,
384 ..indices
385 },
386 );
387
388 Box::new(left.chain(right))
389}
390
391fn levenshtein_impl<'src, 'target: 'src, T: Clone + PartialEq + 'target>(
392 target: &[&'target T],
393 source: &[&'src T],
394 Indices {
395 target_start,
396 target_end,
397 source_start,
398 source_end,
399 }: Indices,
400) -> Box<dyn DoubleEndedIterator<Item = OrderedArrayLikeChangeRef<'target, T>> + 'target> {
401 #[inline]
402 fn changelist_from_change_table<'src, 'target: 'src, T: PartialEq>(
403 table: Vec<Vec<ChangeInternal>>,
404 target: &[&'target T],
405 _source: &[&'src T],
406 Indices {
407 target_start,
408 target_end,
409 source_start,
410 source_end,
411 }: Indices,
412 ) -> Box<dyn DoubleEndedIterator<Item = OrderedArrayLikeChangeRef<'target, T>> + 'target> {
413 let rev = target_start > target_end || source_start > source_end;
414 let mut target_pos = target_start.abs_diff(target_end);
415 let mut source_pos = source_start.abs_diff(source_end);
416 let mut changelist = Vec::with_capacity(
417 table
418 .last()
419 .and_then(|r| r.last())
420 .map(|c| c.cost())
421 .unwrap_or_default(),
422 );
423
424 while target_pos > 0 && source_pos > 0 {
426 match rev {
427 true => {
428 match &(table[target_pos][source_pos]) {
429 ChangeInternal::NoOp(_) => {
430 target_pos -= 1;
431 source_pos -= 1;
432 }
433 ChangeInternal::Replace(_) => {
434 changelist.push(OrderedArrayLikeChangeRef::Replace(
435 target[target_end - target_pos],
436 source_end - source_pos,
437 ));
438 target_pos -= 1;
439 source_pos -= 1;
440 }
441 ChangeInternal::Insert(_) => {
442 changelist.push(OrderedArrayLikeChangeRef::Insert(
443 target[target_end - target_pos],
444 source_end - source_pos,
445 ));
446 target_pos -= 1;
447 }
448 ChangeInternal::Delete(_) => {
449 changelist.push(OrderedArrayLikeChangeRef::Delete(
450 source_end - source_pos,
451 None,
452 ));
453 source_pos -= 1;
454 }
455 }
456 if changelist.len() == table.last().unwrap().last().unwrap().cost() {
457 target_pos = 0;
458 source_pos = 0;
459 break;
460 }
461 }
462 false => {
463 match &(table[target_pos][source_pos]) {
464 ChangeInternal::NoOp(_) => {
465 target_pos -= 1;
466 source_pos -= 1;
467 }
468 ChangeInternal::Replace(_) => {
469 changelist.push(OrderedArrayLikeChangeRef::Replace(
470 target[target_start + target_pos - 1],
471 source_start + source_pos - 1,
472 ));
473 target_pos -= 1;
474 source_pos -= 1;
475 }
476 ChangeInternal::Insert(_) => {
477 changelist.push(OrderedArrayLikeChangeRef::Insert(
478 target[target_start + target_pos - 1],
479 source_start + source_pos,
480 ));
481 target_pos -= 1;
482 }
483 ChangeInternal::Delete(_) => {
484 changelist.push(OrderedArrayLikeChangeRef::Delete(
485 source_start + source_pos - 1,
486 None,
487 ));
488 source_pos -= 1;
489 }
490 }
491 if changelist.len() == table.last().unwrap().last().unwrap().cost() {
492 target_pos = 0;
493 source_pos = 0;
494 break;
495 }
496 }
497 }
498 }
499
500 match rev {
501 true => {
502 while target_pos > 0 {
504 changelist.push(OrderedArrayLikeChangeRef::Insert(
505 target[target_end - target_pos],
506 source_end - source_pos,
507 ));
508 target_pos -= 1;
509 }
510
511 if source_pos > 0 {
513 changelist.push(OrderedArrayLikeChangeRef::Delete(
514 source_start,
515 Some(source_end - source_pos),
516 ));
517 }
518 }
519 false => {
520 while target_pos > 0 {
522 changelist.push(OrderedArrayLikeChangeRef::Insert(
523 target[target_start + target_pos - 1],
524 source_start + source_pos,
525 ));
526 target_pos -= 1;
527 }
528
529 if source_pos > 0 {
531 changelist.push(OrderedArrayLikeChangeRef::Delete(
532 source_start,
533 Some(source_start + source_pos - 1),
534 ));
535 }
536 }
537 }
538
539 Box::new(changelist.into_iter())
540 }
541
542 let table = match (target_start > target_end, source_start > source_end) {
543 (false, false) => create_full_change_table(
544 &target[target_start..target_end],
545 &source[source_start..source_end],
546 ),
547 (true, true) => create_full_change_table(
548 &target[target_end..target_start],
549 &source[source_end..source_start],
550 ),
551 (false, true) => create_full_change_table(
552 &target[target_start..target_end],
553 &source[source_end..source_start],
554 ),
555 (true, false) => create_full_change_table(
556 &target[target_end..target_start],
557 &source[source_start..source_end],
558 ),
559 };
560
561 changelist_from_change_table(
562 table,
563 target,
564 source,
565 Indices {
566 target_start,
567 target_end,
568 source_start,
569 source_end,
570 },
571 )
572}
573
574pub fn apply<T, L>(
575 changes: impl Into<OrderedArrayLikeDiffOwned<T>>,
576 existing: L,
577) -> Box<dyn Iterator<Item = T>>
578where
579 T: Clone + 'static,
580 L: IntoIterator<Item = T> + FromIterator<T>,
581{
582 let mut ret = existing.into_iter().collect::<Rope<_>>();
583
584 for change in changes.into().0 {
585 change.apply(&mut ret);
586 }
587
588 Box::new(ret.into_iter())
589}
590
591#[cfg(feature = "nanoserde")]
592mod nanoserde_impls {
593 use super::*;
594 use nanoserde::{DeBin, SerBin};
595
596 impl<T> OrderedArrayLikeChangeOwned<T> {
597 #[inline]
598 fn nanoserde_discriminant(&self) -> u8 {
599 match self {
600 OrderedArrayLikeChangeOwned::Replace(_, _) => 0,
601 OrderedArrayLikeChangeOwned::Insert(_, _) => 1,
602 OrderedArrayLikeChangeOwned::Delete(_, _) => 2,
603 OrderedArrayLikeChangeOwned::Swap(_, _) => 3,
604 }
605 }
606 }
607
608 impl<T> OrderedArrayLikeChangeRef<'_, T> {
609 #[inline]
610 fn nanoserde_discriminant(&self) -> u8 {
611 match self {
612 OrderedArrayLikeChangeRef::Replace(_, _) => 0,
613 OrderedArrayLikeChangeRef::Insert(_, _) => 1,
614 OrderedArrayLikeChangeRef::Delete(_, _) => 2,
615 OrderedArrayLikeChangeRef::Swap(_, _) => 3,
616 }
617 }
618 }
619
620 impl<T: SerBin> SerBin for OrderedArrayLikeChangeOwned<T> {
621 fn ser_bin(&self, output: &mut Vec<u8>) {
622 match self {
623 OrderedArrayLikeChangeOwned::Replace(val, idx) => {
624 self.nanoserde_discriminant().ser_bin(output);
625 val.ser_bin(output);
626 idx.ser_bin(output);
627 }
628 OrderedArrayLikeChangeOwned::Insert(val, idx) => {
629 self.nanoserde_discriminant().ser_bin(output);
630 val.ser_bin(output);
631 idx.ser_bin(output);
632 }
633 OrderedArrayLikeChangeOwned::Delete(idx, opt_start) => {
634 self.nanoserde_discriminant().ser_bin(output);
635 idx.ser_bin(output);
636 opt_start.ser_bin(output);
637 }
638 OrderedArrayLikeChangeOwned::Swap(l, r) => {
639 self.nanoserde_discriminant().ser_bin(output);
640 l.ser_bin(output);
641 r.ser_bin(output);
642 }
643 }
644 }
645 }
646
647 impl<T: SerBin> SerBin for OrderedArrayLikeChangeRef<'_, T> {
648 fn ser_bin(&self, output: &mut Vec<u8>) {
649 match self {
650 OrderedArrayLikeChangeRef::Replace(val, idx) => {
651 self.nanoserde_discriminant().ser_bin(output);
652 val.ser_bin(output);
653 idx.ser_bin(output);
654 }
655 OrderedArrayLikeChangeRef::Insert(val, idx) => {
656 self.nanoserde_discriminant().ser_bin(output);
657 val.ser_bin(output);
658 idx.ser_bin(output);
659 }
660 OrderedArrayLikeChangeRef::Delete(idx, opt_start) => {
661 self.nanoserde_discriminant().ser_bin(output);
662 idx.ser_bin(output);
663 opt_start.ser_bin(output);
664 }
665 OrderedArrayLikeChangeRef::Swap(l, r) => {
666 self.nanoserde_discriminant().ser_bin(output);
667 l.ser_bin(output);
668 r.ser_bin(output);
669 }
670 }
671 }
672 }
673
674 impl<T: DeBin> DeBin for OrderedArrayLikeChangeOwned<T> {
675 fn de_bin(offset: &mut usize, bytes: &[u8]) -> Result<Self, nanoserde::DeBinErr> {
676 match <u8 as DeBin>::de_bin(offset, bytes)? {
677 0 => {
678 let val = <T as DeBin>::de_bin(offset, bytes)?;
679 let idx = <usize as DeBin>::de_bin(offset, bytes)?;
680 Ok(OrderedArrayLikeChangeOwned::Replace(val, idx))
681 }
682 1 => {
683 let val = <T as DeBin>::de_bin(offset, bytes)?;
684 let idx = <usize as DeBin>::de_bin(offset, bytes)?;
685 Ok(OrderedArrayLikeChangeOwned::Insert(val, idx))
686 }
687 2 => {
688 let idx = <usize as DeBin>::de_bin(offset, bytes)?;
689 let opt_start = <Option<usize> as DeBin>::de_bin(offset, bytes)?;
690 Ok(OrderedArrayLikeChangeOwned::Delete(idx, opt_start))
691 }
692 3 => {
693 let l = <usize as DeBin>::de_bin(offset, bytes)?;
694 let r = <usize as DeBin>::de_bin(offset, bytes)?;
695 Ok(OrderedArrayLikeChangeOwned::Swap(l, r))
696 }
697 _ => Err(nanoserde::DeBinErr {
698 o: *offset - 1,
699 l: 1,
700 s: 1,
701 }),
702 }
703 }
704 }
705
706 impl<T: SerBin> SerBin for OrderedArrayLikeDiffRef<'_, T> {
707 fn ser_bin(&self, output: &mut Vec<u8>) {
708 self.0.ser_bin(output);
709 }
710 }
711
712 impl<T: SerBin> SerBin for OrderedArrayLikeDiffOwned<T> {
713 fn ser_bin(&self, output: &mut Vec<u8>) {
714 self.0.ser_bin(output);
715 }
716 }
717
718 impl<T: DeBin> DeBin for OrderedArrayLikeDiffOwned<T> {
719 fn de_bin(offset: &mut usize, bytes: &[u8]) -> Result<Self, nanoserde::DeBinErr> {
720 let ret = <Vec<_> as DeBin>::de_bin(offset, bytes)?;
721 Ok(Self(ret))
722 }
723 }
724}
725
726#[cfg(test)]
727mod test {
728 use std::collections::LinkedList;
729
730 use crate as structdiff;
731 use crate::collections::ordered_array_like::{
732 apply, OrderedArrayLikeDiffOwned, OrderedArrayLikeDiffRef,
733 };
734 use nanorand::{Rng, WyRand};
735
736 use structdiff::{Difference, StructDiff};
737
738 use super::hirschberg;
739 use super::levenshtein;
740
741 #[test]
742 fn test_string() {
743 let s1 = String::from("tested");
744 let s2 = String::from("testing");
745
746 let s1_vec = s1.chars().collect::<Vec<_>>();
747 let s2_vec = s2.chars().collect::<Vec<_>>();
748 for diff_type in [levenshtein, hirschberg] {
749 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
750 assert_eq!(&s1_vec, &s2_vec);
751 return;
752 };
753
754 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
755 assert_eq!(s1, changed)
756 }
757 }
758
759 #[test]
760 fn test_dna() {
761 let s1 = String::from("ACCCGGTCGTCAATTA");
762 let s2 = String::from("ACCACCGGTTGGTCCAATAA");
763
764 let s1_vec = s1.chars().collect::<Vec<_>>();
765 let s2_vec = s2.chars().collect::<Vec<_>>();
766
767 {
768 let diff_type = hirschberg;
769 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
770 assert_eq!(&s1_vec, &s2_vec);
771 return;
772 };
773
774 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
775 assert_eq!(s1, changed)
776 }
777 }
778
779 #[test]
780 fn test_one_empty_string() {
781 let s1: Vec<char> = "abc".chars().collect();
782 let s2: Vec<char> = "".chars().collect();
783
784 {
785 let diff_type = hirschberg;
786 let Some(changes) = diff_type(&s1, &s2) else {
787 assert_eq!(s1, s2);
788 return;
789 };
790
791 assert_eq!(
792 changes.0.len(),
793 s1.len(),
794 "Should require deletions for all characters in the non-empty string."
795 );
796 }
797 }
798
799 #[test]
800 fn test_empty_strings() {
801 let s1: Vec<char> = "".chars().collect();
802 let s2: Vec<char> = "".chars().collect();
803
804 for diff_type in [levenshtein, hirschberg] {
805 let Some(changes) = diff_type(&s1, &s2) else {
806 assert_eq!(s1, s2);
807 return;
808 };
809
810 assert!(
811 changes.0.is_empty(),
812 "No changes should be needed for two empty strings."
813 );
814 }
815 }
816
817 #[test]
818 fn test_identical_strings() {
819 let s1: Vec<char> = "rust".chars().collect();
820 for diff_type in [levenshtein, hirschberg] {
821 let changes = diff_type(&s1, &s1);
822 assert!(
823 changes.is_none(),
824 "No changes should be needed for identical strings."
825 );
826 }
827 }
828
829 #[test]
830 fn test_random_strings() {
831 let mut rng = WyRand::new();
832 let charset = "abcdefghijklmnopqrstuvwxyz";
833 let charset_len = charset.chars().count();
834
835 for _ in 0..100 {
836 let s1_len = rng.generate_range(0..10); let s2_len = rng.generate_range(0..10);
839
840 let s1: String = (0..s1_len)
841 .map(|_| {
842 charset
843 .chars()
844 .nth(rng.generate_range(0..charset_len))
845 .unwrap()
846 })
847 .collect();
848 let s2: String = (0..s2_len)
849 .map(|_| {
850 charset
851 .chars()
852 .nth(rng.generate_range(0..charset_len))
853 .unwrap()
854 })
855 .collect();
856
857 let s1_vec: Vec<char> = s1.chars().collect();
858 let s2_vec: Vec<char> = s2.chars().collect();
859
860 for diff_type in [levenshtein, hirschberg] {
861 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
862 assert_eq!(&s1_vec, &s2_vec);
863 continue;
864 };
865
866 let changed = apply(changes, s2_vec.clone()).collect::<Vec<char>>();
867 assert_eq!(&s1_vec, &changed);
868 }
869 }
870 }
871
872 #[test]
873 fn test_random_f64_lists() {
874 let mut rng = WyRand::new();
875
876 for _ in 0..100 {
877 let list1_len = rng.generate_range(8..10);
879 let list2_len = rng.generate_range(8..10);
880
881 let list1: Vec<f64> = (0..list1_len).map(|_| rng.generate::<f64>()).collect();
882 let list2: Vec<f64> = (0..list2_len).map(|_| rng.generate::<f64>()).collect();
883
884 for diff_type in [levenshtein, hirschberg] {
885 let Some(changes) = diff_type(&list1, &list2) else {
886 assert_eq!(&list1, &list2);
887 return;
888 };
889
890 let changed = apply(changes, list2.clone()).collect::<Vec<_>>();
891 assert_eq!(list1, changed)
892 }
893 }
894 }
895
896 #[test]
897 fn test_collection_strategies() {
898 #[derive(Debug, PartialEq, Clone, Default, Difference)]
899 #[difference(setters)]
900 struct TestCollection {
901 #[difference(collection_strategy = "ordered_array_like")]
902 test1: Vec<i32>,
903 #[difference(collection_strategy = "ordered_array_like")]
904 test2: LinkedList<i32>,
905 }
906
907 let first = TestCollection {
908 test1: vec![10, 15, 20, 25, 30],
909 test2: vec![10, 15, 17].into_iter().collect(),
910 };
911
912 let second = TestCollection {
913 test1: Vec::default(),
914 test2: vec![10, 15, 17, 19].into_iter().collect(),
915 };
916
917 let diffs = first.diff(&second).to_owned();
918
919 type TestCollectionFields = <TestCollection as StructDiff>::Diff;
920
921 if let TestCollectionFields::test1(OrderedArrayLikeDiffOwned(val)) = &diffs[0] {
922 assert_eq!(val.len(), 1);
923 } else {
924 panic!("Collection strategy failure");
925 }
926
927 if let TestCollectionFields::test2(OrderedArrayLikeDiffOwned(val)) = &diffs[1] {
928 assert_eq!(val.len(), 1);
929 } else {
930 panic!("Collection strategy failure");
931 }
932
933 let diffed = first.apply(diffs);
934
935 assert_eq!(diffed.test1, second.test1);
936 assert_eq!(diffed.test2, second.test2);
937 }
938
939 #[test]
940 fn test_collection_strategies_ref() {
941 #[derive(Debug, PartialEq, Clone, Difference, Default)]
942 #[difference(setters)]
943 struct TestCollection {
944 #[difference(collection_strategy = "ordered_array_like")]
945 test1: Vec<i32>,
946 #[difference(collection_strategy = "ordered_array_like")]
947 test2: LinkedList<i32>,
948 }
949
950 let first = TestCollection {
951 test1: vec![10, 15, 20, 25, 30],
952 test2: vec![10, 15, 17].into_iter().collect(),
953 };
954
955 let second = TestCollection {
956 test1: Vec::default(),
957 test2: vec![10, 15, 17, 19].into_iter().collect(),
958 };
959
960 let diffs = first.diff_ref(&second).to_owned();
961
962 type TestCollectionFields<'target> = <TestCollection as StructDiff>::DiffRef<'target>;
963
964 if let TestCollectionFields::test1(OrderedArrayLikeDiffRef(val)) = &diffs[0] {
965 assert_eq!(val.len(), 1);
966 } else {
967 panic!("Collection strategy failure");
968 }
969
970 if let TestCollectionFields::test2(OrderedArrayLikeDiffRef(val)) = &diffs[1] {
971 assert_eq!(val.len(), 1);
972 } else {
973 panic!("Collection strategy failure");
974 }
975
976 let owned = diffs.into_iter().map(Into::into).collect();
977 let diffed = first.apply(owned);
978
979 assert_eq!(diffed.test1, second.test1);
980 assert_eq!(diffed.test2, second.test2);
981 }
982
983 mod problem_cases {
984 use super::*;
985
986 #[test]
987 fn test_string() {
988 let s1 = String::from("AGTACGCA");
989 let s2 = String::from("TATGC");
990
991 let s1_vec = s1.chars().collect::<Vec<_>>();
992 let s2_vec = s2.chars().collect::<Vec<_>>();
993
994 let Some(changes) = hirschberg(&s1_vec, &s2_vec) else {
995 assert_eq!(&s1_vec, &s2_vec);
996 return;
997 };
998
999 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1000 assert_eq!(s1, changed)
1001 }
1002
1003 #[test]
1004 fn test_string_2() {
1005 let s1 = String::from("testinged");
1006 let s2 = String::from("testeding");
1007
1008 let s1_vec = s1.chars().collect::<Vec<_>>();
1009 let s2_vec = s2.chars().collect::<Vec<_>>();
1010
1011 let Some(changes) = hirschberg(&s1_vec, &s2_vec) else {
1012 assert_eq!(&s1_vec, &s2_vec);
1013 return;
1014 };
1015
1016 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1017 assert_eq!(s1, changed)
1018 }
1019
1020 #[test]
1021 fn test_string_3() {
1022 let s1 = String::from("tested");
1023 let s2 = String::from("testing");
1024
1025 let s1_vec = s1.chars().collect::<Vec<_>>();
1026 let s2_vec = s2.chars().collect::<Vec<_>>();
1027 for diff_type in [levenshtein, hirschberg] {
1028 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
1029 assert_eq!(&s1_vec, &s2_vec);
1030 return;
1031 };
1032
1033 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1034 assert_eq!(s1, changed)
1035 }
1036 }
1037
1038 #[test]
1039 fn test_string_4() {
1040 let s1 = String::from("oanehxv");
1041 let s2 = String::from("yfh");
1042
1043 let s1_vec = s1.chars().collect::<Vec<_>>();
1044 let s2_vec = s2.chars().collect::<Vec<_>>();
1045 for diff_type in [hirschberg, levenshtein] {
1046 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
1047 assert_eq!(&s1_vec, &s2_vec);
1048 return;
1049 };
1050
1051 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1052 assert_eq!(s1, changed)
1053 }
1054 }
1055
1056 #[test]
1057 fn test_string_5() {
1058 let s1 = String::from("lllzrsul");
1059 let s2 = String::from("eoz");
1060
1061 let s1_vec = s1.chars().collect::<Vec<_>>();
1062 let s2_vec = s2.chars().collect::<Vec<_>>();
1063 for diff_type in [levenshtein, hirschberg] {
1064 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
1065 assert_eq!(&s1_vec, &s2_vec);
1066 return;
1067 };
1068
1069 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1070 assert_eq!(s1, changed)
1071 }
1072 }
1073
1074 #[test]
1075 fn test_string_6() {
1076 let s1 = String::from("mc");
1077 let s2 = String::from("rbuzmjw");
1078
1079 let s1_vec = s1.chars().collect::<Vec<_>>();
1080 let s2_vec = s2.chars().collect::<Vec<_>>();
1081 for diff_type in [hirschberg, levenshtein] {
1082 let Some(changes) = diff_type(&s1_vec, &s2_vec) else {
1083 assert_eq!(&s1_vec, &s2_vec);
1084 return;
1085 };
1086
1087 let changed = apply(changes, s2.chars().collect::<Vec<_>>()).collect::<String>();
1088 assert_eq!(s1, changed)
1089 }
1090 }
1091 }
1092}