structdiff/collections/
ordered_array_like.rs

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    /// (start, optional end) range for deletion
67    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    /// (start, optional end) range for deletion
78    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    // create cost table
182    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                // char matches, skip comparisons
191                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); // TODO make this configurable
258        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                // char matches, skip comparisons
264                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    // base cases
305    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        // collect required changes to make source into target
425        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                // target is longer than source, add the missing elements
503                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                // source is longer than target, remove the extra elements
512                if source_pos > 0 {
513                    changelist.push(OrderedArrayLikeChangeRef::Delete(
514                        source_start,
515                        Some(source_end - source_pos),
516                    ));
517                }
518            }
519            false => {
520                // target is longer than source, add the missing elements
521                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                // source is longer than target, remove the extra elements
530                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            // Generate and test 100 pairs of strings
837            let s1_len = rng.generate_range(0..10); // Keep string lengths manageable
838            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            // Generate and test 100 pairs of lists
878            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}