use std::collections::VecDeque;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DistancesFromMode {
Out,
In,
All,
}
pub fn distances_from(graph: &Graph, sources: &[VertexId]) -> IgraphResult<Vec<Option<u32>>> {
let n = graph.vcount();
validate_sources(sources, n)?;
if sources.is_empty() {
return Ok(Vec::new());
}
let n_us = n as usize;
let total = sources.len().checked_mul(n_us).ok_or_else(|| {
IgraphError::InvalidArgument("distances_from: result size overflows".into())
})?;
let mut result = vec![None; total];
if graph.is_directed() {
let adj = build_out_adj(graph, n_us)?;
for (row, &src) in sources.iter().enumerate() {
bfs_with_adj(&adj, src, n_us, row, &mut result);
}
} else {
for (row, &src) in sources.iter().enumerate() {
bfs_undirected(graph, src, n_us, row, &mut result)?;
}
}
Ok(result)
}
pub fn distances_from_with_mode(
graph: &Graph,
sources: &[VertexId],
mode: DistancesFromMode,
) -> IgraphResult<Vec<Option<u32>>> {
let n = graph.vcount();
validate_sources(sources, n)?;
if sources.is_empty() {
return Ok(Vec::new());
}
let n_us = n as usize;
let total = sources.len().checked_mul(n_us).ok_or_else(|| {
IgraphError::InvalidArgument("distances_from: result size overflows".into())
})?;
let mut result = vec![None; total];
if !graph.is_directed() {
for (row, &src) in sources.iter().enumerate() {
bfs_undirected(graph, src, n_us, row, &mut result)?;
}
return Ok(result);
}
let adj = match mode {
DistancesFromMode::Out => build_out_adj(graph, n_us)?,
DistancesFromMode::In => build_in_adj(graph, n_us)?,
DistancesFromMode::All => build_all_adj(graph, n_us)?,
};
for (row, &src) in sources.iter().enumerate() {
bfs_with_adj(&adj, src, n_us, row, &mut result);
}
Ok(result)
}
fn validate_sources(sources: &[VertexId], n: u32) -> IgraphResult<()> {
for &s in sources {
if s >= n {
return Err(IgraphError::InvalidArgument(format!(
"distances_from: source {s} out of range (vcount={n})"
)));
}
}
Ok(())
}
fn bfs_undirected(
graph: &Graph,
source: VertexId,
n_us: usize,
row: usize,
result: &mut [Option<u32>],
) -> IgraphResult<()> {
let offset = row * n_us;
let mut visited = vec![false; n_us];
let mut queue = VecDeque::new();
visited[source as usize] = true;
result[offset + source as usize] = Some(0);
queue.push_back((source, 0u32));
while let Some((cur, dist)) = queue.pop_front() {
let next_dist = dist + 1;
for nb in graph.neighbors_iter(cur)? {
let nb_idx = nb as usize;
if !visited[nb_idx] {
visited[nb_idx] = true;
result[offset + nb_idx] = Some(next_dist);
queue.push_back((nb, next_dist));
}
}
}
Ok(())
}
fn bfs_with_adj(
adj: &[Vec<VertexId>],
source: VertexId,
n_us: usize,
row: usize,
result: &mut [Option<u32>],
) {
let offset = row * n_us;
let mut visited = vec![false; n_us];
let mut queue = VecDeque::new();
visited[source as usize] = true;
result[offset + source as usize] = Some(0);
queue.push_back((source, 0u32));
while let Some((cur, dist)) = queue.pop_front() {
let next_dist = dist + 1;
for &nb in &adj[cur as usize] {
let nb_idx = nb as usize;
if !visited[nb_idx] {
visited[nb_idx] = true;
result[offset + nb_idx] = Some(next_dist);
queue.push_back((nb, next_dist));
}
}
}
}
fn build_out_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
let m =
u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
for eid in 0..m {
let (from, to) = graph.edge(eid)?;
adj[from as usize].push(to);
}
Ok(adj)
}
fn build_in_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
let m =
u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
for eid in 0..m {
let (from, to) = graph.edge(eid)?;
adj[to as usize].push(from);
}
Ok(adj)
}
fn build_all_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
let m =
u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
for eid in 0..m {
let (from, to) = graph.edge(eid)?;
adj[from as usize].push(to);
if from != to {
adj[to as usize].push(from);
}
}
Ok(adj)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_sources() {
let g = Graph::with_vertices(3);
let d = distances_from(&g, &[]).unwrap();
assert!(d.is_empty());
}
#[test]
fn single_source_matches_distances() {
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 d = distances_from(&g, &[0]).unwrap();
assert_eq!(d, vec![Some(0), Some(1), Some(2), Some(3)]);
}
#[test]
fn multiple_sources() {
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 d = distances_from(&g, &[0, 3]).unwrap();
let n = 4;
assert_eq!(d[0], Some(0));
assert_eq!(d[1], Some(1));
assert_eq!(d[2], Some(2));
assert_eq!(d[3], Some(3));
assert_eq!(d[n], Some(3));
assert_eq!(d[n + 1], Some(2));
assert_eq!(d[n + 2], Some(1));
assert_eq!(d[n + 3], Some(0));
}
#[test]
fn disconnected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let d = distances_from(&g, &[0]).unwrap();
assert_eq!(d[0], Some(0));
assert_eq!(d[1], Some(1));
assert_eq!(d[2], None);
assert_eq!(d[3], None);
}
#[test]
fn source_out_of_range() {
let g = Graph::with_vertices(3);
assert!(distances_from(&g, &[5]).is_err());
}
#[test]
fn directed_out() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = distances_from(&g, &[0, 2]).unwrap();
let n = 3;
assert_eq!(d[0], Some(0));
assert_eq!(d[1], Some(1));
assert_eq!(d[2], Some(2));
assert_eq!(d[n], None);
assert_eq!(d[n + 1], None);
assert_eq!(d[n + 2], Some(0));
}
#[test]
fn directed_in_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = distances_from_with_mode(&g, &[2], DistancesFromMode::In).unwrap();
assert_eq!(d[0], Some(2));
assert_eq!(d[1], Some(1));
assert_eq!(d[2], Some(0));
}
#[test]
fn directed_all_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = distances_from_with_mode(&g, &[2], DistancesFromMode::All).unwrap();
assert_eq!(d[0], Some(2));
assert_eq!(d[1], Some(1));
assert_eq!(d[2], Some(0));
}
#[test]
fn duplicate_sources() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = distances_from(&g, &[0, 0]).unwrap();
let n = 3;
assert_eq!(&d[..n], &d[n..]);
}
#[test]
fn star_graph() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
g.add_edge(0, 4).unwrap();
let d = distances_from(&g, &[1, 2]).unwrap();
let n = 5;
assert_eq!(d[0], Some(1));
assert_eq!(d[1], Some(0));
assert_eq!(d[2], Some(2));
assert_eq!(d[3], Some(2));
assert_eq!(d[4], Some(2));
assert_eq!(d[n], Some(1));
assert_eq!(d[n + 1], Some(2));
assert_eq!(d[n + 2], Some(0));
}
}