bug fixes and performance improvements
This commit is contained in:
115
src/utils/bfs.rs
115
src/utils/bfs.rs
@@ -1,115 +0,0 @@
|
||||
use std::collections::{VecDeque, HashSet};
|
||||
use std::iter;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::unwrap_or;
|
||||
use crate::utils::BoxedIter;
|
||||
|
||||
// TODO: move to own crate
|
||||
|
||||
/// 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:?}")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::{hash::Hash, cell::RefCell, rc::Rc};
|
||||
use std::cell::RefCell;
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
// TODO: make this a crate
|
||||
@@ -20,6 +22,7 @@ impl<'a, I, O> Cache<'a, I, O> where
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rc<F: 'a>(closure: F) -> Rc<Self> where F: Fn(I, &Self) -> O {
|
||||
Rc::new(Self::new(closure))
|
||||
}
|
||||
@@ -44,4 +47,27 @@ impl<'a, I, O> Cache<'a, I, O> where
|
||||
let store = self.store.borrow();
|
||||
store.get(i).cloned()
|
||||
}
|
||||
|
||||
|
||||
/// Convert this cache into a cached [Fn(&I) -> O]
|
||||
#[allow(unused)]
|
||||
pub fn into_fn(self) -> impl Fn(&I) -> O + 'a where I: 'a {
|
||||
move |i| self.find(i)
|
||||
}
|
||||
|
||||
/// Borrow this cache with a cached [Fn(&I) -> O]
|
||||
#[allow(unused)]
|
||||
pub fn as_fn<'b: 'a>(&'b self) -> impl Fn(&I) -> O + 'b where I: 'b {
|
||||
move |i| self.find(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, O> IntoIterator for Cache<'a, I, O> {
|
||||
type IntoIter = hashbrown::hash_map::IntoIter<I, O>;
|
||||
type Item = (I, O);
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let Cache{ store, .. } = self;
|
||||
let map = store.into_inner();
|
||||
map.into_iter()
|
||||
}
|
||||
}
|
||||
0
src/utils/coprefix.rs
Normal file
0
src/utils/coprefix.rs
Normal file
@@ -1,19 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use lasso::RodeoResolver;
|
||||
|
||||
pub trait InternedDisplay {
|
||||
fn fmt(&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
rr: RodeoResolver
|
||||
) -> std::fmt::Result;
|
||||
}
|
||||
|
||||
impl<T> InternedDisplay for T where T: Display {
|
||||
fn fmt(&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
rr: RodeoResolver
|
||||
) -> std::fmt::Result {
|
||||
<Self as Display>::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
181
src/utils/interner.rs
Normal file
181
src/utils/interner.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use std::sync::{Mutex, Arc};
|
||||
use std::num::NonZeroU32;
|
||||
use std::hash::Hash;
|
||||
|
||||
use lasso::{Rodeo, Spur, Key};
|
||||
use base64::{engine::general_purpose::STANDARD_NO_PAD as BASE64, Engine};
|
||||
|
||||
/// A token representing an interned string or sequence in an interner
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Token<const RANK: u8>(pub Spur);
|
||||
|
||||
/// An interner that can intern strings, and sequences of things it
|
||||
/// interned as long as they have the same rank
|
||||
pub trait Interner: Sync {
|
||||
fn str2tok(&self, str: &str) -> Token<String>;
|
||||
fn tok2str(&self, token: Token<String>) -> String;
|
||||
fn slc2tok<const RANK: u8>(&self, slice: &[Token<RANK>]) -> Token<{RANK + 1}>;
|
||||
fn tok2slc<const RANK: u8>(&self, token: Token<RANK>) -> Vec<Token<{RANK - 1}>>;
|
||||
fn tok2strv(&self, token: Token<Vec<Token<String>>>) -> Vec<String> {
|
||||
self.tok2slc(token).into_iter().map(|t| self.tok2str(t)).collect()
|
||||
}
|
||||
fn tokv2strv(&self, slice: &[Token<String>]) -> Vec<String> {
|
||||
slice.iter().map(|t| self.tok2str(*t)).collect()
|
||||
}
|
||||
/// Get the first token of a sequence
|
||||
fn head<const RANK: u8>(&self, token: Token<RANK>) -> Token<{RANK - 1}>;
|
||||
/// Returns the length of a sequence
|
||||
fn len<const RANK: u8>(&self, token: Token<RANK>) -> usize
|
||||
where Token<{RANK - 1}>: Clone;
|
||||
/// Returns the length of the longest identical prefix of the two sequences
|
||||
fn coprefix<const RANK: u8>(&self, a: Token<RANK>, b: Token<RANK>) -> usize
|
||||
where Token<{RANK - 1}>: Clone;
|
||||
}
|
||||
|
||||
fn serialize_seq<const RANK: u8>(seq: &[Token<RANK>]) -> String {
|
||||
let data: Vec<u8> = seq.iter()
|
||||
.map(|t| u32::from(t.0.into_inner()).to_le_bytes().into_iter())
|
||||
.flatten()
|
||||
.collect();
|
||||
BASE64.encode(data)
|
||||
}
|
||||
|
||||
fn deserialize_seq<const RANK: u8>(string: &str) -> Vec<Token<RANK>> {
|
||||
let data = BASE64.decode(string)
|
||||
.expect("String is not valid base64");
|
||||
assert!(data.len() % 4 == 0, "Tokens should serialize to 3 bytes each");
|
||||
data.array_chunks::<4>().map(|a| {
|
||||
let bytes = [a[0], a[1], a[2], a[3]];
|
||||
let nz32 = NonZeroU32::new(u32::from_le_bytes(bytes))
|
||||
.expect("Token representation should never be zero");
|
||||
Token(Spur::try_from_usize(u32::from(nz32) as usize).unwrap())
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// An interner that delegates the actual work to Lasso
|
||||
#[derive(Clone)]
|
||||
pub struct LassoInterner {
|
||||
strings: Arc<Mutex<Rodeo>>,
|
||||
slices: Arc<Mutex<Rodeo>>
|
||||
}
|
||||
|
||||
impl LassoInterner {
|
||||
/// Create an empty interner. Called to create the singleton.
|
||||
fn new() -> Self {
|
||||
Self{
|
||||
slices: Arc::new(Mutex::new(Rodeo::new())),
|
||||
strings: Arc::new(Mutex::new(Rodeo::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Interner for LassoInterner {
|
||||
fn str2tok(&self, str: &str) -> Token<String> {
|
||||
let mut strings = self.strings.lock().unwrap();
|
||||
let key = strings.get_or_intern(str);
|
||||
Token(key)
|
||||
}
|
||||
|
||||
fn tok2str<'a>(&'a self, token: Token<String>) -> String {
|
||||
let key = token.0;
|
||||
let strings = self.strings.lock().unwrap();
|
||||
strings.resolve(&key).to_string()
|
||||
}
|
||||
|
||||
fn slc2tok<const RANK: u8>(&self, slice: &[Token<RANK>]) -> Token<{RANK + 1}> {
|
||||
let data = serialize_seq(slice);
|
||||
let mut slices = self.slices.lock().unwrap();
|
||||
let key = slices.get_or_intern(data);
|
||||
Token(key)
|
||||
}
|
||||
|
||||
fn tok2slc<'a, const RANK: u8>(&'a self, token: Token<RANK>) -> Vec<Token<{RANK - 1}>> {
|
||||
let key = token.0;
|
||||
let slices = self.slices.lock().unwrap();
|
||||
let string = slices.resolve(&key);
|
||||
deserialize_seq(string)
|
||||
}
|
||||
|
||||
fn head<const RANK: u8>(&self, token: Token<RANK>) -> Token<{RANK - 1}> {
|
||||
let key = token.0;
|
||||
let slices = self.slices.lock().unwrap();
|
||||
let string = slices.resolve(&key);
|
||||
deserialize_seq(&string[0..5])[0]
|
||||
}
|
||||
|
||||
fn len<const RANK: u8>(&self, token: Token<RANK>) -> usize where Token<{RANK - 1}>: Clone {
|
||||
let key = token.0;
|
||||
let slices = self.slices.lock().unwrap();
|
||||
let string = slices.resolve(&key);
|
||||
assert!(string.len() % 4 == 0, "Tokens should serialize to 3 characters");
|
||||
string.len() / 4
|
||||
}
|
||||
|
||||
fn coprefix<const RANK: u8>(&self, a: Token<RANK>, b: Token<RANK>) -> usize where Token<{RANK - 1}>: Clone {
|
||||
let keya = a.0;
|
||||
let keyb = b.0;
|
||||
let slices = self.slices.lock().unwrap();
|
||||
let sa = slices.resolve(&keya);
|
||||
let sb = slices.resolve(&keyb);
|
||||
sa.bytes()
|
||||
.zip(sb.bytes())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count() / 4
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an interner that inherits the singleton's data, and
|
||||
/// block all future interaction with the singleton.
|
||||
///
|
||||
/// DO NOT call within [dynamic] or elsewhere pre-main
|
||||
pub fn mk_interner() -> impl Interner {
|
||||
LassoInterner::new()
|
||||
}
|
||||
|
||||
pub trait StringLike: Clone + Eq + Hash {
|
||||
fn into_str(self, i: &Interner) -> String;
|
||||
fn into_tok(self, i: &Interner) -> Token<String>;
|
||||
}
|
||||
|
||||
impl StringLike for String {
|
||||
fn into_str(self, _i: &Interner) -> String {self}
|
||||
fn into_tok(self, i: &Interner) -> Token<String> {i.str2tok(&self)}
|
||||
}
|
||||
|
||||
impl StringLike for Token<String> {
|
||||
fn into_str(self, i: &Interner) -> String {i.tok2str(self)}
|
||||
fn into_tok(self, _i: &Interner) -> Token<String> {self}
|
||||
}
|
||||
|
||||
pub trait StringVLike: Clone + Eq + Hash {
|
||||
fn into_strv(self, i: &Interner) -> Vec<String>;
|
||||
fn into_tok(self, i: &Interner) -> Token<Vec<Token<String>>>;
|
||||
fn into_tokv(self, i: &Interner) -> Vec<Token<String>>;
|
||||
}
|
||||
|
||||
impl StringVLike for Vec<String> {
|
||||
fn into_strv(self, _i: &Interner) -> Vec<String> {self}
|
||||
fn into_tok(self, i: &Interner) -> Token<Vec<Token<String>>> {
|
||||
let tokv = self.into_iter()
|
||||
.map(|s| i.str2tok(&s))
|
||||
.collect::<Vec<_>>();
|
||||
i.slc2tok(&tokv)
|
||||
}
|
||||
fn into_tokv(self, i: &Interner) -> Vec<Token<String>> {
|
||||
self.into_iter()
|
||||
.map(|s| i.str2tok(&s))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl StringVLike for Vec<Token<String>> {
|
||||
fn into_strv(self, i: &Interner) -> Vec<String> {i.tokv2strv(&self)}
|
||||
fn into_tok(self, i: &Interner) -> Token<Vec<Token<String>>> {i.slc2tok(&self)}
|
||||
fn into_tokv(self, _i: &Interner) -> Vec<Token<String>> {self}
|
||||
}
|
||||
|
||||
impl StringVLike for Token<Vec<Token<String>>> {
|
||||
fn into_strv(self, i: &Interner) -> Vec<String> {i.tok2strv(self)}
|
||||
fn into_tok(self, _i: &Interner) -> Token<Vec<Token<String>>> {self}
|
||||
fn into_tokv(self, i: &Interner) -> Vec<Token<String>> {i.tok2slc(self)}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/// Utility functions to get rid of explicit casts to BoxedIter which are tedious
|
||||
|
||||
use std::{iter, mem};
|
||||
use std::iter;
|
||||
|
||||
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
||||
pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>;
|
||||
|
||||
@@ -1,87 +1,26 @@
|
||||
mod cache;
|
||||
pub mod translate;
|
||||
mod replace_first;
|
||||
mod interned_display;
|
||||
pub use interned_display::InternedDisplay;
|
||||
// mod visitor;
|
||||
// mod interned_display;
|
||||
// mod interner;
|
||||
mod variant;
|
||||
mod print_nname;
|
||||
mod pushed;
|
||||
pub use pushed::pushed;
|
||||
pub use print_nname::{print_nname_seq, print_nname};
|
||||
// pub use interner::*;
|
||||
// pub use interned_display::InternedDisplay;
|
||||
pub use replace_first::replace_first;
|
||||
pub use cache::Cache;
|
||||
mod substack;
|
||||
pub use substack::Stackframe;
|
||||
pub use substack::{Stackframe, Substack, SubstackIterator};
|
||||
mod side;
|
||||
pub use side::Side;
|
||||
mod unwrap_or;
|
||||
pub mod iter;
|
||||
pub use iter::BoxedIter;
|
||||
mod bfs;
|
||||
mod string_from_charset;
|
||||
pub use string_from_charset::string_from_charset;
|
||||
mod xloop;
|
||||
mod protomap;
|
||||
pub use protomap::ProtoMap;
|
||||
mod product2;
|
||||
pub use product2::Product2;
|
||||
|
||||
use mappable_rc::Mrc;
|
||||
|
||||
pub fn mrc_derive<T: ?Sized, P, U: ?Sized>(m: &Mrc<T>, p: P) -> Mrc<U>
|
||||
where P: for<'a> FnOnce(&'a T) -> &'a U {
|
||||
Mrc::map(Mrc::clone(m), p)
|
||||
}
|
||||
|
||||
pub fn mrc_try_derive<T: ?Sized, P, U: ?Sized>(m: &Mrc<T>, p: P) -> Option<Mrc<U>>
|
||||
where P: for<'a> FnOnce(&'a T) -> Option<&'a U> {
|
||||
Mrc::try_map(Mrc::clone(m), p).ok()
|
||||
}
|
||||
|
||||
pub fn mrc_empty_slice<T>() -> Mrc<[T]> {
|
||||
mrc_derive_slice(&Mrc::new(Vec::new()))
|
||||
}
|
||||
|
||||
pub fn to_mrc_slice<T>(v: Vec<T>) -> Mrc<[T]> {
|
||||
Mrc::map(Mrc::new(v), |v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn collect_to_mrc<I>(iter: I) -> Mrc<[I::Item]> where I: Iterator {
|
||||
to_mrc_slice(iter.collect())
|
||||
}
|
||||
|
||||
pub fn mrc_derive_slice<T>(mv: &Mrc<Vec<T>>) -> Mrc<[T]> {
|
||||
mrc_derive(mv, |v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn one_mrc_slice<T>(t: T) -> Mrc<[T]> {
|
||||
Mrc::map(Mrc::new([t; 1]), |v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn mrc_to_iter<T>(ms: Mrc<[T]>) -> impl Iterator<Item = Mrc<T>> {
|
||||
let mut i = 0;
|
||||
std::iter::from_fn(move || if i < ms.len() {
|
||||
let out = Some(mrc_derive(&ms, |s| &s[i]));
|
||||
i += 1;
|
||||
out
|
||||
} else {None})
|
||||
}
|
||||
|
||||
pub fn mrc_unnest<T>(m: &Mrc<Mrc<T>>) -> Mrc<T> {
|
||||
Mrc::clone(m.as_ref())
|
||||
}
|
||||
|
||||
pub fn mrc_slice_to_only<T>(m: Mrc<[T]>) -> Result<Mrc<T>, ()> {
|
||||
Mrc::try_map(m, |slice| {
|
||||
if slice.len() != 1 {None}
|
||||
else {Some(&slice[0])}
|
||||
}).map_err(|_| ())
|
||||
}
|
||||
|
||||
pub fn mrc_slice_to_only_option<T>(m: Mrc<[T]>) -> Result<Option<Mrc<T>>, ()> {
|
||||
if m.len() > 1 {return Err(())}
|
||||
Ok(Mrc::try_map(m, |slice| {
|
||||
if slice.len() == 0 {None}
|
||||
else {Some(&slice[0])}
|
||||
}).ok())
|
||||
}
|
||||
|
||||
pub fn mrc_concat<T: Clone>(a: &Mrc<[T]>, b: &Mrc<[T]>) -> Mrc<[T]> {
|
||||
collect_to_mrc(a.iter().chain(b.iter()).cloned())
|
||||
}
|
||||
pub use protomap::ProtoMap;
|
||||
16
src/utils/print_nname.rs
Normal file
16
src/utils/print_nname.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::interner::{Interner, Token};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn print_nname(t: Token<Vec<Token<String>>>, i: &Interner) -> String {
|
||||
i.r(t).iter().map(|t| i.r(*t)).join("::")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn print_nname_seq<'a>(
|
||||
tv: impl Iterator<Item = &'a Token<Vec<Token<String>>>>,
|
||||
i: &Interner
|
||||
) -> String {
|
||||
tv.map(|t| print_nname(*t, i)).join(", ")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use super::Side;
|
||||
|
||||
/// The output of a two-part algorithm. The values are
|
||||
///
|
||||
/// - [Product2::Left] or [Product2::Right] if one of the arguments is the product
|
||||
/// - [Product2::Either] if the arguments are identical
|
||||
/// - [Product2::New] if the product is a different value from either
|
||||
pub enum Product2<T> {
|
||||
Left,
|
||||
Right,
|
||||
#[allow(unused)]
|
||||
Either,
|
||||
#[allow(unused)]
|
||||
New(T)
|
||||
}
|
||||
impl<T> Product2<T> {
|
||||
/// Convert the product into a concrete value by providing the original arguments
|
||||
pub fn pick(self, left: T, right: T) -> T {
|
||||
match self {
|
||||
Self::Left | Self::Either => left,
|
||||
Self::Right => right,
|
||||
Self::New(t) => t
|
||||
}
|
||||
}
|
||||
|
||||
/// Combine some subresults into a tuple representing a greater result
|
||||
pub fn join<U>(
|
||||
self, (lt, rt): (T, T),
|
||||
second: Product2<U>, (lu, ru): (U, U)
|
||||
) -> Product2<(T, U)> {
|
||||
match (self, second) {
|
||||
(Self::Either, Product2::Either) => Product2::Either,
|
||||
(Self::Left | Self::Either, Product2::Left | Product2::Either) => Product2::Left,
|
||||
(Self::Right | Self::Either, Product2::Right | Product2::Either) => Product2::Right,
|
||||
(t, u) => Product2::New((t.pick(lt, rt), u.pick(lu, ru)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate results back into the type of the original problem.
|
||||
pub fn map<A, F: FnOnce(T) -> A>(self, f: F) -> Product2<A> {
|
||||
match self {
|
||||
Product2::Left => Product2::Left, Product2::Right => Product2::Right,
|
||||
Product2::Either => Product2::Either,
|
||||
Product2::New(t) => Product2::New(f(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Technically very different but sometimes neecessary to translate
|
||||
impl<T> From<Side> for Product2<T> {
|
||||
fn from(value: Side) -> Self {match value {
|
||||
Side::Left => Self::Left,
|
||||
Side::Right => Self::Right
|
||||
}}
|
||||
}
|
||||
8
src/utils/pushed.rs
Normal file
8
src/utils/pushed.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
/// Create a new vector consisting of the provided vector with the
|
||||
/// element appended
|
||||
pub fn pushed<T: Clone>(vec: &Vec<T>, t: T) -> Vec<T> {
|
||||
let mut next = Vec::with_capacity(vec.len() + 1);
|
||||
next.extend_from_slice(&vec[..]);
|
||||
next.push(t);
|
||||
next
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::BoxedIter;
|
||||
|
||||
/// A primitive for encoding the two sides Left and Right. While booleans
|
||||
/// are technically usable for this purpose, they're less descriptive.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -55,4 +57,36 @@ impl Side {
|
||||
Side::Right => (opposite, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Produces an increasing sequence on Right, and a decreasing sequence
|
||||
/// on Left
|
||||
pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I)
|
||||
-> BoxedIter<'a, I::Item>
|
||||
{
|
||||
match self {
|
||||
Side::Right => Box::new(iter) as BoxedIter<I::Item>,
|
||||
Side::Left => Box::new(iter.rev()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use itertools::Itertools;
|
||||
use super::*;
|
||||
|
||||
/// I apparently have a tendency to mix these up so it's best if
|
||||
/// the sides are explicitly stated
|
||||
#[test]
|
||||
fn test_walk() {
|
||||
assert_eq!(
|
||||
Side::Right.walk(0..4).collect_vec(),
|
||||
vec![0, 1, 2, 3],
|
||||
"can walk a range"
|
||||
);
|
||||
assert_eq!(
|
||||
Side::Left.walk(0..4).collect_vec(),
|
||||
vec![3, 2, 1, 0],
|
||||
"can walk a range backwards"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
|
||||
// TODO: extract to crate
|
||||
@@ -8,71 +9,60 @@ use std::fmt::Debug;
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stackframe<'a, T> {
|
||||
pub item: T,
|
||||
pub prev: Option<&'a Stackframe<'a, T>>,
|
||||
pub prev: &'a Substack<'a, T>,
|
||||
pub len: usize
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> Stackframe<'a, T> {
|
||||
pub fn new(item: T) -> Self {
|
||||
Self {
|
||||
item,
|
||||
prev: None,
|
||||
len: 1
|
||||
}
|
||||
}
|
||||
/// Get the item owned by this listlike, very fast O(1)
|
||||
pub fn item(&self) -> &T { &self.item }
|
||||
/// Get the next link in the list, very fast O(1)
|
||||
pub fn prev(&self) -> Option<&'a Stackframe<T>> { self.prev }
|
||||
/// Construct an iterator over the listlike, very fast O(1)
|
||||
pub fn iter(&self) -> StackframeIterator<T> {
|
||||
StackframeIterator { curr: Some(self) }
|
||||
}
|
||||
pub fn push(&self, item: T) -> Stackframe<'_, T> {
|
||||
Stackframe {
|
||||
item,
|
||||
prev: Some(self),
|
||||
len: self.len + 1
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn opush(prev: Option<&'a Self>, item: T) -> Self {
|
||||
Self {
|
||||
item,
|
||||
prev,
|
||||
len: prev.map_or(1, |s| s.len)
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn len(&self) -> usize { self.len }
|
||||
#[allow(unused)]
|
||||
pub fn pop(&self, count: usize) -> Option<&Self> {
|
||||
if count == 0 {Some(self)}
|
||||
else {self.prev.expect("Index out of range").pop(count - 1)}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn opop(cur: Option<&Self>, count: usize) -> Option<&Self> {
|
||||
if count == 0 {cur}
|
||||
else {Self::opop(cur.expect("Index out of range").prev, count - 1)}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn o_into_iter(curr: Option<&Self>) -> StackframeIterator<T> {
|
||||
StackframeIterator { curr }
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Substack<'a, T> {
|
||||
Frame(Stackframe<'a, T>),
|
||||
Bottom
|
||||
}
|
||||
|
||||
impl<'a, T> Debug for Stackframe<'a, T> where T: Debug {
|
||||
impl<'a, T> Substack<'a, T> {
|
||||
/// Convert the substack into an option of stackframe
|
||||
pub fn opt(&'a self) -> Option<&'a Stackframe<'a, T>> { match self {
|
||||
Self::Frame(f) => Some(f),
|
||||
Self::Bottom => None
|
||||
} }
|
||||
/// Construct an iterator over the listlike, very fast O(1)
|
||||
pub fn iter(&self) -> SubstackIterator<T> {
|
||||
SubstackIterator { curr: self }
|
||||
}
|
||||
pub fn push(&'a self, item: T) -> Self {
|
||||
Self::Frame(self.new_frame(item))
|
||||
}
|
||||
pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> {
|
||||
Stackframe {
|
||||
item,
|
||||
prev: &self,
|
||||
len: self.opt().map_or(1, |s| s.len)
|
||||
}
|
||||
}
|
||||
pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> {
|
||||
if let Self::Frame(p) = self {
|
||||
if count == 0 {Some(&p)}
|
||||
else {p.prev.pop(count - 1)}
|
||||
} else {None}
|
||||
}
|
||||
pub fn len(&self) -> usize { match self {
|
||||
Self::Frame(f) => f.len,
|
||||
Self::Bottom => 0
|
||||
} }
|
||||
}
|
||||
|
||||
impl<'a, T> Debug for Substack<'a, T> where T: Debug {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Substack")?;
|
||||
f.debug_list().entries(self.iter()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StackframeIterator<'a, T> {
|
||||
curr: Option<&'a Stackframe<'a, T>>
|
||||
pub struct SubstackIterator<'a, T> {
|
||||
curr: &'a Substack<'a, T>
|
||||
}
|
||||
|
||||
impl<'a, T> StackframeIterator<'a, T> {
|
||||
impl<'a, T> SubstackIterator<'a, T> {
|
||||
#[allow(unused)]
|
||||
pub fn first_some<U, F>(&mut self, f: F) -> Option<U>
|
||||
where F: Fn(&T) -> Option<U> {
|
||||
@@ -83,15 +73,38 @@ impl<'a, T> StackframeIterator<'a, T> {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns an iterator that starts from the bottom of the stack
|
||||
/// and ends at the current position. This moves all items to the
|
||||
/// heap by copying them to a [Vec]
|
||||
pub fn rev_vec_clone(self) -> Vec<T> where T: Clone {
|
||||
let mut deque = VecDeque::with_capacity(self.curr.len());
|
||||
for item in self { deque.push_front(item.clone()) }
|
||||
deque.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for StackframeIterator<'a, T> {
|
||||
impl<'a, T> Copy for SubstackIterator<'a, T> {}
|
||||
impl<'a, T> Clone for SubstackIterator<'a, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { curr: self.curr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for SubstackIterator<'a, T> {
|
||||
type Item = &'a T;
|
||||
fn next(&mut self) -> Option<&'a T> {
|
||||
let curr = self.curr?;
|
||||
let item = curr.item();
|
||||
let prev = curr.prev();
|
||||
let curr = self.curr.opt()?;
|
||||
let item = &curr.item;
|
||||
let prev = curr.prev;
|
||||
self.curr = prev;
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.curr.len(), Some(self.curr.len()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::mem;
|
||||
|
||||
// TODO: extract to crate
|
||||
|
||||
#[allow(unused)]
|
||||
/// Map over a `&mut` with a mapper function that takes ownership of
|
||||
/// the value
|
||||
#[allow(unused)]
|
||||
pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) {
|
||||
unsafe {
|
||||
let mut acc = mem::MaybeUninit::<T>::uninit().assume_init();
|
||||
@@ -17,6 +17,7 @@ pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) {
|
||||
|
||||
/// Map over a `&mut` with a mapper function that takes ownership of
|
||||
/// the value and also produces some unrelated data.
|
||||
#[allow(unused)]
|
||||
pub fn process<T, U, F: FnOnce(T) -> (T, U)>(data: &mut T, f: F) -> U {
|
||||
unsafe {
|
||||
let mut acc = mem::MaybeUninit::<T>::uninit().assume_init();
|
||||
|
||||
19
src/utils/variant.rs
Normal file
19
src/utils/variant.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// trait Var<T> {
|
||||
// type With<U>: Var<U>;
|
||||
|
||||
// fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::With<U>;
|
||||
// fn map_multi<U, V, Ret: Var<U> + Var<V>>(
|
||||
// self, f: impl FnOnce(T) -> Ret
|
||||
// ) -> <Self::With<U> as Var<U>>::With<V>;
|
||||
// }
|
||||
|
||||
// enum Variant<T, U> {
|
||||
// Head(T),
|
||||
// Tail(U)
|
||||
// }
|
||||
|
||||
// impl<H, T: Var<_>> Var<H> for Variant<H, T> {
|
||||
// fn map<U>(self, f: impl FnOnce(H) -> U) -> Self::With<U> {
|
||||
// match
|
||||
// }
|
||||
// }
|
||||
@@ -78,8 +78,8 @@ macro_rules! xloop {
|
||||
(while $cond:expr; $body:stmt; $exit:stmt) => {
|
||||
{
|
||||
loop {
|
||||
if $cond { break { $exit } }
|
||||
else { $body }
|
||||
if $cond { $body }
|
||||
else { break { $exit } }
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -87,6 +87,6 @@ macro_rules! xloop {
|
||||
xloop!(for ( $init; $cond; $step ) $body; ())
|
||||
};
|
||||
($init:stmt; $cond:expr; $step:stmt; $body:stmt; $exit:stmt) => {
|
||||
{ $init; xloop!(while !($cond); { $body; $step }; $exit) }
|
||||
{ $init; xloop!(while $cond; { $body; $step }; $exit) }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user