use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubcomponentMode {
Out,
In,
All,
}
pub fn subcomponent(
graph: &Graph,
source: VertexId,
mode: SubcomponentMode,
) -> IgraphResult<Vec<VertexId>> {
let n = graph.vcount();
if source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
if n == 0 {
return Ok(Vec::new());
}
let adj = build_adj(graph, mode)?;
let mut visited = vec![false; n as usize];
let mut result = Vec::new();
let mut queue = std::collections::VecDeque::new();
visited[source as usize] = true;
queue.push_back(source);
while let Some(v) = queue.pop_front() {
result.push(v);
for &nb in &adj[v as usize] {
if !visited[nb as usize] {
visited[nb as usize] = true;
queue.push_back(nb);
}
}
}
Ok(result)
}
fn build_adj(graph: &Graph, mode: SubcomponentMode) -> IgraphResult<Vec<Vec<VertexId>>> {
let n = graph.vcount() as usize;
let ecount = graph.ecount();
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
if src == tgt {
continue;
}
if !graph.is_directed() || mode == SubcomponentMode::All {
adj[src as usize].push(tgt);
adj[tgt as usize].push(src);
} else if mode == SubcomponentMode::Out {
adj[src as usize].push(tgt);
} else {
adj[tgt as usize].push(src);
}
}
Ok(adj)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_out_of_range() {
let g = Graph::with_vertices(3);
assert!(subcomponent(&g, 5, SubcomponentMode::All).is_err());
}
#[test]
fn test_single_vertex() {
let g = Graph::with_vertices(1);
let r = subcomponent(&g, 0, SubcomponentMode::All).unwrap();
assert_eq!(r, vec![0]);
}
#[test]
fn test_disconnected_undirected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::All).unwrap();
assert_eq!(r.len(), 2);
assert!(r.contains(&0));
assert!(r.contains(&1));
}
#[test]
fn test_connected_undirected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::All).unwrap();
assert_eq!(r.len(), 4);
}
#[test]
fn test_directed_out() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 0).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::Out).unwrap();
assert_eq!(r, vec![0, 1, 2]);
}
#[test]
fn test_directed_in() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 0).unwrap();
let r = subcomponent(&g, 2, SubcomponentMode::In).unwrap();
assert_eq!(r, vec![2, 1, 0, 3]);
}
#[test]
fn test_directed_all() {
let mut g = Graph::new(5, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 1).unwrap();
g.add_edge(3, 4).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::All).unwrap();
assert_eq!(r.len(), 3);
assert!(r.contains(&0));
assert!(r.contains(&1));
assert!(r.contains(&2));
}
#[test]
fn test_isolated_vertex() {
let g = Graph::with_vertices(5);
let r = subcomponent(&g, 3, SubcomponentMode::All).unwrap();
assert_eq!(r, vec![3]);
}
#[test]
fn test_self_loop_ignored() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::Out).unwrap();
assert_eq!(r, vec![0, 1]);
}
#[test]
fn test_cycle_directed_out() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::Out).unwrap();
assert_eq!(r.len(), 3);
}
#[test]
fn test_bfs_order() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 3).unwrap();
let r = subcomponent(&g, 0, SubcomponentMode::Out).unwrap();
assert_eq!(r[0], 0);
assert!(r[1] == 1 || r[1] == 2);
}
#[test]
fn test_undirected_mode_ignored() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let out = subcomponent(&g, 0, SubcomponentMode::Out).unwrap();
let in_ = subcomponent(&g, 0, SubcomponentMode::In).unwrap();
let all = subcomponent(&g, 0, SubcomponentMode::All).unwrap();
assert_eq!(out.len(), 3);
assert_eq!(in_.len(), 3);
assert_eq!(all.len(), 3);
}
}