use std::collections::VecDeque;
use crate::error::{HullabalooError, HullabalooResult};
#[derive(Debug, Clone)]
pub struct Graph {
pub adjacency: Vec<Vec<usize>>,
}
impl Graph {
pub fn num_vertices(&self) -> usize {
self.adjacency.len()
}
pub fn degree(&self, v: usize) -> usize {
self.adjacency[v].len()
}
pub fn neighbors(&self, v: usize) -> &[usize] {
&self.adjacency[v]
}
pub fn distance(&self, start: usize, goal: usize) -> HullabalooResult<usize> {
if start >= self.num_vertices() || goal >= self.num_vertices() {
return Err(HullabalooError::InvalidStructure {
message: format!(
"graph indices out of range (start={start}, goal={goal}, size={})",
self.num_vertices()
),
});
}
if start == goal {
return Ok(0);
}
let dist = self.bfs(start, Some(goal))?;
match dist[goal] {
usize::MAX => Err(HullabalooError::InvalidStructure {
message: "graph is disconnected: no path between requested vertices".to_string(),
}),
goal_dist => Ok(goal_dist),
}
}
pub fn diameter(&self) -> HullabalooResult<usize> {
let n = self.num_vertices();
if n == 0 {
return Ok(0);
}
let mut diameter = 0usize;
for start in 0..n {
let dist = self.bfs_from(start)?;
if let Some(max_for_start) = dist.into_iter().max() {
diameter = diameter.max(max_for_start);
}
}
Ok(diameter)
}
fn bfs_from(&self, start: usize) -> HullabalooResult<Vec<usize>> {
let dist = self.bfs(start, None)?;
if dist.contains(&usize::MAX) {
return Err(HullabalooError::InvalidStructure {
message: "graph is disconnected".to_string(),
});
}
Ok(dist)
}
fn bfs(&self, start: usize, stop_at: Option<usize>) -> HullabalooResult<Vec<usize>> {
if start >= self.num_vertices() {
return Err(HullabalooError::InvalidStructure {
message: format!(
"graph start index out of range (start={start}, size={})",
self.num_vertices()
),
});
}
let vertex_count = self.num_vertices();
let mut dist = vec![usize::MAX; vertex_count];
let mut queue = VecDeque::new();
dist[start] = 0;
queue.push_back(start);
while let Some(v) = queue.pop_front() {
let next_dist = dist[v] + 1;
for &n in &self.adjacency[v] {
if n >= vertex_count {
return Err(HullabalooError::InvalidStructure {
message: format!(
"graph adjacency references {n} but size is {vertex_count}"
),
});
}
if dist[n] != usize::MAX {
continue;
}
dist[n] = next_dist;
if Some(n) == stop_at {
return Ok(dist);
}
queue.push_back(n);
}
}
Ok(dist)
}
}
#[cfg(test)]
mod tests {
use super::Graph;
#[test]
fn distances_work_on_cycle() {
let graph = Graph {
adjacency: vec![vec![1, 3], vec![0, 2], vec![1, 3], vec![0, 2]],
};
assert_eq!(graph.distance(0, 0).unwrap(), 0);
assert_eq!(graph.distance(0, 1).unwrap(), 1);
assert_eq!(graph.distance(0, 2).unwrap(), 2);
assert_eq!(graph.distance(0, 3).unwrap(), 1);
assert_eq!(graph.diameter().unwrap(), 2);
}
}