use crate::id::NodeId;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct VisitSet {
seen: BTreeSet<NodeId>,
}
impl VisitSet {
#[must_use]
pub const fn new() -> Self {
Self {
seen: BTreeSet::new(),
}
}
pub fn mark(&mut self, node: NodeId) -> bool {
self.seen.insert(node)
}
#[must_use]
pub fn contains(&self, node: NodeId) -> bool {
self.seen.contains(&node)
}
pub fn clear(&mut self) {
self.seen.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.seen.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.seen.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = NodeId> + '_ {
self.seen.iter().copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WalkOrder {
PreOrder,
PostOrder,
}
pub fn walk_graph<F>(roots: &[NodeId], successors: F, order: WalkOrder) -> Vec<NodeId>
where
F: Fn(NodeId) -> Vec<NodeId>,
{
enum Step {
Enter(NodeId),
Emit(NodeId),
}
let mut seen = VisitSet::new();
let mut out = Vec::new();
let mut stack: Vec<Step> = Vec::new();
for root in roots.iter().rev() {
stack.push(Step::Enter(*root));
}
while let Some(step) = stack.pop() {
match step {
Step::Emit(node) => out.push(node),
Step::Enter(node) => {
if !seen.mark(node) {
continue;
}
match order {
WalkOrder::PreOrder => out.push(node),
WalkOrder::PostOrder => stack.push(Step::Emit(node)),
}
for successor in successors(node).into_iter().rev() {
stack.push(Step::Enter(successor));
}
}
}
}
out
}
pub fn reachable<F>(roots: &[NodeId], successors: F) -> VisitSet
where
F: Fn(NodeId) -> Vec<NodeId>,
{
let mut set = VisitSet::new();
for node in walk_graph(roots, successors, WalkOrder::PreOrder) {
let _ = set.mark(node);
}
set
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
use crate::id::IdSpace;
use alloc::vec;
use alloc::vec::Vec;
fn n(counter: u64) -> NodeId {
NodeId::from_parts(IdSpace::MODEL, counter)
}
#[test]
fn mark_reports_first_visit_only() {
let mut seen = VisitSet::new();
assert!(seen.mark(n(1)));
assert!(!seen.mark(n(1)));
assert!(seen.contains(n(1)));
assert_eq!(seen.len(), 1);
seen.clear();
assert!(seen.is_empty());
}
#[test]
fn a_self_referential_node_terminates() {
let successors = |id: NodeId| {
if id == n(1) { vec![n(1)] } else { Vec::new() }
};
let visited = walk_graph(&[n(1)], successors, WalkOrder::PreOrder);
assert_eq!(visited, vec![n(1)]);
}
#[test]
fn a_mutual_cycle_terminates_and_visits_each_node_once() {
let successors = |id: NodeId| match id.counter() {
1 => vec![n(2)],
2 => vec![n(3)],
3 => vec![n(1)],
_ => Vec::new(),
};
let visited = walk_graph(&[n(1)], successors, WalkOrder::PreOrder);
assert_eq!(visited, vec![n(1), n(2), n(3)]);
}
#[test]
fn post_order_emits_children_before_parents() {
let successors = |id: NodeId| {
if id == n(1) {
vec![n(2), n(3)]
} else {
Vec::new()
}
};
let visited = walk_graph(&[n(1)], successors, WalkOrder::PostOrder);
assert_eq!(visited, vec![n(2), n(3), n(1)]);
}
#[test]
fn multiple_roots_are_all_walked_without_duplicates() {
let successors = |id: NodeId| match id.counter() {
1 | 2 => vec![n(3)],
_ => Vec::new(),
};
let visited = walk_graph(&[n(1), n(2)], successors, WalkOrder::PreOrder);
assert_eq!(visited, vec![n(1), n(3), n(2)]);
}
#[test]
fn reachable_collects_the_transitive_closure() {
let successors = |id: NodeId| match id.counter() {
1 => vec![n(2)],
2 => vec![n(3)],
3 => vec![n(1)],
_ => Vec::new(),
};
let set = reachable(&[n(1)], successors);
let all: Vec<NodeId> = set.iter().collect();
assert_eq!(all, vec![n(1), n(2), n(3)]);
}
#[test]
fn a_deep_chain_does_not_depend_on_stack_depth() {
let successors = |id: NodeId| {
let next = id.counter() + 1;
if next <= 100_000 {
vec![n(next)]
} else {
Vec::new()
}
};
let visited = walk_graph(&[n(1)], successors, WalkOrder::PreOrder);
assert_eq!(visited.len(), 100_000);
}
}