metrale_grammar/support/
union_find.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// Union-find / disjoint-set — port of `cpp/support/union_find_set.h`.
4//
5// Uses path compression on `find` and union-by-size on `union_sets`,
6// keyed by an arbitrary hashable + ordered element type.
7
8use std::collections::HashMap;
9use std::hash::Hash;
10
11/// A disjoint-set forest over elements of type `T`.
12///
13/// `T` must be `Clone + Eq + Hash` for storage and `Ord` so that
14/// [`UnionFindSet::all_sets`] can produce deterministic output.
15#[derive(Debug, Default, Clone)]
16pub struct UnionFindSet<T> {
17    /// Maps each element to `(parent, subtree_size)`.
18    parent_and_size: HashMap<T, (T, usize)>,
19}
20
21impl<T: Clone + Eq + Hash + Ord> UnionFindSet<T> {
22    /// Create an empty union-find set.
23    pub fn new() -> Self {
24        Self {
25            parent_and_size: HashMap::new(),
26        }
27    }
28
29    /// Add `element` as its own singleton set.
30    ///
31    /// Returns `true` if inserted, `false` if it already existed.
32    pub fn add(&mut self, element: T) -> bool {
33        if self.parent_and_size.contains_key(&element) {
34            return false;
35        }
36        self.parent_and_size.insert(element.clone(), (element, 1));
37        true
38    }
39
40    /// Remove every element from the set.
41    pub fn clear(&mut self) {
42        self.parent_and_size.clear();
43    }
44
45    /// Whether `element` is present.
46    pub fn contains(&self, element: &T) -> bool {
47        self.parent_and_size.contains_key(element)
48    }
49
50    /// Number of stored elements.
51    pub fn len(&self) -> usize {
52        self.parent_and_size.len()
53    }
54
55    /// Whether the set is empty.
56    pub fn is_empty(&self) -> bool {
57        self.parent_and_size.is_empty()
58    }
59
60    /// Find the representative of the set containing `element`,
61    /// applying path compression.
62    ///
63    /// # Panics
64    /// Panics if `element` is not present — mirrors the C++
65    /// `XGRAMMAR_CHECK`.
66    pub fn find(&mut self, element: &T) -> T {
67        let parent = self
68            .parent_and_size
69            .get(element)
70            .expect("Element not found in union-find set.")
71            .0
72            .clone();
73        if &parent == element {
74            return parent;
75        }
76        let root = self.find(&parent);
77        // Path compression: point `element` straight at the root.
78        if let Some(entry) = self.parent_and_size.get_mut(element) {
79            entry.0 = root.clone();
80        }
81        root
82    }
83
84    /// Merge the sets containing `a` and `b` (union by size).
85    ///
86    /// # Panics
87    /// Panics if either element is absent.
88    pub fn union_sets(&mut self, a: &T, b: &T) {
89        assert!(
90            self.parent_and_size.contains_key(a),
91            "Element not found in union-find set."
92        );
93        assert!(
94            self.parent_and_size.contains_key(b),
95            "Element not found in union-find set."
96        );
97        let mut root_a = self.find(a);
98        let mut root_b = self.find(b);
99        if root_a == root_b {
100            return;
101        }
102        let size_a = self.parent_and_size[&root_a].1;
103        let size_b = self.parent_and_size[&root_b].1;
104        // Ensure root_a is the larger set.
105        if size_a < size_b {
106            std::mem::swap(&mut root_a, &mut root_b);
107        }
108        let combined = self.parent_and_size[&root_a].1 + self.parent_and_size[&root_b].1;
109        self.parent_and_size.get_mut(&root_b).unwrap().0 = root_a.clone();
110        self.parent_and_size.get_mut(&root_a).unwrap().1 = combined;
111    }
112
113    /// Collect every disjoint set.
114    ///
115    /// Each inner vector is sorted ascending, and the outer vector is
116    /// sorted by each set's smallest element — deterministic output.
117    pub fn all_sets(&mut self) -> Vec<Vec<T>> {
118        let elements: Vec<T> = self.parent_and_size.keys().cloned().collect();
119        let mut root_to_idx: HashMap<T, usize> = HashMap::new();
120        let mut result: Vec<Vec<T>> = Vec::new();
121        for value in elements {
122            let root = self.find(&value);
123            let idx = *root_to_idx.entry(root).or_insert_with(|| {
124                result.push(Vec::new());
125                result.len() - 1
126            });
127            result[idx].push(value);
128        }
129        for vec in result.iter_mut() {
130            vec.sort();
131        }
132        result.sort_by(|v1, v2| v1[0].cmp(&v2[0]));
133        result
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn add_returns_false_on_duplicate() {
143        let mut uf: UnionFindSet<i32> = UnionFindSet::new();
144        assert!(uf.add(1));
145        assert!(!uf.add(1));
146        assert_eq!(uf.len(), 1);
147    }
148
149    #[test]
150    fn singleton_is_its_own_root() {
151        let mut uf = UnionFindSet::new();
152        uf.add(5);
153        assert_eq!(uf.find(&5), 5);
154    }
155
156    #[test]
157    fn union_merges_sets() {
158        let mut uf = UnionFindSet::new();
159        for i in 1..=4 {
160            uf.add(i);
161        }
162        uf.union_sets(&1, &2);
163        uf.union_sets(&3, &4);
164        assert_eq!(uf.find(&1), uf.find(&2));
165        assert_eq!(uf.find(&3), uf.find(&4));
166        assert_ne!(uf.find(&1), uf.find(&3));
167        uf.union_sets(&2, &3);
168        assert_eq!(uf.find(&1), uf.find(&4));
169    }
170
171    #[test]
172    fn union_idempotent_on_same_set() {
173        let mut uf = UnionFindSet::new();
174        uf.add(1);
175        uf.add(2);
176        uf.union_sets(&1, &2);
177        uf.union_sets(&1, &2);
178        assert_eq!(uf.find(&1), uf.find(&2));
179    }
180
181    #[test]
182    fn all_sets_is_deterministic() {
183        let mut uf = UnionFindSet::new();
184        for i in 1..=6 {
185            uf.add(i);
186        }
187        uf.union_sets(&1, &3);
188        uf.union_sets(&3, &5);
189        uf.union_sets(&2, &4);
190        let sets = uf.all_sets();
191        assert_eq!(sets, vec![vec![1, 3, 5], vec![2, 4], vec![6]]);
192    }
193
194    #[test]
195    fn clear_empties_the_set() {
196        let mut uf = UnionFindSet::new();
197        uf.add(1);
198        uf.add(2);
199        uf.clear();
200        assert!(uf.is_empty());
201        assert!(!uf.contains(&1));
202    }
203
204    #[test]
205    fn contains_reports_membership() {
206        let mut uf = UnionFindSet::new();
207        uf.add(42);
208        assert!(uf.contains(&42));
209        assert!(!uf.contains(&7));
210    }
211
212    #[test]
213    #[should_panic(expected = "Element not found")]
214    fn find_missing_panics() {
215        let mut uf: UnionFindSet<i32> = UnionFindSet::new();
216        uf.find(&99);
217    }
218
219    #[test]
220    fn works_with_string_elements() {
221        let mut uf: UnionFindSet<String> = UnionFindSet::new();
222        uf.add("a".to_string());
223        uf.add("b".to_string());
224        uf.add("c".to_string());
225        uf.union_sets(&"a".to_string(), &"c".to_string());
226        let sets = uf.all_sets();
227        assert_eq!(
228            sets,
229            vec![
230                vec!["a".to_string(), "c".to_string()],
231                vec!["b".to_string()]
232            ]
233        );
234    }
235}