#[derive(Clone, Debug)]
pub struct LRUList<T> {
values: Vec<ListEntry<T>>,
}
#[derive(Clone, Debug)]
struct ListEntry<T> {
value: Option<T>,
next: usize,
prev: usize,
}
impl<T> LRUList<T> {
const FREE: usize = 0;
const OCCUPIED: usize = 1;
pub(crate) fn with_capacity(capacity: usize) -> LRUList<T> {
let cap = capacity.saturating_add(2);
let mut values = Vec::with_capacity(cap);
values.push(ListEntry::<T> {
value: None,
next: 0,
prev: 0,
});
values.push(ListEntry::<T> {
value: None,
next: 1,
prev: 1,
});
LRUList { values }
}
pub(crate) fn try_with_capacity(
capacity: usize,
) -> Result<LRUList<T>, crate::stores::BuildError> {
let capacity = capacity
.checked_add(2)
.ok_or(crate::stores::BuildError::InvalidValue {
field: "max_size",
reason: "capacity overflow",
})?;
let mut values = Vec::new();
values.try_reserve_exact(capacity).map_err(|_| {
crate::stores::BuildError::InvalidValue {
field: "max_size",
reason: "allocation failed",
}
})?;
values.push(ListEntry::<T> {
value: None,
next: 0,
prev: 0,
});
values.push(ListEntry::<T> {
value: None,
next: 1,
prev: 1,
});
Ok(LRUList { values })
}
pub(crate) fn unlink(&mut self, index: usize) {
let prev = self.values[index].prev;
let next = self.values[index].next;
self.values[prev].next = next;
self.values[next].prev = prev;
}
pub(crate) fn link_after(&mut self, index: usize, prev: usize) {
let next = self.values[prev].next;
self.values[index].prev = prev;
self.values[index].next = next;
self.values[prev].next = index;
self.values[next].prev = index;
}
pub(crate) fn move_to_front(&mut self, index: usize) {
self.unlink(index);
self.link_after(index, Self::OCCUPIED);
}
pub(crate) fn push_front(&mut self, value: T) -> usize {
if self.values[Self::FREE].next == Self::FREE {
self.values.push(ListEntry::<T> {
value: None,
next: Self::FREE,
prev: Self::FREE,
});
self.values[Self::FREE].next = self.values.len() - 1;
}
let index = self.values[Self::FREE].next;
self.values[index].value = Some(value);
self.unlink(index);
self.link_after(index, Self::OCCUPIED);
index
}
pub(crate) fn remove(&mut self, index: usize) -> T {
self.unlink(index);
self.link_after(index, Self::FREE);
self.values[index].value.take().expect("invalid index")
}
pub(crate) fn back(&self) -> usize {
self.values[Self::OCCUPIED].prev
}
pub(crate) fn get(&self, index: usize) -> &T {
self.values[index].value.as_ref().expect("invalid index")
}
pub(crate) fn get_mut(&mut self, index: usize) -> &mut T {
self.values[index].value.as_mut().expect("invalid index")
}
pub(crate) fn set(&mut self, index: usize, value: T) -> Option<T> {
self.values[index].value.replace(value)
}
pub(crate) fn clear(&mut self) {
self.values.clear();
self.values.push(ListEntry::<T> {
value: None,
next: 0,
prev: 0,
});
self.values.push(ListEntry::<T> {
value: None,
next: 1,
prev: 1,
});
}
pub(crate) fn drain_into(&mut self, out: &mut Vec<T>) {
let mut index = self.values[Self::OCCUPIED].next;
while index != Self::OCCUPIED {
let next = self.values[index].next;
if let Some(value) = self.values[index].value.take() {
out.push(value);
}
index = next;
}
self.clear();
}
pub fn iter(&self) -> LRUListIterator<'_, T> {
LRUListIterator::<T> {
list: self,
index: Self::OCCUPIED,
}
}
pub(crate) fn iter_indices(&self) -> LRUListIndexIterator<'_, T> {
LRUListIndexIterator::<T> {
list: self,
index: Self::OCCUPIED,
}
}
}
#[derive(Debug)]
pub struct LRUListIterator<'a, T> {
list: &'a LRUList<T>,
index: usize,
}
impl<'a, T> Iterator for LRUListIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let next = self.list.values[self.index].next;
if next == LRUList::<T>::OCCUPIED {
None
} else {
let value = self.list.values[next].value.as_ref();
self.index = next;
value
}
}
}
#[derive(Debug)]
pub struct LRUListIndexIterator<'a, T> {
list: &'a LRUList<T>,
index: usize,
}
impl<T> Iterator for LRUListIndexIterator<'_, T> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
let next = self.list.values[self.index].next;
if next == LRUList::<T>::OCCUPIED {
None
} else {
self.index = next;
Some(next)
}
}
}
#[cfg(test)]
mod tests {
use super::LRUList;
fn order(l: &LRUList<i32>) -> Vec<i32> {
l.iter().copied().collect()
}
#[test]
fn push_order_and_back() {
let mut l = LRUList::with_capacity(4);
assert!(order(&l).is_empty());
let a = l.push_front(1);
let b = l.push_front(2);
let c = l.push_front(3);
assert_eq!(order(&l), vec![3, 2, 1]); assert_eq!(*l.get(a), 1);
assert_eq!(*l.get(b), 2);
assert_eq!(*l.get(c), 3);
assert_eq!(l.back(), a); }
#[test]
fn index_stable_across_other_removal() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(10);
let b = l.push_front(20);
let c = l.push_front(30);
assert_eq!(l.remove(b), 20);
assert_eq!(*l.get(a), 10);
assert_eq!(*l.get(c), 30);
assert_eq!(order(&l), vec![30, 10]);
}
#[test]
fn freed_slots_are_reused() {
let mut l = LRUList::with_capacity(2);
let a = l.push_front(1);
assert_eq!(l.remove(a), 1);
let b = l.push_front(2);
assert_eq!(a, b, "a freed slot must be reused, not grown");
assert_eq!(*l.get(b), 2);
assert_eq!(order(&l), vec![2]);
}
#[test]
fn move_to_front_reorders() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(1);
let b = l.push_front(2);
let _c = l.push_front(3);
assert_eq!(order(&l), vec![3, 2, 1]);
l.move_to_front(a);
assert_eq!(order(&l), vec![1, 3, 2]);
assert_eq!(l.back(), b); }
fn order_reversed(l: &LRUList<i32>) -> Vec<i32> {
let mut out = Vec::new();
let mut idx = l.values[LRUList::<i32>::OCCUPIED].prev;
while idx != LRUList::<i32>::OCCUPIED {
out.push(*l.get(idx));
idx = l.values[idx].prev;
}
out.reverse();
out
}
#[test]
fn move_to_front_on_the_current_head_keeps_both_link_directions_intact() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(1);
let _b = l.push_front(2);
let c = l.push_front(3);
assert_eq!(order(&l), vec![3, 2, 1]);
for _ in 0..3 {
l.move_to_front(c);
assert_eq!(order(&l), vec![3, 2, 1]);
assert_eq!(order_reversed(&l), order(&l), "prev chain must mirror next");
assert_eq!(l.back(), a);
}
l.move_to_front(a);
assert_eq!(order(&l), vec![1, 3, 2]);
assert_eq!(order_reversed(&l), order(&l));
}
#[test]
fn move_to_front_on_the_sole_entry_keeps_the_ring_intact() {
let mut l = LRUList::with_capacity(2);
let a = l.push_front(42);
for _ in 0..3 {
l.move_to_front(a);
assert_eq!(order(&l), vec![42]);
assert_eq!(order_reversed(&l), vec![42]);
assert_eq!(l.back(), a);
}
let b = l.push_front(7);
assert_eq!(order(&l), vec![7, 42]);
assert_eq!(order_reversed(&l), order(&l));
assert_eq!(l.back(), a);
assert_eq!(*l.get(b), 7);
}
#[test]
fn set_replaces_and_clear_resets() {
let mut l = LRUList::with_capacity(2);
let a = l.push_front(7);
assert_eq!(l.set(a, 8), Some(7));
assert_eq!(*l.get(a), 8);
l.clear();
assert!(order(&l).is_empty());
let b = l.push_front(9); assert_eq!(*l.get(b), 9);
}
#[test]
fn iter_indices_matches_iter_order() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(1);
let b = l.push_front(2);
let c = l.push_front(3);
assert_eq!(l.iter_indices().collect::<Vec<_>>(), vec![c, b, a]);
let by_index: Vec<i32> = l.iter_indices().map(|i| *l.get(i)).collect();
assert_eq!(by_index, order(&l));
l.move_to_front(a);
assert_eq!(l.iter_indices().collect::<Vec<_>>(), vec![a, c, b]);
assert_eq!(l.remove(c), 3);
assert_eq!(l.iter_indices().collect::<Vec<_>>(), vec![a, b]);
let by_index: Vec<i32> = l.iter_indices().map(|i| *l.get(i)).collect();
assert_eq!(by_index, order(&l));
let empty: LRUList<i32> = LRUList::with_capacity(2);
assert!(empty.iter_indices().next().is_none());
}
#[test]
fn drain_into_yields_mru_to_lru_and_resets() {
let mut l = LRUList::with_capacity(4);
l.push_front(1);
l.push_front(2);
let c = l.push_front(3);
l.move_to_front(c);
let mut out = Vec::new();
l.drain_into(&mut out);
assert_eq!(out, vec![3, 2, 1], "drain must be MRU -> LRU");
assert!(order(&l).is_empty());
assert!(l.iter_indices().next().is_none());
let a = l.push_front(9);
let b = l.push_front(10);
assert_eq!(*l.get(a), 9);
assert_eq!(*l.get(b), 10);
assert_eq!(order(&l), vec![10, 9]);
assert_eq!(l.back(), a);
assert_eq!(l.remove(b), 10);
let d = l.push_front(11);
assert_eq!(d, b, "a freed slot must be reused after a drain, not grown");
assert_eq!(order(&l), vec![11, 9]);
let mut l2: LRUList<i32> = LRUList::with_capacity(2);
let mut out2 = vec![42];
l2.drain_into(&mut out2);
assert_eq!(out2, vec![42]);
let e = l2.push_front(5);
assert_eq!(*l2.get(e), 5);
}
#[test]
fn stale_index_after_push_front_refers_to_recycled_slot() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(1);
let b = l.push_front(2);
let c = l.push_front(3);
let snapshot: Vec<usize> = l.iter_indices().collect();
assert_eq!(snapshot, vec![c, b, a]);
assert_eq!(l.remove(b), 2);
assert_eq!(*l.get(snapshot[0]), 3);
assert_eq!(*l.get(snapshot[2]), 1);
let d = l.push_front(99);
assert_eq!(d, b, "push_front must recycle the most recently freed slot");
assert_eq!(*l.get(snapshot[1]), 99);
assert_eq!(
l.remove(snapshot[1]),
99,
"replaying a stale index removes the recycled entry, not the original"
);
assert_eq!(order(&l), vec![3, 1]);
}
#[test]
fn iter_indices_empty_after_all_removals() {
let mut l = LRUList::with_capacity(4);
let a = l.push_front(1);
let b = l.push_front(2);
assert_eq!(l.remove(a), 1);
assert_eq!(l.remove(b), 2);
assert!(l.iter_indices().next().is_none());
assert!(order(&l).is_empty());
}
#[test]
#[should_panic(expected = "invalid index")]
fn remove_of_freed_slot_panics() {
let mut l = LRUList::with_capacity(2);
let a = l.push_front(1);
assert_eq!(l.remove(a), 1);
let _ = l.remove(a);
}
#[test]
#[should_panic(expected = "invalid index")]
fn get_of_freed_slot_panics() {
let mut l = LRUList::with_capacity(2);
let a = l.push_front(1);
assert_eq!(l.remove(a), 1);
let _ = l.get(a);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn get_out_of_range_index_panics() {
let mut l = LRUList::with_capacity(2);
l.push_front(1);
let _ = l.get(999);
}
#[test]
fn drain_into_skips_freed_slots() {
let mut l = LRUList::with_capacity(8);
let a = l.push_front(1);
let b = l.push_front(2);
let _c = l.push_front(3);
let d = l.push_front(4);
assert_eq!(l.remove(b), 2);
assert_eq!(l.remove(d), 4);
l.move_to_front(a);
let mut out = Vec::new();
l.drain_into(&mut out);
assert_eq!(out, vec![1, 3]);
assert!(order(&l).is_empty());
}
}