use crate::core::algo::traversal::{Traversal, TraversalError};
use crate::core::view::{GraphView, NodeId};
use std::collections::{HashMap, HashSet};
pub type Predecessors = HashMap<NodeId, Vec<NodeId>>;
pub trait Connectivity: GraphView {
fn predecessors(&self) -> Predecessors {
let mut predecessors: Predecessors = HashMap::new();
for node in self.node_ids() {
predecessors.entry(node).or_default();
}
for node in self.node_ids() {
for link in self.links(node) {
predecessors.entry(link.target).or_default().push(node);
}
}
predecessors
}
fn weakly_connected_components(&self) -> Result<Vec<Vec<NodeId>>, TraversalError> {
let predecessors = if self.is_directed() {
self.predecessors()
} else {
Predecessors::new()
};
let mut visited = HashSet::new();
let mut components = Vec::new();
for root in self.node_ids() {
if !visited.insert(root) {
continue;
}
let mut component = vec![root];
let mut stack = vec![root];
while let Some(node) = stack.pop() {
for link in self.links(node) {
if visited.insert(link.target) {
component.push(link.target);
stack.push(link.target);
}
}
if let Some(preds) = predecessors.get(&node) {
for &predecessor in preds {
if visited.insert(predecessor) {
component.push(predecessor);
stack.push(predecessor);
}
}
}
}
component.sort_unstable();
components.push(component);
}
Ok(components)
}
fn strongly_connected_components(&self) -> Result<Vec<Vec<NodeId>>, TraversalError> {
let mut visited = HashSet::new();
let mut finish_order = Vec::with_capacity(self.node_count());
for root in self.node_ids() {
self.dfs_postorder_into(root, &mut visited, &mut finish_order)?;
}
let predecessors = self.predecessors();
let mut visited = HashSet::new();
let mut components = Vec::new();
for &root in finish_order.iter().rev() {
if !visited.insert(root) {
continue;
}
let mut component = vec![root];
let mut stack = vec![root];
while let Some(node) = stack.pop() {
for &predecessor in predecessors.get(&node).into_iter().flatten() {
if visited.insert(predecessor) {
component.push(predecessor);
stack.push(predecessor);
}
}
}
component.sort_unstable();
components.push(component);
}
Ok(components)
}
fn connected_components(&self) -> Result<Vec<Vec<NodeId>>, TraversalError> {
if self.is_directed() {
self.strongly_connected_components()
} else {
self.weakly_connected_components()
}
}
fn is_weakly_connected(&self) -> Result<bool, TraversalError> {
Ok(self.weakly_connected_components()?.len() <= 1)
}
fn is_strongly_connected(&self) -> Result<bool, TraversalError> {
let Some(start) = self.node_ids().next() else {
return Ok(true);
};
let total = self.node_count();
if self.reachable_from(start)?.len() != total {
return Ok(false);
}
let predecessors = self.predecessors();
let mut visited = HashSet::new();
visited.insert(start);
let mut stack = vec![start];
while let Some(node) = stack.pop() {
for &predecessor in predecessors.get(&node).into_iter().flatten() {
if visited.insert(predecessor) {
stack.push(predecessor);
}
}
}
Ok(visited.len() == total)
}
fn is_connected(&self) -> Result<bool, TraversalError> {
if self.is_directed() {
self.is_strongly_connected()
} else {
self.is_weakly_connected()
}
}
}
impl<G: GraphView + ?Sized> Connectivity for G {}
#[cfg(test)]
#[cfg(feature = "graph")]
mod tests {
use super::*;
use crate::core::EdgeFiltered;
use crate::graph::Graph;
#[test]
fn test_cycle_is_one_strong_component() {
let mut graph = Graph::<u32, u32, ()>::new(true);
let nodes: Vec<_> = (0..3).map(|i| graph.add_node(i)).collect();
for i in 0..3 {
graph.add_edge(nodes[i], nodes[(i + 1) % 3], 1, ()).unwrap();
}
assert_eq!(graph.strongly_connected_components().unwrap().len(), 1);
assert!(graph.is_strongly_connected().unwrap());
assert!(graph.is_connected().unwrap());
}
#[test]
fn test_directed_path_is_weakly_but_not_strongly_connected() {
let mut graph = Graph::<u32, u32, ()>::new(true);
let a = graph.add_node(0);
let b = graph.add_node(1);
graph.add_edge(a, b, 1, ()).unwrap();
assert!(graph.is_weakly_connected().unwrap());
assert!(!graph.is_strongly_connected().unwrap());
assert_eq!(graph.strongly_connected_components().unwrap().len(), 2);
}
#[test]
fn test_components_survive_sparse_ids() {
let mut graph = Graph::<u32, u32, ()>::new(true);
let a = graph.add_node(0);
let gap = graph.add_node(1);
let b = graph.add_node(2);
let c = graph.add_node(3);
graph.remove_node(gap).unwrap();
graph.add_edge(a, b, 1, ()).unwrap();
graph.add_edge(b, c, 1, ()).unwrap();
graph.add_edge(c, a, 1, ()).unwrap();
let scc = graph.strongly_connected_components().unwrap();
assert_eq!(scc, vec![vec![a, b, c]]);
}
#[test]
fn test_empty_graph_is_connected() {
let graph = Graph::<u32, u32, ()>::new(true);
assert!(graph.is_connected().unwrap());
assert!(graph.connected_components().unwrap().is_empty());
}
#[test]
fn test_connectivity_over_a_filtered_view() {
let mut graph = Graph::<u32, u32, &str>::new(false);
let a = graph.add_node(0);
let b = graph.add_node(1);
let c = graph.add_node(2);
let rail = graph.add_edge(a, b, 1, "rail").unwrap();
graph.add_edge(b, c, 1, "road").unwrap();
assert!(graph.is_connected().unwrap());
let rail_only = EdgeFiltered::new(&graph, |edge| edge == rail);
assert_eq!(
rail_only.weakly_connected_components().unwrap(),
vec![vec![a, b], vec![c]]
);
assert!(!rail_only.is_connected().unwrap());
}
}