use crate::core::algo::traversal::TraversalError;
use crate::core::view::NodeId;
use crate::graph::graph::Graph;
use std::collections::HashSet;
use std::fmt::Debug;
use std::hash::Hash;
#[derive(Debug, thiserror::Error)]
pub enum SearchError {
#[error("Invalid node reference: node ID {0} not found")]
InvalidNode(NodeId),
}
impl From<TraversalError> for SearchError {
fn from(error: TraversalError) -> Self {
match error {
TraversalError::InvalidNode(id) => SearchError::InvalidNode(id),
}
}
}
#[deprecated(
since = "3.0.0",
note = "superseded by `xgraph::core::Traversal`, which is implemented for every graph \
view — including filtered ones — and carries no unused type parameters"
)]
pub trait Search<W, N, E>
where
W: Copy + Default + PartialEq,
N: Clone + Eq + Hash + Debug,
E: Clone + Default + Debug,
{
fn has_path(&self, start: NodeId, target: NodeId) -> Result<bool, SearchError>;
fn bfs_path(&self, start: NodeId, target: NodeId) -> Result<Option<Vec<NodeId>>, SearchError>;
#[deprecated(
since = "3.0.0",
note = "an implementation detail of `has_path`; use `has_path`, or \
`xgraph::core::Traversal::explore_into` to drive the search yourself"
)]
fn dfs(
&self,
current: NodeId,
target: NodeId,
visited: &mut HashSet<NodeId>,
) -> Result<bool, SearchError>;
fn has_node(&self, node: NodeId) -> Result<bool, SearchError>;
fn has_cycle(&self) -> Result<bool, SearchError>;
fn has_cycle_directed(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
recursion_stack: &mut HashSet<NodeId>,
) -> Result<bool, SearchError>;
fn has_cycle_undirected(
&self,
node: NodeId,
parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
) -> Result<bool, SearchError>;
}
#[allow(deprecated)]
impl<W, N, E> Search<W, N, E> for Graph<W, N, E>
where
W: Copy + Default + PartialEq,
N: Clone + Eq + Hash + Debug,
E: Clone + Default + Debug,
{
fn has_path(&self, start: NodeId, target: NodeId) -> Result<bool, SearchError> {
crate::core::Traversal::has_path(self, start, target).map_err(SearchError::from)
}
fn bfs_path(&self, start: NodeId, target: NodeId) -> Result<Option<Vec<NodeId>>, SearchError> {
crate::core::Traversal::bfs_path(self, start, target).map_err(SearchError::from)
}
fn dfs(
&self,
current: NodeId,
target: NodeId,
visited: &mut HashSet<NodeId>,
) -> Result<bool, SearchError> {
if !crate::core::GraphView::contains_node(self, current) {
return Err(SearchError::InvalidNode(current));
}
if !crate::core::GraphView::contains_node(self, target) {
return Err(SearchError::InvalidNode(target));
}
crate::core::Traversal::explore_into(self, current, Some(target), visited)
.map_err(SearchError::from)
}
fn has_node(&self, node: NodeId) -> Result<bool, SearchError> {
Ok(crate::core::GraphView::contains_node(self, node))
}
fn has_cycle(&self) -> Result<bool, SearchError> {
crate::core::Traversal::has_cycle(self).map_err(SearchError::from)
}
fn has_cycle_directed(
&self,
node: NodeId,
visited: &mut HashSet<NodeId>,
_recursion_stack: &mut HashSet<NodeId>,
) -> Result<bool, SearchError> {
crate::core::Traversal::has_cycle_from(self, node, visited).map_err(SearchError::from)
}
fn has_cycle_undirected(
&self,
node: NodeId,
_parent: Option<NodeId>,
visited: &mut HashSet<NodeId>,
) -> Result<bool, SearchError> {
crate::core::Traversal::has_cycle_from(self, node, visited).map_err(SearchError::from)
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
use crate::core::view::NodeId;
#[test]
fn test_has_path() {
let mut graph = Graph::<u32, (), ()>::new(false);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
assert!(graph.has_path(n0, n1).unwrap());
assert!(graph.has_path(n1, n0).unwrap());
assert!(matches!(
graph.has_path(NodeId::from_index(999), n0),
Err(SearchError::InvalidNode(id)) if id == NodeId::from_index(999)
));
}
#[test]
fn test_bfs_path() {
let mut graph = Graph::<u32, (), ()>::new(true);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
assert_eq!(graph.bfs_path(n0, n2).unwrap(), Some(vec![n0, n1, n2]));
assert!(matches!(
graph.bfs_path(NodeId::from_index(999), n0),
Err(SearchError::InvalidNode(id)) if id == NodeId::from_index(999)
));
}
#[test]
fn test_dfs() {
let mut graph = Graph::<u32, (), ()>::new(false);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
let mut visited = HashSet::new();
assert!(graph.dfs(n0, n2, &mut visited).unwrap());
assert!(matches!(
graph.dfs(NodeId::from_index(999), n0, &mut visited),
Err(SearchError::InvalidNode(id)) if id == NodeId::from_index(999)
));
}
#[test]
fn test_invalid_nodes() {
let graph = Graph::<u32, (), ()>::new(false);
assert!(!graph.has_node(NodeId::from_index(0)).unwrap());
assert!(matches!(
graph.has_path(NodeId::from_index(0), NodeId::from_index(1)),
Err(SearchError::InvalidNode(_))
));
assert!(matches!(
graph.bfs_path(NodeId::from_index(0), NodeId::from_index(1)),
Err(SearchError::InvalidNode(_))
));
}
#[test]
fn test_cycle_detection_directed() {
let mut graph = Graph::<u32, (), ()>::new(true);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
graph.add_edge(n2, n0, 1, ()).unwrap();
assert!(graph.has_cycle().unwrap());
}
#[test]
fn test_cycle_detection_undirected() {
let mut graph = Graph::<u32, (), ()>::new(false);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
graph.add_edge(n2, n0, 1, ()).unwrap();
assert!(graph.has_cycle().unwrap());
}
#[test]
fn test_no_cycle_directed() {
let mut graph = Graph::<u32, (), ()>::new(true);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
assert!(!graph.has_cycle().unwrap());
}
#[test]
fn test_no_cycle_undirected() {
let mut graph = Graph::<u32, (), ()>::new(false);
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1, ()).unwrap();
graph.add_edge(n1, n2, 1, ()).unwrap();
assert!(!graph.has_cycle().unwrap());
}
#[test]
fn test_parallel_edges_form_an_undirected_cycle() {
let mut graph = Graph::<u32, (), ()>::new(false);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_edge(a, b, 1, ()).unwrap();
graph.add_edge(a, b, 1, ()).unwrap();
assert!(graph.has_cycle().unwrap());
}
#[test]
fn test_single_edge_is_not_an_undirected_cycle() {
let mut graph = Graph::<u32, (), ()>::new(false);
let a = graph.add_node(());
let b = graph.add_node(());
graph.add_edge(a, b, 1, ()).unwrap();
assert!(!graph.has_cycle().unwrap());
}
#[test]
fn test_deep_chain_does_not_overflow_the_stack() {
let mut graph = Graph::<u32, usize, ()>::new(true);
let nodes: Vec<_> = (0..200_000).map(|i| graph.add_node(i)).collect();
for pair in nodes.windows(2) {
graph.add_edge(pair[0], pair[1], 1, ()).unwrap();
}
let first = *nodes.first().unwrap();
let last = *nodes.last().unwrap();
assert!(graph.has_path(first, last).unwrap());
assert!(!graph.has_cycle().unwrap());
}
}