blob: 64c191470a3be91207f205b1ebf228f086a2d74f [file]
// Copyright © 2025 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::bitset::BitSet;
/// A trait implementing a depth-first search over a graph
pub trait DepthFirstSearch {
type ChildIter;
/// Pre-visit a node. This is called before any children or edges are
/// visited. Returns an iterator to this node's children in the graph.
fn pre(&mut self, id: usize) -> Self::ChildIter;
/// Visit an edge. An edge is visited before the child at the end of that
/// edge is pre-visited. Every edge is visited, even if if the node this
/// edge points to has already been visited.
fn edge(&mut self, _parent: usize, _child: usize) {
// Does nothing by default
}
/// Post-visit a node. This is called after all the children have been
/// visited.
fn post(&mut self, _id: usize) {
// Does nothing by default
}
}
struct DFSEntry<I: Iterator<Item = usize>> {
id: usize,
children: I,
}
pub fn dfs<I, D>(dfs: &mut D, start: usize)
where
I: Iterator<Item = usize>,
D: DepthFirstSearch<ChildIter = I>,
{
let mut seen = BitSet::new();
let mut stack = Vec::new();
seen.insert(start);
let children = dfs.pre(start);
stack.push(DFSEntry {
id: start,
children,
});
loop {
let Some(entry) = stack.last_mut() else {
break;
};
loop {
if let Some(id) = entry.children.next() {
dfs.edge(entry.id, id);
if seen.contains(id) {
continue;
}
seen.insert(id);
let children = dfs.pre(id);
stack.push(DFSEntry { id, children });
} else {
dfs.post(entry.id);
stack.pop();
}
// We're only looping here because we want to make the
// seen.contains() case a fast path. Both the case where we
// push the stack and recurse into a child or when we pop the
// stack want to continue in the next iteration of the outer
// loop.
break;
}
}
}