use std::collections::{hash_map::Entry, HashMap};
use std::hash::Hash;
use std::mem;
#[derive(Copy, Clone)]
enum CacheState {
Complete,
InProgress { entry_depth: usize, accessed: bool },
Invalid,
}
struct CacheEntry<T> {
state: CacheState,
value: T,
}
struct MemoState<K, V, F> {
depth: usize,
largest_used_cycle: Option<usize>,
cache: HashMap<K, CacheEntry<V>>,
f: F,
}
pub fn memoize<K, V>(f: impl FnOnce(&mut dyn FnMut(K) -> V, K) -> V + Clone) -> impl FnMut(K) -> V
where
K: Copy + Eq + Hash + std::fmt::Debug,
V: Clone + Default + Eq + std::fmt::Debug,
{
let mut state = MemoState {
depth: 0,
largest_used_cycle: None,
cache: HashMap::new(),
f,
};
move |k| state.call(k)
}
impl<K, V, F> MemoState<K, V, F>
where
K: Copy + Eq + Hash + std::fmt::Debug,
V: Clone + Default + Eq + std::fmt::Debug,
F: FnOnce(&mut dyn FnMut(K) -> V, K) -> V + Clone,
{
fn call(&mut self, k: K) -> V {
let entry = match self.cache.entry(k) {
Entry::Occupied(entry) => {
let entry = entry.into_mut();
match &mut entry.state {
CacheState::Complete => return entry.value.clone(),
CacheState::InProgress {
entry_depth,
accessed,
} => {
*accessed = true;
self.largest_used_cycle =
Some(self.largest_used_cycle.unwrap_or(*entry_depth).min(*entry_depth));
return entry.value.clone();
}
CacheState::Invalid => {
entry.value = V::default();
}
}
entry
}
Entry::Vacant(entry) => entry.insert(CacheEntry {
state: CacheState::Invalid,
value: V::default(),
}),
};
entry.state = CacheState::InProgress {
entry_depth: self.depth,
accessed: false,
};
let outer_largest_used_cycle = self.largest_used_cycle.take();
loop {
self.depth += 1;
self.largest_used_cycle = None;
let f = self.f.clone();
let v = f(&mut |k| self.call(k), k);
self.depth -= 1;
let entry = self.cache.get_mut(&k).unwrap();
let old_value = mem::replace(&mut entry.value, v);
match &mut entry.state {
CacheState::InProgress { accessed, .. } => {
if *accessed {
*accessed = false;
if entry.value != old_value {
continue;
}
}
}
CacheState::Complete => {
assert_eq!(entry.value, old_value);
}
_ => unreachable!(),
}
entry.state = CacheState::Complete;
if self.largest_used_cycle == Some(self.depth) {
continue;
}
if let Some(cycle_entry_depth) = self.largest_used_cycle {
assert!(cycle_entry_depth < self.depth);
entry.state = CacheState::Invalid;
}
if let Some(cycle_entry_depth) = outer_largest_used_cycle {
self.largest_used_cycle =
Some(self.largest_used_cycle.unwrap_or(cycle_entry_depth).min(cycle_entry_depth));
}
return entry.value.clone();
}
}
}