Skip to main content

detcore/edit_distance/
bubbles.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use core::hash::Hash;
10use std::collections::HashMap;
11use std::fmt::Debug;
12use std::ops::Range;
13
14/// Output from generating a permutation between two vecs of items.
15/// It provides the actual permutation generated as well as information about
16/// unmatched items from both the source and target arrays
17#[derive(Debug, PartialEq, Eq)]
18pub struct PermutationResult {
19    pub perm: Vec<usize>,
20    pub unmatched_source_indices: Vec<usize>,
21    pub unmatched_target_indices: Vec<usize>,
22}
23
24/// Result of generating a permutation from the source vec to the target while
25/// maintaining the position of values by marking unmatched values in the target
26/// with `None` instead of just removing them
27#[derive(Debug)]
28pub struct ExpandedPermResult {
29    pub perm: Vec<Option<usize>>,
30    pub unmatched_source_indices: Vec<usize>,
31    pub unmatched_target_indices: Vec<usize>,
32}
33
34impl ExpandedPermResult {
35    /// Get a normal permutation result from the expansion by removing the
36    /// unmatched target values
37    fn collapse(self) -> PermutationResult {
38        let Self {
39            perm,
40            unmatched_source_indices,
41            unmatched_target_indices,
42        } = self;
43
44        PermutationResult {
45            perm: perm.into_iter().flatten().collect(),
46            unmatched_source_indices,
47            unmatched_target_indices,
48        }
49    }
50}
51/// generating a permutation from the source vec to the target while
52/// maintaining the position of values by marking unmatched values in the target
53/// with `None` instead of just removing them
54pub fn generate_expanded_permutation<T>(source: &[T], target: &[T]) -> ExpandedPermResult
55where
56    T: Hash + Eq,
57{
58    let mut index_source: HashMap<&T, Vec<usize>> = Default::default();
59    let mut unmatched_target_indices: Vec<usize> = Default::default();
60    let mut unmatched_source_indices: Vec<usize>;
61
62    source
63        .iter()
64        .enumerate()
65        .for_each(|(i, v)| index_source.entry(v).or_default().push(i));
66
67    index_source.values_mut().for_each(|vec| vec.reverse());
68
69    let result_perm = target
70        .iter()
71        .enumerate()
72        .map(|(i, v)| (i, index_source.get_mut(v).and_then(|tp| tp.pop())))
73        .map(|(i, v)| {
74            v.is_none().then(|| unmatched_target_indices.push(i));
75            v
76        })
77        .collect::<Vec<_>>();
78
79    unmatched_source_indices = index_source
80        .values()
81        .flat_map(|v| v.iter())
82        .cloned()
83        .collect();
84
85    unmatched_source_indices.sort();
86
87    ExpandedPermResult {
88        perm: result_perm,
89        unmatched_source_indices,
90        unmatched_target_indices,
91    }
92}
93
94/// Generate a permutation that when applied to the first argument's elements
95/// produces the second argument. If there are elements in the first that are
96/// not in the second (and vice versa) then those elements will not be present
97/// in the final permutation, and thus the permutation will be shorter than
98/// both input slices. Information about which elements are unmatched in both
99/// the source and target vectors is provided in the result
100pub fn generate_permutation<T>(source: &[T], target: &[T]) -> PermutationResult
101where
102    T: Hash + Eq,
103{
104    generate_expanded_permutation(source, target).collapse()
105}
106
107/// a derangement of the type generated by `generate_permutation` has the property
108/// that if any two sequential elements differ by only 1 (when going from lower index
109/// to higher index), we know those two elements will not be split up on sorting. We
110/// can use this fact to ignore the first and last parts of the range as long as there
111/// are no gaps.
112///
113/// This function will find the range within the permutation where sorting has to be
114/// performed. Outside of this range, no swaps will need to take place when sorting the
115/// permutation
116fn find_sortable_range(perm: &[usize], max_index: usize) -> Range<usize> {
117    let prefix_len = perm
118        .iter()
119        .enumerate()
120        .take_while(|(expected, value)| *expected == **value)
121        .count();
122
123    // If the permutation is already sorted and complete, the prefix length will be
124    // the entire permutation, so shouldn't try to calculate a postfix length (because
125    // that will also be the length of the permutation)
126    let postfix_len = if prefix_len < perm.len() {
127        perm.iter()
128            .rev()
129            .enumerate()
130            .take_while(|(index_from_end, value)| (max_index - *index_from_end) == **value)
131            .count()
132    } else {
133        0
134    };
135
136    Range {
137        start: prefix_len,
138        end: perm.len() - postfix_len,
139    }
140}
141
142pub struct BubbleSortIter<'a, T> {
143    perm: Vec<usize>,
144    source: &'a [T],
145    swap_history: Vec<(usize, usize)>,
146    history_size: usize,
147    history_cursor: usize,
148    unused_value_count: usize,
149}
150
151impl<'a, T> BubbleSortIter<'a, T>
152where
153    T: Hash + Eq,
154{
155    fn new(
156        perm: Vec<usize>,
157        source: &'a [T],
158        swap_history: Vec<(usize, usize)>,
159        unused_value_count: usize,
160    ) -> Self {
161        BubbleSortIter {
162            perm,
163            source,
164            history_size: swap_history.len(),
165            history_cursor: swap_history.len(),
166            swap_history,
167            unused_value_count,
168        }
169    }
170
171    /// Get the edit distance between the source and target slices. This is different from the
172    /// swap distance because any elements that appear in one slice, but not the other will
173    /// not contribute to the swap distance
174    pub fn edit_distance(&self) -> usize {
175        self.history_size + self.unused_value_count
176    }
177
178    /// Number of swaps take to permute the source slice into the target slice when values
179    /// not accounting for items that appear in one slice, but not the other
180    pub fn swap_distance(&self) -> usize {
181        self.history_size
182    }
183
184    pub fn next<'b>(&'b mut self) -> Option<Perm<'a, 'b, T>> {
185        (self.history_cursor < self.history_size).then(|| {
186            let swap = self.swap_history[self.history_cursor];
187            self.perm.swap(swap.0, swap.1);
188            self.history_cursor += 1;
189
190            self.current()
191        })
192    }
193
194    pub fn skip(&mut self, distance: usize) {
195        for _ in 0..distance {
196            let _ = self.next();
197        }
198    }
199
200    pub fn next_back<'b>(&'b mut self) -> Option<Perm<'a, 'b, T>> {
201        (self.history_cursor > 0).then(|| {
202            self.history_cursor -= 1;
203            let swap = self.swap_history[self.history_cursor];
204            self.perm.swap(swap.0, swap.1);
205
206            self.current()
207        })
208    }
209
210    pub fn skip_back(&mut self, distance: usize) {
211        for _ in 0..distance {
212            let _ = self.next_back();
213        }
214    }
215
216    pub fn current<'b>(&'b self) -> Perm<'a, 'b, T> {
217        Perm::new(self)
218    }
219
220    pub fn midpoint<'b>(&'b mut self) -> Perm<'a, 'b, T> {
221        let m = self.history_size / 2;
222        if m > self.history_cursor {
223            self.skip(m - self.history_cursor);
224        } else {
225            self.skip_back(self.history_cursor - m);
226        }
227        self.current()
228    }
229
230    pub fn interpolate<'b>(&'b mut self, interpolate_percentage: u8) -> Perm<'a, 'b, T> {
231        let m = (self.history_size * (interpolate_percentage as usize)) / 100;
232        if m > self.history_cursor {
233            self.skip(m - self.history_cursor);
234        } else {
235            self.skip_back(self.history_cursor - m);
236        }
237        self.current()
238    }
239
240    pub fn history_size(&self) -> usize {
241        self.history_size
242    }
243}
244
245pub struct Perm<'a, 'b, T> {
246    perm: &'b Vec<usize>,
247    source: &'a [T],
248    i: usize,
249}
250
251impl<'a, 'b, T> Perm<'a, 'b, T> {
252    fn new(parent: &'b BubbleSortIter<'a, T>) -> Self {
253        Perm {
254            perm: &parent.perm,
255            source: parent.source,
256            i: 0,
257        }
258    }
259}
260
261impl<'a, 'b, T> Iterator for Perm<'a, 'b, T>
262where
263    T: Hash + Eq,
264{
265    type Item = &'a T;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        (self.i < self.perm.len()).then(|| {
269            let result = &self.source[self.perm[self.i]];
270            self.i += 1;
271            result
272        })
273    }
274}
275
276/// Perform the bubble sort algorithm and return the record of each pairwise swap to perform
277/// the sort
278fn bubble_sort_history<T>(perm: &mut [usize], source: &[T]) -> Vec<(usize, usize)> {
279    let mut history = vec![];
280    let sortable_range = find_sortable_range(perm, source.len() - 1);
281    let index_offset = sortable_range.start;
282    let sortable_slice = &mut perm[sortable_range];
283    let size = sortable_slice.len();
284
285    if size == 0 {
286        return history;
287    }
288
289    for i in 0..(size - 1) {
290        let mut swapped = false;
291        for j in 0..(size - i - 1) {
292            if sortable_slice[j] > sortable_slice[j + 1] {
293                // swapping occurs if elements
294                // are not in the intended order
295                // this is save because both the
296                sortable_slice.swap(j, j + 1);
297
298                history.push((index_offset + j, index_offset + j + 1));
299                swapped = true;
300            }
301        }
302
303        // no swapping means the array is already sorted
304        // so no need for further comparison
305        if !swapped {
306            break;
307        }
308    }
309
310    history
311}
312
313pub fn bubble_sort_distance<T>(source: &[T], target: &[T]) -> usize
314where
315    T: PartialEq + Hash + Eq + Debug + Clone + Copy + 'static,
316{
317    iterable_bubble_sort(source, target).history_size
318}
319
320pub fn iterable_bubble_sort_from_perm<T>(
321    source: &[T],
322    permutation_result: PermutationResult,
323) -> BubbleSortIter<'_, T>
324where
325    T: Hash + Eq,
326{
327    let PermutationResult {
328        mut perm,
329        unmatched_source_indices,
330        unmatched_target_indices,
331    } = permutation_result;
332
333    let history = bubble_sort_history(&mut perm, source);
334
335    let unused_values = unmatched_source_indices.len() + unmatched_target_indices.len();
336
337    BubbleSortIter::new(perm, source, history, unused_values)
338}
339
340pub fn iterable_bubble_sort<'a, T>(source: &'a [T], target: &'a [T]) -> BubbleSortIter<'a, T>
341where
342    T: Hash + Eq,
343{
344    iterable_bubble_sort_from_perm(source, generate_permutation(source, target))
345}
346
347#[cfg(test)]
348mod tests {
349    use std::str::from_utf8;
350
351    use super::*;
352
353    #[test]
354    fn test_get_permutations() {
355        assert_eq!(
356            generate_permutation(b"abaca", b"aaacb"),
357            PermutationResult {
358                perm: vec![0, 2, 4, 3, 1],
359                unmatched_source_indices: vec![],
360                unmatched_target_indices: vec![],
361            }
362        );
363    }
364
365    #[test]
366    fn test_get_permutations_with_mismatch() {
367        assert_eq!(
368            generate_permutation(b"abacaF", b"aaaacb"),
369            PermutationResult {
370                perm: vec![0, 2, 4, 3, 1],
371                unmatched_source_indices: vec![5],
372                unmatched_target_indices: vec![3],
373            }
374        );
375    }
376
377    #[test]
378    fn test_get_permutations_with_lots_of_mismatches() {
379        // Once where mismatches are the only differences
380        assert_eq!(
381            generate_permutation(b"AabcefGHijmNNp", b"abcDefijKLmOpQRST"),
382            PermutationResult {
383                perm: vec![1, 2, 3, 4, 5, 8, 9, 10, 13],
384                unmatched_source_indices: vec![0, 6, 7, 11, 12],
385                unmatched_target_indices: vec![3, 8, 9, 11, 13, 14, 15, 16],
386            }
387        );
388
389        // And again with some shuffling on the source. The unmatched target
390        // indices shouldn't change
391        assert_eq!(
392            generate_permutation(b"fceaiGHNpbAjmN", b"abcDefijKLmOpQRST"),
393            PermutationResult {
394                perm: vec![3, 9, 1, 2, 0, 4, 11, 12, 8],
395                unmatched_source_indices: vec![5, 6, 7, 10, 13],
396                unmatched_target_indices: vec![3, 8, 9, 11, 13, 14, 15, 16],
397            }
398        );
399    }
400    macro_rules! assert_sortable_range_eq {
401        ([$($full:expr),*], [$($expected_sortable_range:expr),*]; where max_index = $max_index:expr) => {
402            let full_perm : &[usize] = &[$($full),*];
403            let range = find_sortable_range(full_perm, $max_index);
404            let actual_sortable_range = &full_perm[range];
405            let expected_sortable_range: &[usize] = &[$($expected_sortable_range),*];
406            assert_eq!(actual_sortable_range, expected_sortable_range);
407        };
408    }
409
410    #[test]
411    fn test_find_sortable_range() {
412        assert_sortable_range_eq!([0, 1, 2, 3, 4, 5, 6], []; where max_index = 6);
413        assert_sortable_range_eq!([0, 1, 2, 4, 3, 5, 6], [4, 3]; where max_index = 6);
414        assert_sortable_range_eq!([0, 1, 2, 4, 3, 5, 6], [4, 3, 5, 6]; where max_index = 7);
415        assert_sortable_range_eq!([1, 2, 4, 3, 5, 6], [1, 2, 4, 3]; where max_index = 6);
416    }
417
418    #[test]
419    fn test_bubble_sort_distance() {
420        assert_eq!(bubble_sort_distance(b"abaca", b"aaacb"), 4);
421    }
422
423    macro_rules! assert_perm_eq {
424        ($expected:expr, $perm:expr) => {
425            assert_eq!(
426                Ok($expected),
427                from_utf8(&$perm.cloned().collect::<Vec<u8>>())
428            );
429        };
430    }
431
432    #[test]
433    fn test_bubble_sort_iterator() {
434        let mut it = iterable_bubble_sort(b"abaca", b"aaacb");
435
436        // walk backwards through permutations
437
438        assert_perm_eq!("abaca", it.current());
439        assert_perm_eq!("aabca", it.next_back().expect("Should be 1 of 4"));
440        assert_perm_eq!("aacba", it.next_back().expect("Should be 2 of 4"));
441        assert_perm_eq!("aacab", it.next_back().expect("Should be 3 of 4"));
442        assert_perm_eq!("aaacb", it.next_back().expect("Should be 4 of 4"));
443
444        // walk forwards through permutations
445
446        assert_perm_eq!("aaacb", it.current());
447        assert_perm_eq!("aacab", it.next().expect("Should be 1 of 4"));
448        assert_perm_eq!("aacba", it.next().expect("Should be 2 of 4"));
449        assert_perm_eq!("aabca", it.next().expect("Should be 3 of 4"));
450        assert_perm_eq!("abaca", it.next().expect("Should be 4 of 4"));
451    }
452
453    #[test]
454    fn test_bubble_sort_distance_mismatch() {
455        assert_eq!(bubble_sort_distance(b"afbaca", b"aadacb"), 4);
456    }
457
458    #[test]
459    fn test_bubble_sort_iterator_mismatch() {
460        let mut it = iterable_bubble_sort(b"afbaca", b"aadacb");
461
462        assert_eq!(it.edit_distance(), it.swap_distance() + 2);
463
464        // walk backwards through permutations
465
466        assert_perm_eq!("abaca", it.current());
467        assert_perm_eq!("aabca", it.next_back().expect("Should be 1 of 4"));
468        assert_perm_eq!("aacba", it.next_back().expect("Should be 2 of 4"));
469        assert_perm_eq!("aacab", it.next_back().expect("Should be 3 of 4"));
470        assert_perm_eq!("aaacb", it.next_back().expect("Should be 4 of 4"));
471
472        // walk forwards through permutations
473
474        assert_perm_eq!("aaacb", it.current());
475        assert_perm_eq!("aacab", it.next().expect("Should be 1 of 4"));
476        assert_perm_eq!("aacba", it.next().expect("Should be 2 of 4"));
477        assert_perm_eq!("aabca", it.next().expect("Should be 3 of 4"));
478        assert_perm_eq!("abaca", it.next().expect("Should be 4 of 4"));
479    }
480
481    #[test]
482    pub fn test_midpoint() {
483        let mut it = iterable_bubble_sort(b"baaaa", b"aaaab");
484
485        assert_perm_eq!("aabaa", it.midpoint());
486    }
487
488    /// This macro implifies checking for the correctness of the `expanded_perm`
489    /// function. You pass in a source and target string, and it checks the
490    /// produced expanded perm against the actual after mapping unmatched
491    /// characters in the target to `_`
492    macro_rules! assert_expanded_target_eq {
493        ($expected:expr, expand_target($src:expr, $target:expr)) => {
494            assert_eq!(
495                Ok($expected),
496                from_utf8(
497                    &generate_expanded_permutation($src, $target)
498                        .perm
499                        .into_iter()
500                        .map(|i_opt| i_opt.map(|i| $src[i]).unwrap_or(b'_'))
501                        .collect::<Vec<u8>>()
502                )
503            );
504        };
505    }
506
507    #[test]
508    pub fn test_expanted_perm() {
509        assert_expanded_target_eq!("a_", expand_target(b"a", b"ab"));
510        assert_expanded_target_eq!("aaacb", expand_target(b"abaca", b"aaacb"));
511        assert_expanded_target_eq!("_aa_ac_b", expand_target(b"aVbaFGcaER", b"PaaAacTb"));
512    }
513}