use alloc::vec::Vec;
use core::cell::OnceCell;
use core::ops::Index;
pub struct CachedLookup<T, F> {
slots: Vec<OnceCell<T>>,
make_value: F,
}
impl<T, F> CachedLookup<T, F> {
pub fn new(len: usize, make_value: F) -> Self {
CachedLookup {
slots: core::iter::repeat_with(OnceCell::new).take(len).collect(),
make_value,
}
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
}
impl<T, F> Index<usize> for CachedLookup<T, F>
where
F: Fn(usize) -> T,
{
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.slots[index].get_or_init(|| (self.make_value)(index))
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::Cell;
use super::CachedLookup;
use crate::{Algorithm, ChangeTag, capture_diff};
#[test]
fn caches_values_once_per_index() {
let hits = Cell::new(0);
let lookup = CachedLookup::new(3, |idx| {
hits.set(hits.get() + 1);
idx * 2
});
assert_eq!(lookup[1], 2);
assert_eq!(lookup[1], 2);
assert_eq!(lookup[2], 4);
assert_eq!(hits.get(), 2);
}
#[test]
fn works_with_diff_algorithms() {
let old = CachedLookup::new(3, |idx: usize| ["foo", "bar", "baz"][idx].to_string());
let new = CachedLookup::new(3, |idx: usize| ["foo", "blah", "baz"][idx].to_string());
let ops = capture_diff(Algorithm::Myers, &old, 0..old.len(), &new, 0..new.len());
let changes = ops
.iter()
.flat_map(|op| op.iter_changes(&old, &new))
.map(|change| (change.tag(), change.value()))
.collect::<Vec<_>>();
assert_eq!(
changes,
vec![
(ChangeTag::Equal, "foo".to_string()),
(ChangeTag::Delete, "bar".to_string()),
(ChangeTag::Insert, "blah".to_string()),
(ChangeTag::Equal, "baz".to_string()),
]
);
}
}