zrx_graph/graph/visitor/
path.rs

1// Copyright (c) Zensical LLC <https://zensical.org>
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under CLA
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;
29
30// ----------------------------------------------------------------------------
31// Structs
32// ----------------------------------------------------------------------------
33
34/// Visitor for paths between two nodes.
35pub struct Paths<'a> {
36    /// Graph topology.
37    topology: &'a Topology,
38    /// Target node.
39    target: usize,
40    /// Stack for depth-first search.
41    stack: Vec<(usize, usize)>,
42    /// Current path.
43    path: Vec<usize>,
44}
45
46// ----------------------------------------------------------------------------
47// Implementations
48// ----------------------------------------------------------------------------
49
50impl<'a> Paths<'a> {
51    /// Creates a visitor that yields all paths between the given nodes.
52    #[must_use]
53    pub fn new(topology: &'a Topology, source: usize, target: usize) -> Self {
54        Self {
55            topology,
56            target,
57            stack: Vec::from([(source, 0)]),
58            path: Vec::from([source]),
59        }
60    }
61}
62
63// ----------------------------------------------------------------------------
64// Trait implementations
65// ----------------------------------------------------------------------------
66
67impl Iterator for Paths<'_> {
68    type Item = Vec<usize>;
69
70    /// Returns the next path.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// # use std::error::Error;
76    /// # fn main() -> Result<(), Box<dyn Error>> {
77    /// use zrx_graph::visitor::Paths;
78    /// use zrx_graph::Graph;
79    ///
80    /// // Create graph builder and add nodes
81    /// let mut builder = Graph::builder();
82    /// let a = builder.add_node("a");
83    /// let b = builder.add_node("b");
84    /// let c = builder.add_node("c");
85    ///
86    /// // Create edges between nodes
87    /// builder.add_edge(a, b, 0)?;
88    /// builder.add_edge(b, c, 0)?;
89    ///
90    /// // Create graph from builder
91    /// let graph = builder.build();
92    ///
93    /// // Create iterator over paths
94    /// let mut paths = Paths::new(graph.topology(), a, c);
95    /// while let Some(path) = paths.next() {
96    ///     println!("{path:?}");
97    /// }
98    /// # Ok(())
99    /// # }
100    /// ```
101    fn next(&mut self) -> Option<Self::Item> {
102        let outgoing = self.topology.outgoing();
103
104        // Perform a depth-first search to find all paths from the source to
105        // the target, and yield them in the order of discovery
106        while let Some((node, depth)) = self.stack.pop() {
107            // Backtrack by truncating the current path to the depth of the
108            // current node, and then add the current node to the path
109            self.path.truncate(depth);
110            self.path.push(node);
111
112            // In case we've reached the target, yield the current path. Note
113            // that we need to clone it, since we can't return a reference
114            if node == self.target {
115                return Some(self.path.clone());
116            }
117
118            // Add descendants to stack in reverse order for consistent depth-
119            // first ordering. Additionally, perform a debug assertion to ensure
120            // that we don't revisit nodes within the current path, which would
121            // lead to infinite loops, but should never happen in a DAG.
122            for &descendant in outgoing[node].iter().rev() {
123                debug_assert!(!self.path.contains(&descendant));
124                self.stack.push((descendant, depth + 1));
125            }
126        }
127
128        // No more paths to visit
129        None
130    }
131}