zrx_graph/graph/visitor/paths.rs
1// Copyright (c) 2025 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Visitor for paths between two nodes.
27
28use crate::graph::topology::Topology;
29use crate::graph::Graph;
30
31// ----------------------------------------------------------------------------
32// Structs
33// ----------------------------------------------------------------------------
34
35/// Visitor for paths between two nodes.
36pub struct Paths<'a> {
37 /// Graph topology.
38 topology: &'a Topology,
39 /// Target node.
40 target: usize,
41 /// Stack for depth-first search.
42 stack: Vec<(usize, usize)>,
43 /// Current path.
44 path: Vec<usize>,
45}
46
47// ----------------------------------------------------------------------------
48// Implementations
49// ----------------------------------------------------------------------------
50
51impl<T> Graph<T> {
52 /// Creates an iterator over all paths between the given nodes.
53 ///
54 /// # Examples
55 ///
56 /// ```
57 /// # use std::error::Error;
58 /// # fn main() -> Result<(), Box<dyn Error>> {
59 /// use zrx_graph::Graph;
60 ///
61 /// // Create graph builder and add nodes
62 /// let mut builder = Graph::builder();
63 /// let a = builder.add_node("a");
64 /// let b = builder.add_node("b");
65 /// let c = builder.add_node("c");
66 ///
67 /// // Create edges between nodes
68 /// builder.add_edge(a, b, 0)?;
69 /// builder.add_edge(b, c, 0)?;
70 ///
71 /// // Create graph from builder
72 /// let graph = builder.build();
73 ///
74 /// // Create iterator over paths
75 /// for path in graph.paths(a, c) {
76 /// println!("{path:?}");
77 /// }
78 /// # Ok(())
79 /// # }
80 /// ```
81 #[inline]
82 #[must_use]
83 pub fn paths(&self, source: usize, target: usize) -> Paths<'_> {
84 Paths {
85 topology: &self.topology,
86 target,
87 stack: Vec::from([(source, 0)]),
88 path: Vec::from([source]),
89 }
90 }
91}
92
93// ----------------------------------------------------------------------------
94// Trait implementations
95// ----------------------------------------------------------------------------
96
97impl Iterator for Paths<'_> {
98 type Item = Vec<usize>;
99
100 /// Returns the next path.
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// # use std::error::Error;
106 /// # fn main() -> Result<(), Box<dyn Error>> {
107 /// use zrx_graph::Graph;
108 ///
109 /// // Create graph builder and add nodes
110 /// let mut builder = Graph::builder();
111 /// let a = builder.add_node("a");
112 /// let b = builder.add_node("b");
113 /// let c = builder.add_node("c");
114 ///
115 /// // Create edges between nodes
116 /// builder.add_edge(a, b, 0)?;
117 /// builder.add_edge(b, c, 0)?;
118 ///
119 /// // Create graph from builder
120 /// let graph = builder.build();
121 ///
122 /// // Create iterator over paths
123 /// let mut paths = graph.paths(a, c);
124 /// while let Some(path) = paths.next() {
125 /// println!("{path:?}");
126 /// }
127 /// # Ok(())
128 /// # }
129 /// ```
130 fn next(&mut self) -> Option<Self::Item> {
131 let outgoing = self.topology.outgoing();
132
133 // Perform a depth-first search to find all paths from the source to
134 // the target, and emit them in the order of discovery
135 while let Some((node, depth)) = self.stack.pop() {
136 // Backtrack by truncating the current path to the depth of the
137 // current node, and then add the current node to the path
138 self.path.truncate(depth);
139 self.path.push(node);
140
141 // In case we've reached the target, yield the current path. Note
142 // that we need to clone it, since we can't return a reference
143 if node == self.target {
144 return Some(self.path.clone());
145 }
146
147 // Add descendants to stack in reverse order for consistent depth-
148 // first ordering. Additionally, perform a debug assertion to ensure
149 // that we don't revisit nodes within the current path, which would
150 // lead to infinite loops, but should never happen in a DAG.
151 for &descendant in outgoing[node].iter().rev() {
152 debug_assert!(!self.path.contains(&descendant));
153 self.stack.push((descendant, depth + 1));
154 }
155 }
156
157 // No more paths to visit
158 None
159 }
160}