backup commit

This commit is contained in:
2022-10-24 03:16:04 +01:00
parent fbbd6ed256
commit 778c87db77
43 changed files with 1156 additions and 174 deletions

113
src/utils/bfs.rs Normal file
View File

@@ -0,0 +1,113 @@
use std::collections::{VecDeque, HashSet};
use std::iter;
use std::hash::Hash;
use crate::unwrap_or;
use crate::utils::BoxedIter;
/// Two-stage breadth-first search;
/// Instead of enumerating neighbors before returning a node, it puts visited but not yet
/// enumerated nodes in a separate queue and only enumerates them to refill the queue of children
/// one by one once it's empty. This method is preferable for generated graphs because it doesn't
/// allocate memory for the children until necessary, but it's also probably a bit slower since
/// it involves additional processing.
///
/// # Performance
/// `T` is cloned twice for each returned value.
pub fn bfs<T, F, I>(init: T, neighbors: F)
-> impl Iterator<Item = T>
where T: Eq + Hash + Clone + std::fmt::Debug,
F: Fn(T) -> I, I: Iterator<Item = T>
{
let mut visited: HashSet<T> = HashSet::new();
let mut visit_queue: VecDeque<T> = VecDeque::from([init]);
let mut unpack_queue: VecDeque<T> = VecDeque::new();
iter::from_fn(move || {
let next = {loop {
let next = unwrap_or!(visit_queue.pop_front(); break None);
if !visited.contains(&next) { break Some(next) }
}}.or_else(|| loop {
let unpacked = unwrap_or!(unpack_queue.pop_front(); break None);
let mut nbv = neighbors(unpacked).filter(|t| !visited.contains(t));
if let Some(next) = nbv.next() {
visit_queue.extend(nbv);
break Some(next)
}
})?;
visited.insert(next.clone());
unpack_queue.push_back(next.clone());
Some(next)
})
}
/// Same as [bfs] but with a recursion depth limit
///
/// The main intent is to effectively walk infinite graphs of unknown breadth without making the
/// recursion depth dependent on the number of nodes. If predictable runtime is more important
/// than predictable depth, [bfs] with [std::iter::Iterator::take] should be used instead
pub fn bfs_upto<'a, T: 'a, F: 'a, I: 'a>(init: T, neighbors: F, limit: usize)
-> impl Iterator<Item = T> + 'a
where T: Eq + Hash + Clone + std::fmt::Debug,
F: Fn(T) -> I, I: Iterator<Item = T>
{
/// Newtype to store the recursion depth but exclude it from equality comparisons
/// Because BFS visits nodes in increasing distance order, when a node is visited for the
/// second time it will never override the earlier version of itself. This is not the case
/// with Djikstra's algorithm, which can be conceptualised as a "weighted BFS".
#[derive(Eq, Clone, Debug)]
struct Wrap<U>(usize, U);
impl<U: PartialEq> PartialEq for Wrap<U> {
fn eq(&self, other: &Self) -> bool { self.1.eq(&other.1) }
}
impl<U: Hash> Hash for Wrap<U> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state) }
}
bfs(Wrap(0, init), move |Wrap(dist, t)| -> BoxedIter<Wrap<T>> { // boxed because we branch
if dist == limit {Box::new(iter::empty())}
else {Box::new(neighbors(t).map(move |t| Wrap(dist + 1, t)))}
}).map(|Wrap(_, t)| t)
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use super::*;
type Graph = Vec<Vec<usize>>;
fn neighbors(graph: &Graph, pt: usize) -> impl Iterator<Item = usize> + '_ {
graph[pt].iter().copied()
}
fn from_neighborhood_matrix(matrix: Vec<Vec<usize>>) -> Graph {
matrix.into_iter().map(|v| {
v.into_iter().enumerate().filter_map(|(i, ent)| {
if ent > 1 {panic!("Neighborhood matrices must contain binary values")}
else if ent == 1 {Some(i)}
else {None}
}).collect()
}).collect()
}
#[test]
fn test_square() {
let simple_graph = from_neighborhood_matrix(vec![
vec![0,1,0,1,1,0,0,0],
vec![1,0,1,0,0,1,0,0],
vec![0,1,0,1,0,0,1,0],
vec![1,0,1,0,0,0,0,1],
vec![1,0,0,0,0,1,0,1],
vec![0,1,0,0,1,0,1,0],
vec![0,0,1,0,0,1,0,1],
vec![0,0,0,1,1,0,1,0],
]);
let scan = bfs(0, |n| neighbors(&simple_graph, n)).collect_vec();
assert_eq!(scan, vec![0, 1, 3, 4, 2, 5, 7, 6])
}
#[test]
fn test_stringbuilder() {
let scan = bfs("".to_string(), |s| {
vec![s.clone()+";", s.clone()+"a", s+"aaa"].into_iter()
}).take(30).collect_vec();
println!("{scan:?}")
}
}