Skip to main content

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}