Transfer commit
This commit is contained in:
@@ -5,6 +5,8 @@ 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
|
||||
|
||||
@@ -1,96 +1,47 @@
|
||||
use std::{hash::Hash, cell::RefCell, rc::Rc};
|
||||
use hashbrown::HashMap;
|
||||
use mappable_rc::Mrc;
|
||||
|
||||
/// Convenience trait for overriding Mrc's strange cloning logic
|
||||
pub trait MyClone {
|
||||
fn my_clone(&self) -> Self;
|
||||
}
|
||||
|
||||
impl<T> MyClone for T where T: Clone {
|
||||
default fn my_clone(&self) -> Self { self.clone() }
|
||||
}
|
||||
|
||||
impl<T: ?Sized> MyClone for Rc<T> {
|
||||
fn my_clone(&self) -> Self { Rc::clone(self) }
|
||||
}
|
||||
impl<T: ?Sized> MyClone for Mrc<T> {
|
||||
fn my_clone(&self) -> Self { Mrc::clone(self) }
|
||||
}
|
||||
// TODO: make this a crate
|
||||
|
||||
/// Cache the return values of an effectless closure in a hashmap
|
||||
/// Inspired by the closure_cacher crate.
|
||||
pub struct Cache<'a, I, O: 'static> {
|
||||
store: RefCell<HashMap<I, Mrc<O>>>,
|
||||
closure: Box<dyn Fn (I, &Self) -> Mrc<O> + 'a>
|
||||
store: RefCell<HashMap<I, O>>,
|
||||
closure: Box<dyn Fn (I, &Self) -> O + 'a>
|
||||
}
|
||||
|
||||
impl<'a, I, O> Cache<'a, I, O> where
|
||||
I: Eq + Hash + MyClone
|
||||
I: Eq + Hash + Clone, O: Clone
|
||||
{
|
||||
pub fn new<F: 'a>(closure: F) -> Self where F: Fn(I, &Self) -> O {
|
||||
Self::new_raw(move |o, s| Mrc::new(closure(o, s)))
|
||||
}
|
||||
|
||||
/// Take an Mrc<O> closure rather than an O closure
|
||||
/// Used internally to derive caches from other systems working with Mrc-s
|
||||
pub fn new_raw<F: 'a>(closure: F) -> Self where F: Fn(I, &Self) -> Mrc<O> {
|
||||
Self {
|
||||
store: RefCell::new(HashMap::new()),
|
||||
closure: Box::new(closure)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rc<F: 'a>(closure: F) -> Rc<Self> where F: Fn(I, &Self) -> O {
|
||||
Rc::new(Self::new(closure))
|
||||
}
|
||||
|
||||
/// Produce and cache a result by cloning I if necessary
|
||||
pub fn find(&self, i: &I) -> Mrc<O> {
|
||||
pub fn find(&self, i: &I) -> O {
|
||||
let closure = &self.closure;
|
||||
if let Some(v) = self.store.borrow().get(i) {
|
||||
return Mrc::clone(v)
|
||||
return v.clone()
|
||||
}
|
||||
// In the moment of invocation the refcell is on immutable
|
||||
// this is important for recursive calculations
|
||||
let result = closure(i.my_clone(), self);
|
||||
let result = closure(i.clone(), self);
|
||||
let mut store = self.store.borrow_mut();
|
||||
Mrc::clone(store.raw_entry_mut().from_key(i)
|
||||
.or_insert_with(|| (i.my_clone(), result)).1)
|
||||
store.raw_entry_mut().from_key(i)
|
||||
.or_insert_with(|| (i.clone(), result)).1.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Return the result if it has already been computed
|
||||
pub fn known(&self, i: &I) -> Option<Mrc<O>> {
|
||||
pub fn known(&self, i: &I) -> Option<O> {
|
||||
let store = self.store.borrow();
|
||||
store.get(i).map(Mrc::clone)
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
/// Forget the output for the given input
|
||||
pub fn drop(&self, i: &I) -> bool {
|
||||
self.store.borrow_mut().remove(i).is_some()
|
||||
store.get(i).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, O, E> Cache<'a, I, Result<O, E>> where
|
||||
I: Eq + Hash + MyClone,
|
||||
// O: Clone,
|
||||
E: Clone
|
||||
{
|
||||
/// Sink the ref from a Result into the Ok value, such that cloning only occurs on the sad path
|
||||
/// but the return value can be short-circuited
|
||||
pub fn try_find(&self, i: &I) -> Result<Mrc<O>, E> {
|
||||
let ent = self.find(i);
|
||||
Mrc::try_map(ent, |t| t.as_ref().ok())
|
||||
.map_err(|res| Result::as_ref(&res).err().unwrap().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, O> Cache<'a, I, Option<O>> where
|
||||
I: Eq + Hash + MyClone,
|
||||
// O: Clone
|
||||
{
|
||||
#[allow(dead_code)]
|
||||
/// Sink the ref from an Option into the Some value such that the return value can be
|
||||
/// short-circuited
|
||||
pub fn try_find(&self, i: &I) -> Option<Mrc<O>> where I: Clone {
|
||||
let ent = self.find(i);
|
||||
Mrc::try_map(ent, |o| o.as_ref()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
19
src/utils/interned_display.rs
Normal file
19
src/utils/interned_display.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// use std::{collections::HashSet, hash::Hash};
|
||||
|
||||
// use hashbrown::HashMap;
|
||||
|
||||
// #[derive(Copy, Clone)]
|
||||
// pub struct Interned<'a, T> {
|
||||
// interner: &'a Interner<T>,
|
||||
// data: &'a T,
|
||||
// }
|
||||
|
||||
// impl<'a, T: Eq> Eq for Interned<'a, T> {}
|
||||
// impl<'a, T: PartialEq> PartialEq for Interned<'a, T> {
|
||||
// fn eq(&self, other: &Self) -> bool {
|
||||
// if (self.interner as *const _) == (other.interner as *const _) {
|
||||
// (self.data as *const _) == (other.data as *const _)
|
||||
// } else {self.data == other.data}
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub struct Interner<T> {
|
||||
// data: HashSet<T>,
|
||||
// hash_cache: HashMap<>
|
||||
// }
|
||||
|
||||
// impl Interner<T> {
|
||||
|
||||
// }
|
||||
@@ -1,6 +1,6 @@
|
||||
/// Utility functions to get rid of explicit casts to BoxedIter which are tedious
|
||||
|
||||
use std::iter;
|
||||
use std::{iter, mem};
|
||||
|
||||
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
||||
pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>;
|
||||
@@ -30,6 +30,7 @@ where
|
||||
{
|
||||
Box::new(i.flatten())
|
||||
}
|
||||
|
||||
pub fn into_boxed_iter<'a, T: 'a>(t: T) -> BoxedIter<'a, <T as IntoIterator>::Item>
|
||||
where T: IntoIterator {
|
||||
Box::new(t.into_iter())
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
use std::mem;
|
||||
|
||||
// use itertools::Itertools;
|
||||
|
||||
/// Merge two sorted iterators into a sorted iterator.
|
||||
pub fn merge_sorted<T, I, J, F, O>(mut i: I, mut j: J, mut f: F) -> impl Iterator<Item = T>
|
||||
where
|
||||
I: Iterator<Item = T>, J: Iterator<Item = T>,
|
||||
F: FnMut(&T) -> O, O: Ord,
|
||||
{
|
||||
let mut i_item: Option<T> = None;
|
||||
let mut j_item: Option<T> = None;
|
||||
std::iter::from_fn(move || {
|
||||
match (&mut i_item, &mut j_item) {
|
||||
(&mut None, &mut None) => None,
|
||||
(&mut None, j_item @ &mut Some(_)) => Some((j_item, None)),
|
||||
(i_item @ &mut Some(_), &mut None) => Some((i_item, i.next())),
|
||||
(Some(i_val), Some(j_val)) => Some(
|
||||
if f(i_val) < f(j_val) {
|
||||
(&mut i_item, i.next())
|
||||
} else {
|
||||
(&mut j_item, j.next())
|
||||
}
|
||||
)
|
||||
}.and_then(|(dest, value)| mem::replace(dest, value))
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
mod cache;
|
||||
pub mod translate;
|
||||
mod replace_first;
|
||||
mod interner;
|
||||
mod interned_display;
|
||||
pub use interned_display::InternedDisplay;
|
||||
// mod visitor;
|
||||
pub use replace_first::replace_first;
|
||||
pub use cache::Cache;
|
||||
@@ -9,16 +10,13 @@ mod substack;
|
||||
pub use substack::Stackframe;
|
||||
mod side;
|
||||
pub use side::Side;
|
||||
mod merge_sorted;
|
||||
pub use merge_sorted::merge_sorted;
|
||||
mod unwrap_or;
|
||||
pub mod iter;
|
||||
pub use iter::BoxedIter;
|
||||
mod bfs;
|
||||
mod unless_let;
|
||||
mod string_from_charset;
|
||||
pub use string_from_charset::string_from_charset;
|
||||
mod for_loop;
|
||||
mod xloop;
|
||||
mod protomap;
|
||||
pub use protomap::ProtoMap;
|
||||
mod product2;
|
||||
|
||||
@@ -8,7 +8,9 @@ use super::Side;
|
||||
pub enum Product2<T> {
|
||||
Left,
|
||||
Right,
|
||||
#[allow(unused)]
|
||||
Either,
|
||||
#[allow(unused)]
|
||||
New(T)
|
||||
}
|
||||
impl<T> Product2<T> {
|
||||
|
||||
@@ -2,16 +2,18 @@ use std::{iter, ops::{Index, Add}, borrow::Borrow};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
const INLINE_ENTRIES: usize = 2;
|
||||
// TODO: make this a crate alongside substack
|
||||
|
||||
/// Linked-array-list of key-value pairs.
|
||||
/// Lookup and modification is O(n + cachemiss * n / m)
|
||||
/// Can be extended by reference in O(m) < O(n)
|
||||
/// - Lookup and modification is O(n + cachemiss * n / m)
|
||||
/// - Can be extended by reference in O(m) < O(n)
|
||||
///
|
||||
/// The number of elements stored inline in a stackframe is 2 by default, which is enough for most
|
||||
/// recursive algorithms. The cost of overruns is a heap allocation and subsequent heap indirections,
|
||||
/// plus wasted stack space which is likely wasted L1 as well. The cost of underruns is wasted stack
|
||||
/// space.
|
||||
/// The number of elements stored inline in a stackframe is 2 by default,
|
||||
/// which is enough for most recursive algorithms.
|
||||
/// - The cost of overruns is a heap allocation and subsequent
|
||||
/// heap indirections, plus wasted stack space which is likely wasted L1
|
||||
/// as well.
|
||||
/// - The cost of underruns is wasted stack space.
|
||||
pub struct ProtoMap<'a, K, V, const STACK_COUNT: usize = 2> {
|
||||
entries: SmallVec<[(K, Option<V>); STACK_COUNT]>,
|
||||
prototype: Option<&'a ProtoMap<'a, K, V, STACK_COUNT>>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::iter;
|
||||
|
||||
pub fn replace_first<'a, T, F>(slice: &'a [T], mut f: F) -> Option<impl Iterator<Item = T> + 'a>
|
||||
/// Iterate over a sequence with the first element the function returns
|
||||
/// Some() for updated, but only if there is such an element.
|
||||
pub fn replace_first<'a, T, F>(slice: &'a [T], mut f: F)
|
||||
-> Option<impl Iterator<Item = T> + 'a>
|
||||
where T: Clone, F: FnMut(&T) -> Option<T> {
|
||||
for i in 0..slice.len() {
|
||||
if let Some(new) = f(&slice[i]) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
/// 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)]
|
||||
pub enum Side {Left, Right}
|
||||
|
||||
@@ -32,8 +34,11 @@ impl Side {
|
||||
pub fn crop<'a, T>(&self, margin: usize, slice: &'a [T]) -> &'a [T] {
|
||||
self.opposite().slice(slice.len() - margin, slice)
|
||||
}
|
||||
/// ignore N elements from this end and M elements from the other end of a slice
|
||||
pub fn crop_both<'a, T>(&self, margin: usize, opposite: usize, slice: &'a [T]) -> &'a [T] {
|
||||
/// ignore N elements from this end and M elements from the other end
|
||||
/// of a slice
|
||||
pub fn crop_both<'a, T>(&self,
|
||||
margin: usize, opposite: usize, slice: &'a [T]
|
||||
) -> &'a [T] {
|
||||
self.crop(margin, self.opposite().crop(opposite, slice))
|
||||
}
|
||||
/// Pick this side from a pair of things
|
||||
|
||||
@@ -9,6 +9,8 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String {
|
||||
prefix
|
||||
}
|
||||
|
||||
/// Generate alphabetized names from numbers using a set of permitted
|
||||
/// characters
|
||||
pub fn string_from_charset(val: u64, digits: &str) -> String {
|
||||
string_from_charset_rec(val + 1, digits)
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Implement a FILO stack that lives on the regular call stack as a linked list.
|
||||
/// Mainly useful to detect loops in recursive algorithms where the recursion isn't
|
||||
/// deep enough to warrant a heap-allocated set
|
||||
// TODO: extract to crate
|
||||
|
||||
/// A FILO stack that lives on the regular call stack as a linked list.
|
||||
/// Mainly useful to detect loops in recursive algorithms where
|
||||
/// the recursion isn't deep enough to warrant a heap-allocated set.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stackframe<'a, T> {
|
||||
pub item: T,
|
||||
@@ -33,6 +35,7 @@ impl<'a, T: 'a> Stackframe<'a, T> {
|
||||
len: self.len + 1
|
||||
}
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn opush(prev: Option<&'a Self>, item: T) -> Self {
|
||||
Self {
|
||||
item,
|
||||
@@ -40,15 +43,19 @@ impl<'a, T: 'a> Stackframe<'a, T> {
|
||||
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 }
|
||||
}
|
||||
@@ -66,7 +73,9 @@ pub struct StackframeIterator<'a, T> {
|
||||
}
|
||||
|
||||
impl<'a, T> StackframeIterator<'a, T> {
|
||||
pub fn first_some<U, F: Fn(&T) -> Option<U>>(&mut self, f: F) -> Option<U> {
|
||||
#[allow(unused)]
|
||||
pub fn first_some<U, F>(&mut self, f: F) -> Option<U>
|
||||
where F: Fn(&T) -> Option<U> {
|
||||
while let Some(x) = self.next() {
|
||||
if let Some(result) = f(x) {
|
||||
return Some(result)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use std::mem;
|
||||
|
||||
// TODO: extract to crate
|
||||
|
||||
#[allow(unused)]
|
||||
/// Map over a `&mut` with a mapper function that takes ownership of
|
||||
/// the value
|
||||
pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) {
|
||||
unsafe {
|
||||
let mut acc = mem::MaybeUninit::<T>::uninit().assume_init();
|
||||
@@ -10,6 +15,8 @@ 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.
|
||||
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();
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#[macro_export]
|
||||
macro_rules! unless_let {
|
||||
($m:pat_param = $expr:tt) => {
|
||||
if let $m = $expr {} else
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
/// A macro version of [Option::unwrap_or_else] which supports
|
||||
/// flow control statements such as `return` and `break` in the "else"
|
||||
/// branch.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_or {
|
||||
($m:expr; $fail:expr) => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
pub trait Visit<T> {
|
||||
type Return;
|
||||
fn visit(&self, target: T) -> Return;
|
||||
}
|
||||
|
||||
pub trait ImpureVisit<T> {
|
||||
type Shard;
|
||||
type Return;
|
||||
fn impure_visit(&self, target: T) -> (Shard, Return);
|
||||
fn merge(&mut self, s: Shard);
|
||||
}
|
||||
|
||||
pub struct OverlayVisitor<VBase, VOver>(VBase, VOver);
|
||||
|
||||
impl<VBase, VOver, T, R> Visitor<T> for OverlayVisitor<VBase, VOver>
|
||||
where VBase: Visitor<T, Return = Option<R>>, VOver: Visitor<T, Return = Option<R>> {
|
||||
|
||||
}
|
||||
@@ -48,7 +48,8 @@
|
||||
/// to these as well just like the others. In all cases the exit expression is optional, its
|
||||
/// default value is `()`.
|
||||
///
|
||||
/// **todo** find a valid use case for While let for a demo
|
||||
/// TODO: find a valid use case for While let for a demo
|
||||
/// TODO: break out into crate
|
||||
#[macro_export]
|
||||
macro_rules! xloop {
|
||||
(for $p:pat in $it:expr; $body:stmt) => {
|
||||
Reference in New Issue
Block a user