1use arrow::record_batch::RecordBatch;
2use arrow::array::{StringArray, UInt32Array};
3use arrow::datatypes::{DataType, Field, Schema};
4use std::sync::Arc;
5use std::collections::HashMap;
6use crate::algorithms::{GraphAlgorithm, AlgorithmParams};
7use crate::graph::ArrowGraph;
8use crate::error::{GraphError, Result};
9
10#[derive(Debug)]
12struct UnionFind {
13 parent: HashMap<String, String>,
14 rank: HashMap<String, usize>,
15 component_sizes: HashMap<String, usize>,
16}
17
18impl UnionFind {
19 fn new() -> Self {
20 UnionFind {
21 parent: HashMap::new(),
22 rank: HashMap::new(),
23 component_sizes: HashMap::new(),
24 }
25 }
26
27 fn make_set(&mut self, node: String) {
28 if !self.parent.contains_key(&node) {
29 self.parent.insert(node.clone(), node.clone());
30 self.rank.insert(node.clone(), 0);
31 self.component_sizes.insert(node.clone(), 1);
32 }
33 }
34
35 fn find(&mut self, node: &str) -> Option<String> {
36 if !self.parent.contains_key(node) {
37 return None;
38 }
39
40 let parent = self.parent.get(node).unwrap().clone();
42 if parent != node {
43 let root = self.find(&parent)?;
44 self.parent.insert(node.to_string(), root.clone());
45 Some(root)
46 } else {
47 Some(parent)
48 }
49 }
50
51 fn union(&mut self, node1: &str, node2: &str) -> bool {
52 let root1 = match self.find(node1) {
53 Some(r) => r,
54 None => return false,
55 };
56
57 let root2 = match self.find(node2) {
58 Some(r) => r,
59 None => return false,
60 };
61
62 if root1 == root2 {
63 return false; }
65
66 let rank1 = *self.rank.get(&root1).unwrap_or(&0);
68 let rank2 = *self.rank.get(&root2).unwrap_or(&0);
69
70 let (new_root, old_root) = if rank1 > rank2 {
71 (root1, root2)
72 } else if rank1 < rank2 {
73 (root2, root1)
74 } else {
75 self.rank.insert(root1.clone(), rank1 + 1);
77 (root1, root2)
78 };
79
80 self.parent.insert(old_root.clone(), new_root.clone());
82
83 let size1 = *self.component_sizes.get(&new_root).unwrap_or(&0);
85 let size2 = *self.component_sizes.get(&old_root).unwrap_or(&0);
86 self.component_sizes.insert(new_root, size1 + size2);
87 self.component_sizes.remove(&old_root);
88
89 true
90 }
91
92 fn get_components(&mut self) -> HashMap<String, Vec<String>> {
93 let mut components: HashMap<String, Vec<String>> = HashMap::new();
94
95 let nodes: Vec<String> = self.parent.keys().cloned().collect();
97 for node in nodes {
98 if let Some(root) = self.find(&node) {
99 components.entry(root).or_default().push(node);
100 }
101 }
102
103 components
104 }
105
106 fn component_count(&mut self) -> usize {
107 self.get_components().len()
108 }
109}
110
111pub struct WeaklyConnectedComponents;
112
113impl WeaklyConnectedComponents {
114 fn compute_components(&self, graph: &ArrowGraph) -> Result<HashMap<String, u32>> {
116 let mut uf = UnionFind::new();
117
118 for node_id in graph.node_ids() {
120 uf.make_set(node_id.clone());
121 }
122
123 for node_id in graph.node_ids() {
125 if let Some(neighbors) = graph.neighbors(node_id) {
126 for neighbor in neighbors {
127 uf.union(node_id, neighbor);
128 }
129 }
130 }
131
132 let components = uf.get_components();
134 let mut node_to_component: HashMap<String, u32> = HashMap::new();
135
136 for (component_id, (_root, nodes)) in components.into_iter().enumerate() {
137 for node in nodes {
138 node_to_component.insert(node, component_id as u32);
139 }
140 }
141
142 Ok(node_to_component)
143 }
144}
145
146impl GraphAlgorithm for WeaklyConnectedComponents {
147 fn execute(&self, graph: &ArrowGraph, _params: &AlgorithmParams) -> Result<RecordBatch> {
148 let component_map = self.compute_components(graph)?;
149
150 if component_map.is_empty() {
151 let schema = Arc::new(Schema::new(vec![
153 Field::new("node_id", DataType::Utf8, false),
154 Field::new("component_id", DataType::UInt32, false),
155 ]));
156
157 return RecordBatch::try_new(
158 schema,
159 vec![
160 Arc::new(StringArray::from(Vec::<String>::new())),
161 Arc::new(UInt32Array::from(Vec::<u32>::new())),
162 ],
163 ).map_err(GraphError::from);
164 }
165
166 let mut sorted_nodes: Vec<(&String, &u32)> = component_map.iter().collect();
168 sorted_nodes.sort_by_key(|(_, &component_id)| component_id);
169
170 let node_ids: Vec<String> = sorted_nodes.iter().map(|(node, _)| (*node).clone()).collect();
171 let component_ids: Vec<u32> = sorted_nodes.iter().map(|(_, &comp)| comp).collect();
172
173 let schema = Arc::new(Schema::new(vec![
174 Field::new("node_id", DataType::Utf8, false),
175 Field::new("component_id", DataType::UInt32, false),
176 ]));
177
178 RecordBatch::try_new(
179 schema,
180 vec![
181 Arc::new(StringArray::from(node_ids)),
182 Arc::new(UInt32Array::from(component_ids)),
183 ],
184 ).map_err(GraphError::from)
185 }
186
187 fn name(&self) -> &'static str {
188 "weakly_connected_components"
189 }
190
191 fn description(&self) -> &'static str {
192 "Find weakly connected components using Union-Find with path compression"
193 }
194}
195
196pub struct StronglyConnectedComponents;
197
198impl StronglyConnectedComponents {
199 fn tarjan_scc(&self, graph: &ArrowGraph) -> Result<HashMap<String, u32>> {
201 let mut index_counter = 0;
202 let mut stack = Vec::new();
203 let mut indices: HashMap<String, usize> = HashMap::new();
204 let mut lowlinks: HashMap<String, usize> = HashMap::new();
205 let mut on_stack: HashMap<String, bool> = HashMap::new();
206 let mut components: Vec<Vec<String>> = Vec::new();
207
208 for node_id in graph.node_ids() {
210 on_stack.insert(node_id.clone(), false);
211 }
212
213 for node_id in graph.node_ids() {
215 if !indices.contains_key(node_id) {
216 self.tarjan_strongconnect(
217 node_id,
218 graph,
219 &mut index_counter,
220 &mut stack,
221 &mut indices,
222 &mut lowlinks,
223 &mut on_stack,
224 &mut components,
225 )?;
226 }
227 }
228
229 let mut node_to_component: HashMap<String, u32> = HashMap::new();
231 for (comp_id, component) in components.into_iter().enumerate() {
232 for node in component {
233 node_to_component.insert(node, comp_id as u32);
234 }
235 }
236
237 Ok(node_to_component)
238 }
239
240 fn tarjan_strongconnect(
241 &self,
242 node: &str,
243 graph: &ArrowGraph,
244 index_counter: &mut usize,
245 stack: &mut Vec<String>,
246 indices: &mut HashMap<String, usize>,
247 lowlinks: &mut HashMap<String, usize>,
248 on_stack: &mut HashMap<String, bool>,
249 components: &mut Vec<Vec<String>>,
250 ) -> Result<()> {
251 indices.insert(node.to_string(), *index_counter);
253 lowlinks.insert(node.to_string(), *index_counter);
254 *index_counter += 1;
255
256 stack.push(node.to_string());
258 on_stack.insert(node.to_string(), true);
259
260 if let Some(neighbors) = graph.neighbors(node) {
262 for neighbor in neighbors {
263 if !indices.contains_key(neighbor) {
264 self.tarjan_strongconnect(
266 neighbor,
267 graph,
268 index_counter,
269 stack,
270 indices,
271 lowlinks,
272 on_stack,
273 components,
274 )?;
275
276 let neighbor_lowlink = *lowlinks.get(neighbor).unwrap_or(&0);
277 let current_lowlink = *lowlinks.get(node).unwrap_or(&0);
278 lowlinks.insert(node.to_string(), current_lowlink.min(neighbor_lowlink));
279 } else if *on_stack.get(neighbor).unwrap_or(&false) {
280 let neighbor_index = *indices.get(neighbor).unwrap_or(&0);
282 let current_lowlink = *lowlinks.get(node).unwrap_or(&0);
283 lowlinks.insert(node.to_string(), current_lowlink.min(neighbor_index));
284 }
285 }
286 }
287
288 let node_index = *indices.get(node).unwrap_or(&0);
290 let node_lowlink = *lowlinks.get(node).unwrap_or(&0);
291
292 if node_lowlink == node_index {
293 let mut component = Vec::new();
294 loop {
295 if let Some(w) = stack.pop() {
296 on_stack.insert(w.clone(), false);
297 component.push(w.clone());
298 if w == node {
299 break;
300 }
301 } else {
302 break;
303 }
304 }
305 components.push(component);
306 }
307
308 Ok(())
309 }
310}
311
312impl GraphAlgorithm for StronglyConnectedComponents {
313 fn execute(&self, graph: &ArrowGraph, _params: &AlgorithmParams) -> Result<RecordBatch> {
314 let component_map = self.tarjan_scc(graph)?;
315
316 if component_map.is_empty() {
317 let schema = Arc::new(Schema::new(vec![
319 Field::new("node_id", DataType::Utf8, false),
320 Field::new("component_id", DataType::UInt32, false),
321 ]));
322
323 return RecordBatch::try_new(
324 schema,
325 vec![
326 Arc::new(StringArray::from(Vec::<String>::new())),
327 Arc::new(UInt32Array::from(Vec::<u32>::new())),
328 ],
329 ).map_err(GraphError::from);
330 }
331
332 let mut sorted_nodes: Vec<(&String, &u32)> = component_map.iter().collect();
334 sorted_nodes.sort_by_key(|(_, &component_id)| component_id);
335
336 let node_ids: Vec<String> = sorted_nodes.iter().map(|(node, _)| (*node).clone()).collect();
337 let component_ids: Vec<u32> = sorted_nodes.iter().map(|(_, &comp)| comp).collect();
338
339 let schema = Arc::new(Schema::new(vec![
340 Field::new("node_id", DataType::Utf8, false),
341 Field::new("component_id", DataType::UInt32, false),
342 ]));
343
344 RecordBatch::try_new(
345 schema,
346 vec![
347 Arc::new(StringArray::from(node_ids)),
348 Arc::new(UInt32Array::from(component_ids)),
349 ],
350 ).map_err(GraphError::from)
351 }
352
353 fn name(&self) -> &'static str {
354 "strongly_connected_components"
355 }
356
357 fn description(&self) -> &'static str {
358 "Find strongly connected components using Tarjan's algorithm"
359 }
360}