geop_core_math/solvers/walk.rs
1//! Walking a chain: from a start, on to the one next node, and the one
2//! after that, until the chain closes, ends, or runs into itself — a loop
3//! of coedges followed round its face, a tangent chain of edges, the arcs
4//! of one circle, a sketch's curves joined end to end, a pattern's joints
5//! driving each other.
6//!
7//! What comes next is the caller's (`next`, which may refuse with an error
8//! where the chain forks); the walk promises to tell the three ways a chain
9//! can end apart, and to end: no node is walked twice, so a walk over
10//! finitely many nodes ends after at most that many steps, whatever `next`
11//! does. A chain that runs into itself somewhere other than its start is
12//! reported as such, never followed round again.
13//!
14//! [`flood`] is the walk of a graph rather than a chain: from some nodes to
15//! every node reachable from them, each visited once.
16
17use std::{
18 collections::{HashSet, VecDeque},
19 hash::Hash,
20};
21
22/// How a walk ended.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Walked<N> {
25 /// `next` led back to the start: the chain is closed.
26 Closed,
27 /// `next` said there is nothing after the last node: the chain is open.
28 Open,
29 /// `next` led to this node, walked before and not the start: the chain
30 /// runs into a loop not through where it started.
31 Revisited(N),
32}
33
34/// A walk: its nodes in order, the start first, and how it ended.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct Walk<N> {
37 pub nodes: Vec<N>,
38 pub end: Walked<N>,
39}
40
41/// Walks from `start` along `next`: the node after each, or `None` where
42/// there is none. Nodes are told apart by `key`: a node with the key of the
43/// start closes the chain, one with the key of a node walked since runs
44/// into it. `next` is asked once for each node, in order, so it may keep
45/// state of its own.
46pub fn walk_by<N, K: Eq + Hash, E>(
47 start: N,
48 key: impl Fn(&N) -> K,
49 mut next: impl FnMut(&N) -> Result<Option<N>, E>,
50) -> Result<Walk<N>, E> {
51 let first = key(&start);
52 let mut seen = HashSet::new();
53 let mut nodes = vec![start];
54 loop {
55 let Some(after) = next(nodes.last().expect("never empty"))? else {
56 return Ok(Walk {
57 nodes,
58 end: Walked::Open,
59 });
60 };
61 let k = key(&after);
62 if k == first {
63 return Ok(Walk {
64 nodes,
65 end: Walked::Closed,
66 });
67 }
68 if !seen.insert(k) {
69 return Ok(Walk {
70 nodes,
71 end: Walked::Revisited(after),
72 });
73 }
74 nodes.push(after);
75 }
76}
77
78/// [`walk_by`] for nodes that are their own keys.
79pub fn walk<N: Clone + Eq + Hash, E>(
80 start: N,
81 next: impl FnMut(&N) -> Result<Option<N>, E>,
82) -> Result<Walk<N>, E> {
83 walk_by(start, N::clone, next)
84}
85
86/// Which pending node a [`flood`] visits next.
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Take {
89 /// The one handed on last: depth first, a stack.
90 Newest,
91 /// The one handed on first: breadth first, level by level, a queue.
92 Oldest,
93}
94
95/// Visits `starts` and every node reachable from them, each once: `visit`
96/// is asked for each node in turn, and hands on its neighbours, in order;
97/// a node not handed on before joins the pending ones, and which of those
98/// comes next `take` says. Returns the nodes in the order visited.
99///
100/// The order is the starts', `visit`'s and `take`'s alone: what has been
101/// handed on is only ever asked about, never iterated. A flood over
102/// finitely many nodes ends, whatever `visit` hands on. `visit` may keep
103/// state of its own (what it found, what it refused), since each node is
104/// visited once, in order.
105pub fn flood<N: Clone + Eq + Hash, E>(
106 starts: impl IntoIterator<Item = N>,
107 take: Take,
108 mut visit: impl FnMut(&N) -> Result<Vec<N>, E>,
109) -> Result<Vec<N>, E> {
110 let mut seen = HashSet::new();
111 let mut pending = VecDeque::new();
112 for start in starts {
113 if seen.insert(start.clone()) {
114 pending.push_back(start);
115 }
116 }
117 let mut visited = Vec::new();
118 while let Some(node) = match take {
119 Take::Newest => pending.pop_back(),
120 Take::Oldest => pending.pop_front(),
121 } {
122 for next in visit(&node)? {
123 if seen.insert(next.clone()) {
124 pending.push_back(next);
125 }
126 }
127 visited.push(node);
128 }
129 Ok(visited)
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 /// Following `next` in a list of successors: `None` for a node with
137 /// none.
138 fn follow(successor: &[Option<usize>], start: usize) -> Walk<usize> {
139 walk::<_, ()>(start, |&n| Ok(successor[n])).unwrap()
140 }
141
142 #[test]
143 fn a_ring_closes() {
144 let w = follow(&[Some(1), Some(2), Some(0)], 1);
145 assert_eq!((w.nodes, w.end), (vec![1, 2, 0], Walked::Closed));
146 }
147
148 #[test]
149 fn a_chain_ends() {
150 let w = follow(&[Some(1), Some(2), None], 0);
151 assert_eq!((w.nodes, w.end), (vec![0, 1, 2], Walked::Open));
152 }
153
154 /// A chain running into a loop not through its start is reported where
155 /// it does, not followed round the loop for ever.
156 #[test]
157 fn running_into_a_loop_is_reported() {
158 let w = follow(&[Some(1), Some(2), Some(3), Some(1)], 0);
159 assert_eq!((w.nodes, w.end), (vec![0, 1, 2, 3], Walked::Revisited(1)));
160 }
161
162 /// Nodes are told apart by their keys: a node with the start's key
163 /// closes the chain whatever else it carries, and `next` is asked once
164 /// per node.
165 #[test]
166 fn nodes_are_told_apart_by_key() {
167 let mut asked = 0;
168 let w = walk_by::<_, _, ()>(
169 (0, 'a'),
170 |&(k, _)| k,
171 |&(k, _)| {
172 asked += 1;
173 Ok(Some(((k + 1) % 3, 'b')))
174 },
175 )
176 .unwrap();
177 assert_eq!(w.nodes, [(0, 'a'), (1, 'b'), (2, 'b')]);
178 assert_eq!((w.end, asked), (Walked::Closed, 3));
179 }
180
181 /// The graph 0-1, 0-2, 1-3, 2-3, and 4 on its own.
182 fn neighbours(n: &usize) -> Result<Vec<usize>, ()> {
183 Ok(match n {
184 0 => vec![1, 2],
185 1 => vec![0, 3],
186 2 => vec![0, 3],
187 3 => vec![1, 2],
188 _ => vec![],
189 })
190 }
191
192 /// Every node reachable is visited once, in the order `take` says: a
193 /// queue level by level, a stack the newest first.
194 #[test]
195 fn a_flood_visits_what_is_reachable_once() {
196 assert_eq!(flood([0], Take::Oldest, neighbours), Ok(vec![0, 1, 2, 3]));
197 assert_eq!(flood([0], Take::Newest, neighbours), Ok(vec![0, 2, 3, 1]));
198 assert_eq!(
199 flood([4, 0, 4], Take::Oldest, neighbours),
200 Ok(vec![4, 0, 1, 2, 3])
201 );
202 }
203
204 /// An error from a visit ends the flood with it.
205 #[test]
206 fn an_error_ends_the_flood() {
207 let out = flood([0usize], Take::Oldest, |&n| {
208 if n == 2 {
209 Err("no")
210 } else {
211 neighbours(&n).map_err(|_| "")
212 }
213 });
214 assert_eq!(out, Err("no"));
215 }
216
217 /// An error from `next` ends the walk with it.
218 #[test]
219 fn a_fork_refused_by_next_ends_the_walk() {
220 let w = walk::<usize, &str>(0, |&n| if n < 2 { Ok(Some(n + 1)) } else { Err("forks") });
221 assert_eq!(w, Err("forks"));
222 }
223}