mz_ore/
graph.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Graph utilities.
17
18use std::collections::BTreeSet;
19
20/// A non-recursive implementation of a fallible depth-first traversal
21/// starting from `root`.
22///
23/// Assumes that nodes in the graph all have unique node ids.
24///
25/// `at_enter` runs when entering a node. It is expected to return an in-order
26/// list of the children of the node. You can omit children from the list
27/// returned if you want to skip traversing the subgraphs corresponding to
28/// those children. If no children are omitted, `at_enter` can be thought
29/// of as a function that processes the nodes of the graph in pre-order.
30///
31/// `at_exit` runs when exiting a node. It can be thought of as a function that
32/// processes the nodes of the graph in post-order.
33///
34/// This function only enters and exits a node at most once and thus is safe to
35/// run even if the graph contains a cycle.
36pub fn try_nonrecursive_dft<Graph, NodeId, AtEnter, AtExit, E>(
37    graph: &Graph,
38    root: NodeId,
39    at_enter: &mut AtEnter,
40    at_exit: &mut AtExit,
41) -> Result<(), E>
42where
43    NodeId: std::cmp::Ord,
44    AtEnter: FnMut(&Graph, &NodeId) -> Result<Vec<NodeId>, E>,
45    AtExit: FnMut(&Graph, &NodeId) -> Result<(), E>,
46{
47    // All nodes that have been entered but not exited. Last node in the vec is
48    // the node that we most recently entered.
49    let mut entered = Vec::new();
50    // All nodes that have been exited.
51    let mut exited = BTreeSet::new();
52
53    // Pseudocode for the recursive version of this function would look like:
54    // ```
55    // children = at_enter(graph, node)
56    // foreach child in children:
57    //    recursive_call(graph, child)
58    // atexit(graph, node)
59    // ```
60    // In this non-recursive implementation, you can think of the call stack as
61    // been replaced by `entered`. Every time an object is pushed into `entered`
62    // would have been a time you would have pushed a recursive call onto the
63    // call stack. Likewise, times an object is popped from `entered` would have
64    // been times when recursive calls leave the stack.
65
66    // Enter from the root.
67    let children = at_enter(graph, &root)?;
68    entered_node(&mut entered, root, children);
69    while !entered.is_empty() {
70        if let Some(to_enter) = find_next_child_to_enter(&mut entered, &exited) {
71            let children = at_enter(graph, &to_enter)?;
72            entered_node(&mut entered, to_enter, children);
73        } else {
74            // If this node has no more children to descend into,
75            // exit the current node and run `at_exit`.
76            let (to_exit, _) = entered.pop().unwrap();
77            at_exit(graph, &to_exit)?;
78            exited.insert(to_exit);
79        }
80    }
81    Ok(())
82}
83
84/// Same as [`try_nonrecursive_dft`], but allows changes to be made to the graph.
85pub fn try_nonrecursive_dft_mut<Graph, NodeId, AtEnter, AtExit, E>(
86    graph: &mut Graph,
87    root: NodeId,
88    at_enter: &mut AtEnter,
89    at_exit: &mut AtExit,
90) -> Result<(), E>
91where
92    NodeId: std::cmp::Ord + Clone,
93    AtEnter: FnMut(&mut Graph, &NodeId) -> Result<Vec<NodeId>, E>,
94    AtExit: FnMut(&mut Graph, &NodeId) -> Result<(), E>,
95{
96    // Code in this method is identical to the code in `nonrecursive_dft`.
97    let mut entered = Vec::new();
98    let mut exited = BTreeSet::new();
99
100    let children = at_enter(graph, &root)?;
101    entered_node(&mut entered, root, children);
102    while !entered.is_empty() {
103        if let Some(to_enter) = find_next_child_to_enter(&mut entered, &exited) {
104            let children = at_enter(graph, &to_enter)?;
105            entered_node(&mut entered, to_enter, children);
106        } else {
107            let (to_exit, _) = entered.pop().unwrap();
108            at_exit(graph, &to_exit)?;
109            exited.insert(to_exit);
110        }
111    }
112    Ok(())
113}
114
115/// A non-recursive implementation of an infallible depth-first traversal
116/// starting from `root`.
117///
118/// Assumes that nodes in the graph all have unique node ids.
119///
120/// `at_enter` runs when entering a node. It is expected to return an in-order
121/// list of the children of the node. You can omit children from the list
122/// returned if you want to skip the traversing subgraphs corresponding to
123/// those children. If no children are omitted, `at_enter` can be thought
124/// of as a function that processes the nodes of the graph in pre-order.
125///
126/// `at_exit` runs when exiting a node. It can be thought of as a function that
127/// processes the nodes of the graph in post-order.
128///
129/// This function only enters and exits a node at most once and thus is safe to
130/// run even if the graph contains a cycle.
131pub fn nonrecursive_dft<Graph, NodeId, AtEnter, AtExit>(
132    graph: &Graph,
133    root: NodeId,
134    at_enter: &mut AtEnter,
135    at_exit: &mut AtExit,
136) where
137    NodeId: std::cmp::Ord,
138    AtEnter: FnMut(&Graph, &NodeId) -> Vec<NodeId>,
139    AtExit: FnMut(&Graph, &NodeId) -> (),
140{
141    // All nodes that have been entered but not exited. Last node in the vec is
142    // the node that we most recently entered.
143    let mut entered = Vec::new();
144    // All nodes that have been exited.
145    let mut exited = BTreeSet::new();
146
147    // Pseudocode for the recursive version of this function would look like:
148    // ```
149    // atenter(graph, node)
150    // foreach child in children(graph, node):
151    //    recursive_call(graph, child)
152    // atexit(graph, node)
153    // ```
154    // In this non-recursive implementation, you can think of the call stack as
155    // been replaced by `entered`. Every time an object is pushed into `entered`
156    // would have been a time you would have pushed a recursive call onto the
157    // call stack. Likewise, times an object is popped from `entered` would have
158    // been times when recursive calls leave the stack.
159
160    // Enter from the root.
161    let children = at_enter(graph, &root);
162    entered_node(&mut entered, root, children);
163    while !entered.is_empty() {
164        if let Some(to_enter) = find_next_child_to_enter(&mut entered, &exited) {
165            let children = at_enter(graph, &to_enter);
166            entered_node(&mut entered, to_enter, children);
167        } else {
168            // If this node has no more children to descend into,
169            // exit the current node and run `at_exit`.
170            let (to_exit, _) = entered.pop().unwrap();
171            at_exit(graph, &to_exit);
172            exited.insert(to_exit);
173        }
174    }
175}
176
177/// Same as [`nonrecursive_dft`], but allows changes to be made to the graph.
178pub fn nonrecursive_dft_mut<Graph, NodeId, AtEnter, AtExit>(
179    graph: &mut Graph,
180    root: NodeId,
181    at_enter: &mut AtEnter,
182    at_exit: &mut AtExit,
183) where
184    NodeId: std::cmp::Ord + Clone,
185    AtEnter: FnMut(&mut Graph, &NodeId) -> Vec<NodeId>,
186    AtExit: FnMut(&mut Graph, &NodeId) -> (),
187{
188    // Code in this method is identical to the code in `nonrecursive_dft`.
189    let mut entered = Vec::new();
190    let mut exited = BTreeSet::new();
191
192    let children = at_enter(graph, &root);
193    entered_node(&mut entered, root, children);
194    while !entered.is_empty() {
195        if let Some(to_enter) = find_next_child_to_enter(&mut entered, &exited) {
196            let children = at_enter(graph, &to_enter);
197            entered_node(&mut entered, to_enter, children);
198        } else {
199            let (to_exit, _) = entered.pop().unwrap();
200            at_exit(graph, &to_exit);
201            exited.insert(to_exit);
202        }
203    }
204}
205
206/// Add to `entered` that we have entered `node` and `node` has `children`.
207fn entered_node<NodeId>(
208    entered: &mut Vec<(NodeId, Vec<NodeId>)>,
209    node: NodeId,
210    mut children: Vec<NodeId>,
211) where
212    NodeId: std::cmp::Ord,
213{
214    // Reverse children because `find_next_child_to_enter` will traverse the
215    // list of children by popping them out from the back.
216    children.reverse();
217    entered.push((node, children))
218}
219
220/// Find the next child node, if any, that we have not entered.
221fn find_next_child_to_enter<NodeId>(
222    entered: &mut Vec<(NodeId, Vec<NodeId>)>,
223    exited: &BTreeSet<NodeId>,
224) -> Option<NodeId>
225where
226    NodeId: std::cmp::Ord,
227{
228    let (_, children) = entered.last_mut().unwrap();
229    while let Some(child) = children.pop() {
230        if !exited.contains(&child) {
231            return Some(child);
232        }
233    }
234    None
235}