arris_math/disjoint_sets.rs
1//! Disjoint sets over `0..n`: the connected components of a graph the
2//! caller walks, labelled the same way on every platform.
3
4/// Disjoint sets over `0..n`, the lowest member of a set its root, so a
5/// numbering derived from the roots depends on the elements' order and on
6/// nothing else.
7///
8/// ```
9/// use arris_math::DisjointSets;
10///
11/// let mut sets = DisjointSets::new(5);
12/// assert!(sets.union(3, 1));
13/// assert!(sets.union(4, 3));
14/// assert!(!sets.union(1, 4)); // already together
15/// assert_eq!((sets.find(4), sets.find(3), sets.find(2)), (1, 1, 2));
16/// ```
17#[derive(Debug, Clone)]
18pub struct DisjointSets(Vec<usize>);
19
20impl DisjointSets {
21 /// `n` singletons.
22 pub fn new(n: usize) -> Self {
23 DisjointSets((0..n).collect())
24 }
25
26 /// The root of `i`'s set: its lowest member. Halves the path it walks.
27 ///
28 /// Panics if `i` is not below the `n` of [`DisjointSets::new`]: an
29 /// index is the caller's own, never geometry.
30 pub fn find(&mut self, mut i: usize) -> usize {
31 while self.0[i] != i {
32 self.0[i] = self.0[self.0[i]];
33 i = self.0[i];
34 }
35 i
36 }
37
38 /// Joins the sets of `a` and `b`; `false` if they were one already.
39 pub fn union(&mut self, a: usize, b: usize) -> bool {
40 let (a, b) = (self.find(a), self.find(b));
41 if a == b {
42 return false;
43 }
44 self.0[a.max(b)] = a.min(b);
45 true
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use proptest::prelude::*;
53
54 proptest! {
55 /// Against a naive relabelling partition: the same sets, each
56 /// rooted at its least member.
57 #[test]
58 fn matches_a_naive_partition(
59 n in 1usize..40,
60 pairs in prop::collection::vec((0usize..40, 0usize..40), 0..60),
61 ) {
62 let mut sets = DisjointSets::new(n);
63 let mut label: Vec<usize> = (0..n).collect();
64 for (a, b) in pairs {
65 let (a, b) = (a % n, b % n);
66 let merged = sets.union(a, b);
67 let (la, lb) = (label[a], label[b]);
68 prop_assert_eq!(merged, la != lb);
69 let (keep, drop) = (la.min(lb), la.max(lb));
70 for l in &mut label {
71 if *l == drop {
72 *l = keep;
73 }
74 }
75 }
76 for (i, &l) in label.iter().enumerate() {
77 prop_assert_eq!(sets.find(i), l);
78 }
79 }
80 }
81}