Skip to main content

weavatrix_graph/algo/
traversal.rs

1use super::walk::{TraversalWorkspace, bfs_iter_filtered, dfs_iter_filtered};
2use crate::IndexGraphView;
3use crate::Vec;
4use alloc::collections::VecDeque;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum Direction {
8    #[default]
9    Outgoing,
10    Incoming,
11    Both,
12}
13
14#[must_use]
15pub fn bfs<G>(graph: &G, start: G::Node) -> Vec<G::Node>
16where
17    G: IndexGraphView,
18{
19    if !graph.contains_node(start) {
20        return Vec::new();
21    }
22    let mut seen = vec![false; graph.node_bound()];
23    let mut visited = Vec::with_capacity(graph.node_count());
24    seen[G::node_slot(start)] = true;
25    visited.push(start);
26    let mut cursor = 0;
27    while cursor < visited.len() {
28        let node = visited[cursor];
29        cursor += 1;
30        for edge in graph.outgoing_edges(node) {
31            let Some(endpoints) = graph.edge_endpoints(edge) else {
32                continue;
33            };
34            let neighbor = endpoints.target();
35            let slot = G::node_slot(neighbor);
36            if !seen[slot] {
37                seen[slot] = true;
38                visited.push(neighbor);
39            }
40        }
41    }
42    visited
43}
44
45#[must_use]
46pub fn bfs_filtered<G, F>(
47    graph: &G,
48    start: G::Node,
49    direction: Direction,
50    keep_edge: F,
51) -> Vec<G::Node>
52where
53    G: IndexGraphView,
54    F: FnMut(G::Edge) -> bool,
55{
56    let mut workspace = TraversalWorkspace::new();
57    bfs_iter_filtered(graph, start, direction, &mut workspace, keep_edge).collect()
58}
59
60#[must_use]
61pub fn dfs<G>(graph: &G, start: G::Node) -> Vec<G::Node>
62where
63    G: IndexGraphView,
64{
65    dfs_filtered(graph, start, Direction::Outgoing, |_| true)
66}
67
68#[must_use]
69pub fn dfs_filtered<G, F>(
70    graph: &G,
71    start: G::Node,
72    direction: Direction,
73    keep_edge: F,
74) -> Vec<G::Node>
75where
76    G: IndexGraphView,
77    F: FnMut(G::Edge) -> bool,
78{
79    let mut workspace = TraversalWorkspace::new();
80    dfs_iter_filtered(graph, start, direction, &mut workspace, keep_edge).collect()
81}
82
83#[must_use]
84pub fn reachable<G>(graph: &G, source: G::Node, target: G::Node) -> bool
85where
86    G: IndexGraphView,
87{
88    reachable_filtered(graph, source, target, Direction::Outgoing, |_| true)
89}
90
91pub fn reachable_filtered<G, F>(
92    graph: &G,
93    source: G::Node,
94    target: G::Node,
95    direction: Direction,
96    keep_edge: F,
97) -> bool
98where
99    G: IndexGraphView,
100    F: FnMut(G::Edge) -> bool,
101{
102    graph.contains_node(target)
103        && bfs_filtered(graph, source, direction, keep_edge)
104            .into_iter()
105            .any(|node| node == target)
106}
107
108#[must_use]
109pub fn shortest_path<G>(graph: &G, source: G::Node, target: G::Node) -> Option<Vec<G::Node>>
110where
111    G: IndexGraphView,
112{
113    shortest_path_filtered(graph, source, target, Direction::Outgoing, |_| true)
114}
115
116pub fn shortest_path_filtered<G, F>(
117    graph: &G,
118    source: G::Node,
119    target: G::Node,
120    direction: Direction,
121    mut keep_edge: F,
122) -> Option<Vec<G::Node>>
123where
124    G: IndexGraphView,
125    F: FnMut(G::Edge) -> bool,
126{
127    if !graph.contains_node(source) || !graph.contains_node(target) {
128        return None;
129    }
130    let mut predecessor = vec![None; graph.node_bound()];
131    let mut seen = vec![false; graph.node_bound()];
132    let mut queue = VecDeque::with_capacity(graph.node_count());
133    seen[G::node_slot(source)] = true;
134    queue.push_back(source);
135    while let Some(node) = queue.pop_front() {
136        if node == target {
137            return reconstruct_path::<G>(source, target, &predecessor);
138        }
139        for_each_neighbor(graph, node, direction, &mut keep_edge, |neighbor| {
140            let slot = G::node_slot(neighbor);
141            if !seen[slot] {
142                seen[slot] = true;
143                predecessor[slot] = Some(node);
144                queue.push_back(neighbor);
145            }
146        });
147    }
148    None
149}
150
151fn reconstruct_path<G: IndexGraphView>(
152    source: G::Node,
153    target: G::Node,
154    predecessor: &[Option<G::Node>],
155) -> Option<Vec<G::Node>> {
156    let mut path = vec![target];
157    let mut cursor = target;
158    while cursor != source {
159        if path.len() > predecessor.len() {
160            return None;
161        }
162        cursor = predecessor.get(G::node_slot(cursor)).copied().flatten()?;
163        path.push(cursor);
164    }
165    path.reverse();
166    Some(path)
167}
168
169pub(super) fn for_each_neighbor<G, F, V>(
170    graph: &G,
171    node: G::Node,
172    direction: Direction,
173    keep_edge: &mut F,
174    mut visit: V,
175) where
176    G: IndexGraphView,
177    F: FnMut(G::Edge) -> bool,
178    V: FnMut(G::Node),
179{
180    for_each_adjacent(graph, node, direction, keep_edge, |_, neighbor| {
181        visit(neighbor);
182    });
183}
184
185pub(super) fn for_each_adjacent<G, F, V>(
186    graph: &G,
187    node: G::Node,
188    direction: Direction,
189    keep_edge: &mut F,
190    mut visit: V,
191) where
192    G: IndexGraphView,
193    F: FnMut(G::Edge) -> bool,
194    V: FnMut(G::Edge, G::Node),
195{
196    if matches!(direction, Direction::Outgoing | Direction::Both) {
197        for edge in graph.outgoing_edges(node).filter(|edge| keep_edge(*edge)) {
198            if let Some(endpoints) = graph.edge_endpoints(edge) {
199                visit(edge, endpoints.target());
200            }
201        }
202    }
203    if matches!(direction, Direction::Incoming | Direction::Both) {
204        for edge in graph.incoming_edges(node).filter(|edge| keep_edge(*edge)) {
205            if let Some(endpoints) = graph.edge_endpoints(edge) {
206                visit(edge, endpoints.source());
207            }
208        }
209    }
210}