Transfer commit

This commit is contained in:
2023-03-21 19:36:40 +00:00
parent 180ebb56fa
commit f3ce910f66
63 changed files with 1410 additions and 1023 deletions

View File

@@ -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

View File

@@ -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()
}
}

View 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)
}
}

View File

@@ -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> {
// }

View File

@@ -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())

View File

@@ -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))
})
}

View File

@@ -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;

View File

@@ -8,7 +8,9 @@ use super::Side;
pub enum Product2<T> {
Left,
Right,
#[allow(unused)]
Either,
#[allow(unused)]
New(T)
}
impl<T> Product2<T> {

View File

@@ -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>>

View File

@@ -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]) {

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -1,6 +0,0 @@
#[macro_export]
macro_rules! unless_let {
($m:pat_param = $expr:tt) => {
if let $m = $expr {} else
}
}

View File

@@ -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) => {

View File

@@ -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>> {
}

View File

@@ -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) => {