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
142/// The states a bubble sort of a permutation passes through, one adjacent swap
143/// at a time: cursor 0 is the permutation itself and cursor
144/// [`Self::swap_distance`] is sorted order.
145///
146/// No swap is recorded. A state is rebuilt from the permutation's inversion
147/// table, which counts the larger values before each value. A bubble sort pass
148/// moves every value that has a larger one before it one place left, past
149/// exactly one larger value, so it lowers each nonzero entry of the table by
150/// one (Knuth, The Art of Computer Programming, Vol. 3, section 5.2.2). The
151/// state after `k` swaps is therefore the permutation whose table is the
152/// original one less the passes those swaps complete, followed by the first
153/// swaps of the next pass. That keeps memory linear in the permutation and
154/// costs `O(n log n)` per move, where a record of every swap grows with the
155/// square of its length: two schedules of a few million events can be
156/// trillions of swaps apart.
157pub struct BubbleSortIter<'a, T> {
158    perm: Vec<usize>,
159    source: &'a [T],
160    /// The part of `perm` the sort reorders; the rest is already in place.
161    sortable: Range<usize>,
162    /// The values of `perm[sortable]` in ascending order: a value's rank is
163    /// its index here.
164    ranked: Vec<usize>,
165    /// For each rank, how many larger values precede it in the unsorted
166    /// permutation.
167    larger_before: Vec<usize>,
168    /// `at_least[p]`: how many values have at least `p` larger values before
169    /// them, so pass `p` of the sort makes `at_least[p]` swaps.
170    at_least: Vec<usize>,
171    swap_count: usize,
172    cursor: usize,
173    unused_value_count: usize,
174}
175
176impl<'a, T> BubbleSortIter<'a, T>
177where
178    T: Hash + Eq,
179{
180    /// The sort of `perm`, positioned at its end (sorted order).
181    fn new(mut perm: Vec<usize>, source: &'a [T], unused_value_count: usize) -> Self {
182        let sortable = find_sortable_range(&perm, source.len().saturating_sub(1));
183        let unsorted = &mut perm[sortable.clone()];
184        let mut ranked = unsorted.to_vec();
185        ranked.sort_unstable();
186        let mut larger_before = vec![0; unsorted.len()];
187        let mut seen = Fenwick::zeros(unsorted.len());
188        for (preceding, value) in unsorted.iter().enumerate() {
189            let rank = ranked
190                .binary_search(value)
191                .expect("every value of the permutation is ranked");
192            larger_before[rank] = preceding - seen.count_before(rank);
193            seen.add(rank);
194        }
195        let deepest = larger_before.iter().copied().max().unwrap_or(0);
196        let mut at_least = vec![0; deepest + 2];
197        for &larger in &larger_before {
198            at_least[larger] += 1;
199        }
200        for p in (0..=deepest).rev() {
201            at_least[p] += at_least[p + 1];
202        }
203        let swap_count = larger_before.iter().sum();
204        unsorted.copy_from_slice(&ranked);
205        BubbleSortIter {
206            perm,
207            source,
208            sortable,
209            ranked,
210            larger_before,
211            at_least,
212            swap_count,
213            cursor: swap_count,
214            unused_value_count,
215        }
216    }
217
218    /// Get the edit distance between the source and target slices. This is different from the
219    /// swap distance because any elements that appear in one slice, but not the other will
220    /// not contribute to the swap distance
221    pub fn edit_distance(&self) -> usize {
222        self.swap_count + self.unused_value_count
223    }
224
225    /// Number of swaps take to permute the source slice into the target slice when values
226    /// not accounting for items that appear in one slice, but not the other
227    pub fn swap_distance(&self) -> usize {
228        self.swap_count
229    }
230
231    /// The state one swap further toward sorted order. Each step rebuilds
232    /// the state, in `O(n log n)`, as every move does.
233    pub fn next<'b>(&'b mut self) -> Option<Perm<'a, 'b, T>> {
234        (self.cursor < self.swap_count).then(|| {
235            self.seek(self.cursor + 1);
236            self.current()
237        })
238    }
239
240    pub fn skip(&mut self, distance: usize) {
241        self.seek(self.cursor.saturating_add(distance));
242    }
243
244    /// The state one swap back toward the permutation, in `O(n log n)`.
245    pub fn next_back<'b>(&'b mut self) -> Option<Perm<'a, 'b, T>> {
246        (self.cursor > 0).then(|| {
247            self.seek(self.cursor - 1);
248            self.current()
249        })
250    }
251
252    pub fn skip_back(&mut self, distance: usize) {
253        self.seek(self.cursor.saturating_sub(distance));
254    }
255
256    pub fn current<'b>(&'b self) -> Perm<'a, 'b, T> {
257        Perm::new(self)
258    }
259
260    pub fn midpoint<'b>(&'b mut self) -> Perm<'a, 'b, T> {
261        self.seek(self.swap_count / 2);
262        self.current()
263    }
264
265    pub fn interpolate<'b>(&'b mut self, interpolate_percentage: u8) -> Perm<'a, 'b, T> {
266        self.seek((self.swap_count * (interpolate_percentage as usize)) / 100);
267        self.current()
268    }
269
270    pub fn history_size(&self) -> usize {
271        self.swap_count
272    }
273
274    /// Move to the state after the sort's first `swaps` swaps, or to its end
275    /// if it makes fewer.
276    fn seek(&mut self, swaps: usize) {
277        let swaps = swaps.min(self.swap_count);
278        if swaps == self.cursor {
279            return;
280        }
281        let (passes, partial) = self.passes_within(swaps);
282        let state = &mut self.perm[self.sortable.clone()];
283        // Values are placed in ascending order. After `passes` passes, each
284        // still has `larger` larger values before it, and those are exactly
285        // the free slots before its own, as every smaller value is placed.
286        let mut free = Fenwick::ones(state.len());
287        for (rank, &value) in self.ranked.iter().enumerate() {
288            let larger = self.larger_before[rank].saturating_sub(passes);
289            let slot = free.nth(larger);
290            free.remove(slot);
291            state[slot] = value;
292        }
293        let mut left = partial;
294        let mut j = 0;
295        while left > 0 {
296            if state[j] > state[j + 1] {
297                state.swap(j, j + 1);
298                left -= 1;
299            }
300            j += 1;
301        }
302        self.cursor = swaps;
303    }
304
305    /// How many passes the sort's first `swaps` swaps complete, and how many
306    /// swaps of the next pass they include. Pass `p + 1` makes one swap for
307    /// each value with more than `p` larger values before it.
308    fn passes_within(&self, swaps: usize) -> (usize, usize) {
309        let deepest = self.at_least.len() - 2;
310        let (mut passes, mut done) = (0, 0);
311        while passes < deepest && done + self.at_least[passes + 1] <= swaps {
312            done += self.at_least[passes + 1];
313            passes += 1;
314        }
315        (passes, swaps - done)
316    }
317}
318
319pub struct Perm<'a, 'b, T> {
320    perm: &'b Vec<usize>,
321    source: &'a [T],
322    i: usize,
323}
324
325impl<'a, 'b, T> Perm<'a, 'b, T> {
326    fn new(parent: &'b BubbleSortIter<'a, T>) -> Self {
327        Perm {
328            perm: &parent.perm,
329            source: parent.source,
330            i: 0,
331        }
332    }
333}
334
335impl<'a, 'b, T> Iterator for Perm<'a, 'b, T>
336where
337    T: Hash + Eq,
338{
339    type Item = &'a T;
340
341    fn next(&mut self) -> Option<Self::Item> {
342        (self.i < self.perm.len()).then(|| {
343            let result = &self.source[self.perm[self.i]];
344            self.i += 1;
345            result
346        })
347    }
348}
349
350/// A binary indexed (Fenwick) tree of counts over the slots `0..len`: it
351/// counts the marked slots before a slot, and finds the slot of the `n`th
352/// marked one, each in `O(log len)`.
353struct Fenwick(Vec<usize>);
354
355/// The lowest set bit of a tree node's index, nonzero: the size of the range
356/// the node sums.
357fn lowest_bit(node: usize) -> usize {
358    1 << node.trailing_zeros()
359}
360
361impl Fenwick {
362    /// No slot marked.
363    fn zeros(len: usize) -> Self {
364        Fenwick(vec![0; len + 1])
365    }
366
367    /// Every slot marked.
368    fn ones(len: usize) -> Self {
369        let mut tree = vec![1; len + 1];
370        tree[0] = 0;
371        for node in 1..=len {
372            let parent = node + lowest_bit(node);
373            if parent <= len {
374                tree[parent] += tree[node];
375            }
376        }
377        Fenwick(tree)
378    }
379
380    fn add(&mut self, slot: usize) {
381        let mut node = slot + 1;
382        while node < self.0.len() {
383            self.0[node] += 1;
384            node += lowest_bit(node);
385        }
386    }
387
388    fn remove(&mut self, slot: usize) {
389        let mut node = slot + 1;
390        while node < self.0.len() {
391            self.0[node] -= 1;
392            node += lowest_bit(node);
393        }
394    }
395
396    /// How many marked slots precede `slot`.
397    fn count_before(&self, slot: usize) -> usize {
398        let mut node = slot;
399        let mut count = 0;
400        while node > 0 {
401            count += self.0[node];
402            node -= lowest_bit(node);
403        }
404        count
405    }
406
407    /// The marked slot with exactly `n` marked slots before it.
408    fn nth(&self, n: usize) -> usize {
409        let mut slot = 0;
410        let mut rest = n;
411        let mut step = (self.0.len() - 1).checked_ilog2().map_or(0, |log| 1 << log);
412        while step > 0 {
413            if slot + step < self.0.len() && self.0[slot + step] <= rest {
414                slot += step;
415                rest -= self.0[slot];
416            }
417            step >>= 1;
418        }
419        slot
420    }
421}
422
423pub fn bubble_sort_distance<T>(source: &[T], target: &[T]) -> usize
424where
425    T: PartialEq + Hash + Eq + Debug + Clone + Copy + 'static,
426{
427    iterable_bubble_sort(source, target).swap_count
428}
429
430pub fn iterable_bubble_sort_from_perm<T>(
431    source: &[T],
432    permutation_result: PermutationResult,
433) -> BubbleSortIter<'_, T>
434where
435    T: Hash + Eq,
436{
437    let PermutationResult {
438        perm,
439        unmatched_source_indices,
440        unmatched_target_indices,
441    } = permutation_result;
442
443    let unused_values = unmatched_source_indices.len() + unmatched_target_indices.len();
444
445    BubbleSortIter::new(perm, source, unused_values)
446}
447
448pub fn iterable_bubble_sort<'a, T>(source: &'a [T], target: &'a [T]) -> BubbleSortIter<'a, T>
449where
450    T: Hash + Eq,
451{
452    iterable_bubble_sort_from_perm(source, generate_permutation(source, target))
453}
454
455#[cfg(test)]
456mod tests {
457    use std::str::from_utf8;
458
459    use super::*;
460
461    #[test]
462    fn test_get_permutations() {
463        assert_eq!(
464            generate_permutation(b"abaca", b"aaacb"),
465            PermutationResult {
466                perm: vec![0, 2, 4, 3, 1],
467                unmatched_source_indices: vec![],
468                unmatched_target_indices: vec![],
469            }
470        );
471    }
472
473    #[test]
474    fn test_get_permutations_with_mismatch() {
475        assert_eq!(
476            generate_permutation(b"abacaF", b"aaaacb"),
477            PermutationResult {
478                perm: vec![0, 2, 4, 3, 1],
479                unmatched_source_indices: vec![5],
480                unmatched_target_indices: vec![3],
481            }
482        );
483    }
484
485    #[test]
486    fn test_get_permutations_with_lots_of_mismatches() {
487        // Once where mismatches are the only differences
488        assert_eq!(
489            generate_permutation(b"AabcefGHijmNNp", b"abcDefijKLmOpQRST"),
490            PermutationResult {
491                perm: vec![1, 2, 3, 4, 5, 8, 9, 10, 13],
492                unmatched_source_indices: vec![0, 6, 7, 11, 12],
493                unmatched_target_indices: vec![3, 8, 9, 11, 13, 14, 15, 16],
494            }
495        );
496
497        // And again with some shuffling on the source. The unmatched target
498        // indices shouldn't change
499        assert_eq!(
500            generate_permutation(b"fceaiGHNpbAjmN", b"abcDefijKLmOpQRST"),
501            PermutationResult {
502                perm: vec![3, 9, 1, 2, 0, 4, 11, 12, 8],
503                unmatched_source_indices: vec![5, 6, 7, 10, 13],
504                unmatched_target_indices: vec![3, 8, 9, 11, 13, 14, 15, 16],
505            }
506        );
507    }
508    macro_rules! assert_sortable_range_eq {
509        ([$($full:expr),*], [$($expected_sortable_range:expr),*]; where max_index = $max_index:expr) => {
510            let full_perm : &[usize] = &[$($full),*];
511            let range = find_sortable_range(full_perm, $max_index);
512            let actual_sortable_range = &full_perm[range];
513            let expected_sortable_range: &[usize] = &[$($expected_sortable_range),*];
514            assert_eq!(actual_sortable_range, expected_sortable_range);
515        };
516    }
517
518    #[test]
519    fn test_find_sortable_range() {
520        assert_sortable_range_eq!([0, 1, 2, 3, 4, 5, 6], []; where max_index = 6);
521        assert_sortable_range_eq!([0, 1, 2, 4, 3, 5, 6], [4, 3]; where max_index = 6);
522        assert_sortable_range_eq!([0, 1, 2, 4, 3, 5, 6], [4, 3, 5, 6]; where max_index = 7);
523        assert_sortable_range_eq!([1, 2, 4, 3, 5, 6], [1, 2, 4, 3]; where max_index = 6);
524    }
525
526    #[test]
527    fn test_bubble_sort_distance() {
528        assert_eq!(bubble_sort_distance(b"abaca", b"aaacb"), 4);
529    }
530
531    macro_rules! assert_perm_eq {
532        ($expected:expr, $perm:expr) => {
533            assert_eq!(
534                Ok($expected),
535                from_utf8(&$perm.cloned().collect::<Vec<u8>>())
536            );
537        };
538    }
539
540    #[test]
541    fn test_bubble_sort_iterator() {
542        let mut it = iterable_bubble_sort(b"abaca", b"aaacb");
543
544        // walk backwards through permutations
545
546        assert_perm_eq!("abaca", it.current());
547        assert_perm_eq!("aabca", it.next_back().expect("Should be 1 of 4"));
548        assert_perm_eq!("aacba", it.next_back().expect("Should be 2 of 4"));
549        assert_perm_eq!("aacab", it.next_back().expect("Should be 3 of 4"));
550        assert_perm_eq!("aaacb", it.next_back().expect("Should be 4 of 4"));
551
552        // walk forwards through permutations
553
554        assert_perm_eq!("aaacb", it.current());
555        assert_perm_eq!("aacab", it.next().expect("Should be 1 of 4"));
556        assert_perm_eq!("aacba", it.next().expect("Should be 2 of 4"));
557        assert_perm_eq!("aabca", it.next().expect("Should be 3 of 4"));
558        assert_perm_eq!("abaca", it.next().expect("Should be 4 of 4"));
559    }
560
561    #[test]
562    fn test_bubble_sort_distance_mismatch() {
563        assert_eq!(bubble_sort_distance(b"afbaca", b"aadacb"), 4);
564    }
565
566    #[test]
567    fn test_bubble_sort_iterator_mismatch() {
568        let mut it = iterable_bubble_sort(b"afbaca", b"aadacb");
569
570        assert_eq!(it.edit_distance(), it.swap_distance() + 2);
571
572        // walk backwards through permutations
573
574        assert_perm_eq!("abaca", it.current());
575        assert_perm_eq!("aabca", it.next_back().expect("Should be 1 of 4"));
576        assert_perm_eq!("aacba", it.next_back().expect("Should be 2 of 4"));
577        assert_perm_eq!("aacab", it.next_back().expect("Should be 3 of 4"));
578        assert_perm_eq!("aaacb", it.next_back().expect("Should be 4 of 4"));
579
580        // walk forwards through permutations
581
582        assert_perm_eq!("aaacb", it.current());
583        assert_perm_eq!("aacab", it.next().expect("Should be 1 of 4"));
584        assert_perm_eq!("aacba", it.next().expect("Should be 2 of 4"));
585        assert_perm_eq!("aabca", it.next().expect("Should be 3 of 4"));
586        assert_perm_eq!("abaca", it.next().expect("Should be 4 of 4"));
587    }
588
589    #[test]
590    pub fn test_midpoint() {
591        let mut it = iterable_bubble_sort(b"baaaa", b"aaaab");
592
593        assert_perm_eq!("aabaa", it.midpoint());
594    }
595
596    /// Every state of the bubble sort of `perm`, from `perm` itself to sorted
597    /// order, as the sort was first written: swap by swap, keeping each one.
598    fn recorded_states(perm: &[usize]) -> Vec<Vec<usize>> {
599        let mut perm = perm.to_vec();
600        let mut states = vec![perm.clone()];
601        for pass in 0..perm.len().saturating_sub(1) {
602            let mut swapped = false;
603            for j in 0..perm.len() - pass - 1 {
604                if perm[j] > perm[j + 1] {
605                    perm.swap(j, j + 1);
606                    states.push(perm.clone());
607                    swapped = true;
608                }
609            }
610            if !swapped {
611                break;
612            }
613        }
614        states
615    }
616
617    /// Every order of `values`.
618    fn orders(values: &[usize]) -> Vec<Vec<usize>> {
619        if values.is_empty() {
620            return vec![vec![]];
621        }
622        (0..values.len())
623            .flat_map(|first| {
624                let mut rest = values.to_vec();
625                let head = rest.remove(first);
626                orders(&rest).into_iter().map(move |mut order| {
627                    order.insert(0, head);
628                    order
629                })
630            })
631            .collect()
632    }
633
634    /// The sort of `perm` (over `source`) is at the recorded sort's state
635    /// after as many swaps at every cursor: walking back from sorted order,
636    /// forward again, and seeking each percentage and the midpoint.
637    fn assert_states_are_the_recorded_sorts(source: &[()], perm: Vec<usize>) {
638        let states = recorded_states(&perm);
639        let swaps = states.len() - 1;
640        let mut sort = iterable_bubble_sort_from_perm(
641            source,
642            PermutationResult {
643                perm: perm.clone(),
644                unmatched_source_indices: vec![],
645                unmatched_target_indices: vec![],
646            },
647        );
648        assert_eq!(sort.swap_distance(), swaps, "{perm:?}");
649        assert_eq!(sort.perm, states[swaps], "{perm:?}");
650        for state in states.iter().rev().skip(1) {
651            sort.next_back().expect("a swap is left to undo");
652            assert_eq!(&sort.perm, state, "{perm:?}");
653        }
654        assert!(sort.next_back().is_none(), "{perm:?}");
655        for state in &states[1..] {
656            sort.next().expect("a swap is left");
657            assert_eq!(&sort.perm, state, "{perm:?}");
658        }
659        assert!(sort.next().is_none(), "{perm:?}");
660        for percentage in 0..=100 {
661            sort.interpolate(percentage);
662            let expected = &states[swaps * usize::from(percentage) / 100];
663            assert_eq!(&sort.perm, expected, "{perm:?} at {percentage}%");
664        }
665        sort.midpoint();
666        assert_eq!(sort.perm, states[swaps / 2], "{perm:?}");
667    }
668
669    /// The sort rebuilds each state from the permutation's inversion table
670    /// instead of replaying recorded swaps, and every state is the one the
671    /// recorded sort reaches: for every order of up to seven values, and for
672    /// longer shuffles that take many passes. The values are dense, as when
673    /// every event is matched, so that a prefix and suffix already in place
674    /// are left out of the sort; and sparse, as the matched source indices
675    /// are when some events are unmatched.
676    #[test]
677    fn every_state_is_the_recorded_sorts() {
678        for len in 0..=7 {
679            let dense: Vec<usize> = (0..len).collect();
680            let sparse: Vec<usize> = (0..len).map(|value| 2 * value + 1).collect();
681            for (values, source_len) in [(dense, len), (sparse, 2 * len + 2)] {
682                let source = vec![(); source_len];
683                for order in orders(&values) {
684                    assert_states_are_the_recorded_sorts(&source, order);
685                }
686            }
687        }
688        let mut state: u64 = 0x9e37_79b9_7f4a_7c15;
689        let mut random = move |bound: usize| {
690            state ^= state << 13;
691            state ^= state >> 7;
692            state ^= state << 17;
693            state as usize % bound
694        };
695        for len in [20, 41, 64] {
696            for _ in 0..10 {
697                let mut order: Vec<usize> = (0..len).collect();
698                for last in (1..len).rev() {
699                    order.swap(last, random(last + 1));
700                }
701                assert_states_are_the_recorded_sorts(&vec![(); len], order);
702            }
703        }
704    }
705
706    /// The marker under which [`a_long_reversal_sorts_in_bounded_memory`]
707    /// starts this test binary again to run its body alone.
708    const BOUNDED_MEMORY_CHILD_ENV: &str = "HERMIT_BUBBLE_SORT_BOUNDED_MEMORY_CHILD";
709
710    /// Reversing 200,000 events takes 19,999,900,000 adjacent swaps, which a
711    /// record of every swap held in 320 GB. The sort runs alone in a process
712    /// of its own, which caps its data at 1 GiB so that a regression fails
713    /// here instead of exhausting the host, and must peak below 256 MiB
714    /// resident.
715    #[test]
716    fn a_long_reversal_sorts_in_bounded_memory() {
717        // The harness names a test by its path below the crate.
718        let (_, module) = module_path!().split_once("::").unwrap();
719        let name = format!("{module}::a_long_reversal_sorts_in_bounded_memory");
720        if std::env::var(BOUNDED_MEMORY_CHILD_ENV).as_deref() == Ok(name.as_str()) {
721            sort_a_long_reversal_under_a_data_cap();
722            return;
723        }
724        let output = std::process::Command::new(std::env::current_exe().unwrap())
725            .args([name.as_str(), "--exact", "--nocapture", "--test-threads=1"])
726            .env(BOUNDED_MEMORY_CHILD_ENV, &name)
727            .output()
728            .unwrap();
729        let stdout = String::from_utf8_lossy(&output.stdout);
730        let rendered = format!(
731            "status {}\nstdout:\n{stdout}\nstderr:\n{}",
732            output.status,
733            String::from_utf8_lossy(&output.stderr)
734        );
735        assert!(output.status.success(), "{rendered}");
736        // The harness prints the test's name and its output on one line.
737        let peak_kib: u64 = stdout
738            .split("peak resident KiB: ")
739            .nth(1)
740            .and_then(|rest| rest.split_whitespace().next())
741            .unwrap_or_else(|| panic!("the sort did not run:\n{rendered}"))
742            .parse()
743            .unwrap();
744        assert!(
745            peak_kib < 256 << 10,
746            "peaked at {peak_kib} KiB:\n{rendered}"
747        );
748    }
749
750    /// The body of [`a_long_reversal_sorts_in_bounded_memory`]'s own process.
751    fn sort_a_long_reversal_under_a_data_cap() {
752        use nix::sys::resource::Resource;
753        use nix::sys::resource::getrlimit;
754        use nix::sys::resource::setrlimit;
755        // A regression ends in a failed allocation and an abort: dump no
756        // core. A core limit of 1, not 0, also stops a dump to a core_pattern
757        // pipe, which ignores the limit otherwise (fs/coredump.c); a hard
758        // limit already at 0 cannot be raised to it.
759        let (_, core_hard) = getrlimit(Resource::RLIMIT_CORE).unwrap();
760        setrlimit(Resource::RLIMIT_CORE, core_hard.min(1), core_hard.min(1)).unwrap();
761        let (_, data_hard) = getrlimit(Resource::RLIMIT_DATA).unwrap();
762        let data_cap = data_hard.min(1 << 30);
763        setrlimit(Resource::RLIMIT_DATA, data_cap, data_cap).unwrap();
764        let len = 200_000;
765        let source: Vec<usize> = (0..len).collect();
766        let target: Vec<usize> = source.iter().rev().copied().collect();
767        let mut sort = iterable_bubble_sort(&source, &target);
768        let swaps = len * (len - 1) / 2;
769        assert_eq!(sort.swap_distance(), swaps);
770        // Each swap removes one inversion, so the midpoint, half the swaps
771        // from the reversal, has the other half left to sort.
772        let midpoint: Vec<usize> = sort.midpoint().copied().collect();
773        assert_eq!(inversions(&midpoint), swaps - swaps / 2);
774        let status = std::fs::read_to_string("/proc/self/status").unwrap();
775        let peak = status
776            .lines()
777            .find_map(|line| line.strip_prefix("VmHWM:"))
778            .and_then(|kib| kib.trim().strip_suffix("kB"))
779            .expect("/proc/self/status reports VmHWM");
780        println!("peak resident KiB: {}", peak.trim());
781    }
782
783    /// How many pairs of `values`, distinct and each below `values.len()`,
784    /// are out of order.
785    fn inversions(values: &[usize]) -> usize {
786        let mut seen = Fenwick::zeros(values.len());
787        values
788            .iter()
789            .enumerate()
790            .map(|(preceding, &value)| {
791                let larger = preceding - seen.count_before(value);
792                seen.add(value);
793                larger
794            })
795            .sum()
796    }
797
798    /// This macro implifies checking for the correctness of the `expanded_perm`
799    /// function. You pass in a source and target string, and it checks the
800    /// produced expanded perm against the actual after mapping unmatched
801    /// characters in the target to `_`
802    macro_rules! assert_expanded_target_eq {
803        ($expected:expr, expand_target($src:expr, $target:expr)) => {
804            assert_eq!(
805                Ok($expected),
806                from_utf8(
807                    &generate_expanded_permutation($src, $target)
808                        .perm
809                        .into_iter()
810                        .map(|i_opt| i_opt.map(|i| $src[i]).unwrap_or(b'_'))
811                        .collect::<Vec<u8>>()
812                )
813            );
814        };
815    }
816
817    #[test]
818    pub fn test_expanted_perm() {
819        assert_expanded_target_eq!("a_", expand_target(b"a", b"ab"));
820        assert_expanded_target_eq!("aaacb", expand_target(b"abaca", b"aaacb"));
821        assert_expanded_target_eq!("_aa_ac_b", expand_target(b"aVbaFGcaER", b"PaaAacTb"));
822    }
823}