From f49b2783e22bcaac22a5bebc594d0b561a11784f Mon Sep 17 00:00:00 2001 From: Julia Volmer Date: Sat, 4 Jan 2025 23:15:50 +0100 Subject: [PATCH] Add WCC algorithm --- src/algorithms/wcc/algorithm.rs | 88 ++++++++++++++++++++++++++++++++ src/algorithms/wcc/mod.rs | 1 + src/algorithms/wcc/union_find.rs | 75 ++++++++++++++++----------- src/graph.rs | 4 ++ 4 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 src/algorithms/wcc/algorithm.rs diff --git a/src/algorithms/wcc/algorithm.rs b/src/algorithms/wcc/algorithm.rs new file mode 100644 index 0000000..48c2306 --- /dev/null +++ b/src/algorithms/wcc/algorithm.rs @@ -0,0 +1,88 @@ +use crate::{Component, Graph}; + +use super::union_find::UnionFind; + +pub struct WCC<'a> { + graph: &'a Graph, + union_find: UnionFind, +} + +impl<'a> WCC<'a> { + pub fn on(graph: &'a Graph) -> Self { + Self { + graph, + union_find: UnionFind::new(graph.vertices().map(|v| v.clone())), + } + } + pub fn get(self) -> impl Iterator { + let mut union_find = self.union_find; + self.graph + .edges() + .map(|e| (e.clone().0, e.1.clone())) + .for_each(|(from, to)| union_find.union(from, to).unwrap()); + union_find.all_components() + } +} + +#[cfg(test)] +mod tests { + use crate::{Graph, VertexId}; + + use super::*; + + #[test] + fn empty_graph_has_no_component() { + let graph = Graph::from(0, vec![]).unwrap(); + assert_eq!( + WCC::on(&graph).get().collect::>(), + Vec::::new() + ); + } + + #[test] + fn single_vertex_is_a_weak_component() { + let graph = Graph::from(1, vec![]).unwrap(); + assert_eq!( + WCC::on(&graph).get().collect::>(), + vec![Component::from(vec![VertexId(0)])] + ); + } + + #[test] + fn unconnected_vertices_are_in_separate_components() { + let graph = Graph::from(2, vec![]).unwrap(); + let wcc = WCC::on(&graph).get().collect::>(); + assert_eq!(wcc.len(), 2); + assert!(wcc.contains(&Component::from(vec![VertexId(0)]))); + assert!(wcc.contains(&Component::from(vec![VertexId(1)]))); + } + + #[test] + fn connected_vertices_are_in_same_components_indepent_of_edge_direction() { + let graph = Graph::from(2, vec![(0, 1)]).unwrap(); + assert_eq!( + WCC::on(&graph).get().collect::>(), + vec![Component::from(vec![VertexId(0), VertexId(1)]),] + ); + + let graph = Graph::from(2, vec![(1, 0)]).unwrap(); + assert_eq!( + WCC::on(&graph).get().collect::>(), + vec![Component::from(vec![VertexId(1), VertexId(0)]),] + ); + } + + #[test] + fn finds_components_on_some_random_graph() { + let graph = Graph::from(6, vec![(0, 1), (1, 0), (2, 3), (3, 4), (5, 2), (2, 5)]).unwrap(); + let wcc = WCC::on(&graph).get().collect::>(); + assert_eq!(wcc.len(), 2); + assert!(wcc.contains(&Component::from(vec![VertexId(0), VertexId(1)]))); + assert!(wcc.contains(&Component::from(vec![ + VertexId(2), + VertexId(3), + VertexId(4), + VertexId(5) + ]))); + } +} diff --git a/src/algorithms/wcc/mod.rs b/src/algorithms/wcc/mod.rs index 4162f27..9bd9183 100644 --- a/src/algorithms/wcc/mod.rs +++ b/src/algorithms/wcc/mod.rs @@ -1 +1,2 @@ +pub mod algorithm; pub mod union_find; diff --git a/src/algorithms/wcc/union_find.rs b/src/algorithms/wcc/union_find.rs index 7cabe07..3f0db6e 100644 --- a/src/algorithms/wcc/union_find.rs +++ b/src/algorithms/wcc/union_find.rs @@ -8,54 +8,37 @@ ///! Another improvement that could be implemented: path compression in find fn use std::collections::HashMap; -use crate::graph::VertexId; - -#[derive(Debug, PartialEq, Clone)] -enum Node { - TreeRoot(usize), - DecendentOf(VertexId), -} +use crate::{algorithms::component::Component, graph::VertexId}; #[derive(Debug, PartialEq)] -struct Component { - id: VertexId, - size: usize, -} - -#[derive(Debug, PartialEq)] -struct UnionFind { +pub struct UnionFind { list: HashMap, } -#[derive(Debug, PartialEq)] -enum Error { - VertexNotIncluded(VertexId), -} impl UnionFind { - fn new(vertices: impl Iterator) -> Self { - Self { - list: HashMap::from_iter(vertices.map(|v| (v.clone(), Node::TreeRoot(1)))), - } + pub fn new(vertices: impl Iterator) -> Self { + let list = HashMap::from_iter(vertices.map(|v| (v.clone(), Node::TreeRoot(1)))); + Self { list } } - fn find(&self, id: VertexId) -> Result { + fn find(&self, id: VertexId) -> Result { let x = self .list .get(&id) .ok_or(Error::VertexNotIncluded(id.clone()))?; match x.clone() { - Node::TreeRoot(size) => return Ok(Component { id, size }), + Node::TreeRoot(size) => return Ok(ComponentId { id, size }), Node::DecendentOf(id) => return self.find(id), } } - fn union(&mut self, x: VertexId, y: VertexId) -> Result<(), Error> { + pub fn union(&mut self, x: VertexId, y: VertexId) -> Result<(), Error> { match (self.find(x), self.find(y)) { ( - Ok(Component { + Ok(ComponentId { id: xroot, size: xsize, }), - Ok(Component { + Ok(ComponentId { id: yroot, size: ysize, }), @@ -75,6 +58,40 @@ impl UnionFind { (_, Err(e)) => return Err(e), } } + + pub fn all_components(&self) -> impl Iterator { + self.list + .iter() + .map(|(v, _)| (v, self.find(v.clone()))) + .fold( + HashMap::new(), + |mut acc: HashMap, (vertex_id, component_id)| { + acc.entry(component_id.unwrap().id) + .and_modify(|component| component.add(vertex_id.clone())) + .or_insert(Component::from(vec![vertex_id.clone()])); + acc + }, + ) + .into_iter() + .map(|(_, component)| component) + } +} + +#[derive(Debug, PartialEq, Clone)] +enum Node { + TreeRoot(usize), + DecendentOf(VertexId), +} + +#[derive(Debug, PartialEq)] +struct ComponentId { + pub id: VertexId, + size: usize, +} + +#[derive(Debug, PartialEq)] +pub enum Error { + VertexNotIncluded(VertexId), } #[cfg(test)] @@ -86,7 +103,7 @@ mod tests { let union_find = UnionFind::new(vec![VertexId(1)].into_iter()); assert_eq!( union_find.find(VertexId(1)), - Ok(Component { + Ok(ComponentId { id: VertexId(1), size: 1 }) @@ -99,7 +116,7 @@ mod tests { UnionFind::new(vec![VertexId(1), VertexId(3), VertexId(5), VertexId(2)].into_iter()); assert_eq!( union_find.find(VertexId(5)), - Ok(Component { + Ok(ComponentId { id: VertexId(5), size: 1 }) diff --git a/src/graph.rs b/src/graph.rs index 00599af..f54223f 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -150,6 +150,10 @@ impl Graph { pub fn vertices(&self) -> impl Iterator { self.vertices.iter() } + + pub fn edges(&self) -> impl Iterator { + self.edges.iter() + } } #[cfg(test)]