facett_graphview/analysis/
components.rs1use super::Adjacency;
6
7#[derive(Clone, Debug, PartialEq)]
9pub struct Components {
10 pub of: Vec<usize>,
11 pub count: usize,
12}
13
14impl Components {
15 #[must_use]
17 pub fn groups(&self) -> Vec<Vec<usize>> {
18 let mut g = vec![Vec::new(); self.count];
19 for (i, &c) in self.of.iter().enumerate() {
20 g[c].push(i);
21 }
22 g
23 }
24 #[must_use]
26 pub fn largest(&self) -> usize {
27 self.groups().iter().map(Vec::len).max().unwrap_or(0)
28 }
29}
30
31#[must_use]
34pub fn connected(g: &Adjacency) -> Components {
35 let n = g.n;
36 let mut uf = UnionFind::new(n);
37 for i in 0..n {
38 for &(j, _) in &g.und[i] {
39 uf.union(i, j);
40 }
41 }
42 compactify(&(0..n).map(|i| uf.find(i)).collect::<Vec<_>>())
43}
44
45#[must_use]
49pub fn strongly_connected(g: &Adjacency) -> Components {
50 let n = g.n;
51 let mut index = vec![usize::MAX; n];
52 let mut low = vec![0usize; n];
53 let mut on_stack = vec![false; n];
54 let mut stack: Vec<usize> = Vec::new();
55 let mut comp = vec![usize::MAX; n];
56 let mut next_index = 0usize;
57 let mut next_comp = 0usize;
58
59 for start in 0..n {
61 if index[start] != usize::MAX {
62 continue;
63 }
64 let mut work: Vec<(usize, usize)> = vec![(start, 0)];
65 while let Some(&(v, ci)) = work.last() {
66 if ci == 0 {
67 index[v] = next_index;
68 low[v] = next_index;
69 next_index += 1;
70 stack.push(v);
71 on_stack[v] = true;
72 }
73 if ci < g.out[v].len() {
74 work.last_mut().unwrap().1 += 1;
76 let w = g.out[v][ci].0;
77 if index[w] == usize::MAX {
78 work.push((w, 0));
79 } else if on_stack[w] {
80 low[v] = low[v].min(index[w]);
81 }
82 } else {
83 if low[v] == index[v] {
85 loop {
86 let w = stack.pop().unwrap();
87 on_stack[w] = false;
88 comp[w] = next_comp;
89 if w == v {
90 break;
91 }
92 }
93 next_comp += 1;
94 }
95 work.pop();
96 if let Some(&(p, _)) = work.last() {
97 low[p] = low[p].min(low[v]);
98 }
99 }
100 }
101 }
102 Components { of: comp, count: next_comp }
104}
105
106fn compactify(roots: &[usize]) -> Components {
108 let mut map = std::collections::HashMap::new();
109 let mut of = Vec::with_capacity(roots.len());
110 let mut count = 0;
111 for &r in roots {
112 let id = *map.entry(r).or_insert_with(|| {
113 let id = count;
114 count += 1;
115 id
116 });
117 of.push(id);
118 }
119 Components { of, count }
120}
121
122struct UnionFind {
124 parent: Vec<usize>,
125 size: Vec<usize>,
126}
127
128impl UnionFind {
129 fn new(n: usize) -> Self {
130 Self { parent: (0..n).collect(), size: vec![1; n] }
131 }
132 fn find(&mut self, x: usize) -> usize {
133 let mut r = x;
134 while self.parent[r] != r {
135 r = self.parent[r];
136 }
137 let mut c = x;
139 while self.parent[c] != r {
140 let nxt = self.parent[c];
141 self.parent[c] = r;
142 c = nxt;
143 }
144 r
145 }
146 fn union(&mut self, a: usize, b: usize) {
147 let (ra, rb) = (self.find(a), self.find(b));
148 if ra == rb {
149 return;
150 }
151 let (big, small) = if self.size[ra] >= self.size[rb] { (ra, rb) } else { (rb, ra) };
152 self.parent[small] = big;
153 self.size[big] += self.size[small];
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn connected_finds_two_islands() {
163 let g = Adjacency::from_edges(5, &[(0, 1), (1, 2), (3, 4)]);
165 let c = connected(&g);
166 assert_eq!(c.count, 2);
167 assert_eq!(c.of[0], c.of[2], "0 and 2 are in one component");
168 assert_ne!(c.of[0], c.of[3], "the islands are distinct");
169 assert_eq!(c.largest(), 3);
170 }
171
172 #[test]
173 fn scc_collapses_a_cycle_but_splits_a_chain() {
174 let g = Adjacency::from_edges(5, &[(0, 1), (1, 2), (2, 0), (3, 4)]);
176 let scc = strongly_connected(&g);
177 assert_eq!(scc.of[0], scc.of[1]);
178 assert_eq!(scc.of[1], scc.of[2], "the 3-cycle is one SCC");
179 assert_ne!(scc.of[3], scc.of[4], "an acyclic chain is two singleton SCCs");
180 assert_eq!(scc.count, 3, "one cycle-SCC + two singletons");
181 }
182
183 #[test]
184 fn scc_of_a_dag_is_all_singletons() {
185 let g = Adjacency::from_edges(4, &[(0, 1), (1, 2), (2, 3), (0, 3)]);
186 let scc = strongly_connected(&g);
187 assert_eq!(scc.count, 4, "a DAG has no non-trivial SCC");
188 }
189
190 #[test]
191 fn empty_graph_has_isolated_components() {
192 let g = Adjacency::from_edges(3, &[]);
193 assert_eq!(connected(&g).count, 3);
194 assert_eq!(strongly_connected(&g).count, 3);
195 }
196}