use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Copy)]
struct Node {
prev: Option<u64>,
next: Option<u64>,
}
#[derive(Debug, Default)]
pub(crate) struct LruIndex {
head: Option<u64>,
tail: Option<u64>,
nodes: FxHashMap<u64, Node>,
}
impl LruIndex {
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)] pub fn len(&self) -> usize {
self.nodes.len()
}
#[allow(dead_code)] pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn contains(&self, id: u64) -> bool {
self.nodes.contains_key(&id)
}
pub fn push_front(&mut self, id: u64) {
if self.contains(id) {
self.unlink(id);
}
let new_node = Node {
prev: None,
next: self.head,
};
match self.head {
Some(old_head) => {
if let Some(n) = self.nodes.get_mut(&old_head) {
n.prev = Some(id);
}
}
None => {
self.tail = Some(id);
}
}
self.nodes.insert(id, new_node);
self.head = Some(id);
}
pub fn push_back(&mut self, id: u64) {
if self.contains(id) {
self.unlink(id);
}
let new_node = Node {
prev: self.tail,
next: None,
};
match self.tail {
Some(old_tail) => {
if let Some(n) = self.nodes.get_mut(&old_tail) {
n.next = Some(id);
}
}
None => {
self.head = Some(id);
}
}
self.nodes.insert(id, new_node);
self.tail = Some(id);
}
pub fn remove(&mut self, id: u64) -> bool {
if !self.contains(id) {
return false;
}
self.unlink(id);
self.nodes.remove(&id);
true
}
pub fn iter_lru_to_mru(&self) -> LruIter<'_> {
LruIter {
idx: self,
cursor: self.tail,
}
}
fn unlink(&mut self, id: u64) {
let node = match self.nodes.get(&id) {
Some(n) => *n,
None => return,
};
match node.prev {
Some(prev_id) => {
if let Some(p) = self.nodes.get_mut(&prev_id) {
p.next = node.next;
}
}
None => {
self.head = node.next;
}
}
match node.next {
Some(next_id) => {
if let Some(n) = self.nodes.get_mut(&next_id) {
n.prev = node.prev;
}
}
None => {
self.tail = node.prev;
}
}
}
}
pub(crate) struct LruIter<'a> {
idx: &'a LruIndex,
cursor: Option<u64>,
}
impl Iterator for LruIter<'_> {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let cur = self.cursor?;
self.cursor = self.idx.nodes.get(&cur).and_then(|n| n.prev);
Some(cur)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn collect_lru_to_mru(idx: &LruIndex) -> Vec<u64> {
idx.iter_lru_to_mru().collect()
}
#[test]
fn empty() {
let idx = LruIndex::new();
assert_eq!(idx.len(), 0);
assert!(idx.is_empty());
assert!(!idx.contains(1));
assert_eq!(collect_lru_to_mru(&idx), Vec::<u64>::new());
}
#[test]
fn push_one() {
let mut idx = LruIndex::new();
idx.push_front(7);
assert_eq!(idx.len(), 1);
assert!(idx.contains(7));
assert_eq!(collect_lru_to_mru(&idx), vec![7]);
}
#[test]
fn push_three_then_iterate_lru_to_mru() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3);
assert_eq!(collect_lru_to_mru(&idx), vec![1, 2, 3]);
}
#[test]
fn push_existing_moves_to_front() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3);
idx.push_front(1);
assert_eq!(collect_lru_to_mru(&idx), vec![2, 3, 1]);
assert_eq!(idx.len(), 3);
}
#[test]
fn remove_middle() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3);
assert!(idx.remove(2));
assert_eq!(collect_lru_to_mru(&idx), vec![1, 3]);
assert_eq!(idx.len(), 2);
assert!(!idx.contains(2));
}
#[test]
fn remove_head() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3);
assert!(idx.remove(3));
assert_eq!(collect_lru_to_mru(&idx), vec![1, 2]);
}
#[test]
fn remove_tail() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3);
assert!(idx.remove(1));
assert_eq!(collect_lru_to_mru(&idx), vec![2, 3]);
}
#[test]
fn remove_only() {
let mut idx = LruIndex::new();
idx.push_front(1);
assert!(idx.remove(1));
assert!(idx.is_empty());
assert_eq!(collect_lru_to_mru(&idx), Vec::<u64>::new());
idx.push_front(2);
assert_eq!(collect_lru_to_mru(&idx), vec![2]);
}
#[test]
fn remove_absent_is_noop() {
let mut idx = LruIndex::new();
idx.push_front(1);
assert!(!idx.remove(99));
assert_eq!(collect_lru_to_mru(&idx), vec![1]);
}
#[test]
fn push_back_inserts_at_tail() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2); idx.push_back(3); let order: Vec<u64> = collect_lru_to_mru(&idx);
assert_eq!(order, vec![3, 1, 2]);
}
#[test]
fn push_back_moves_existing_to_tail() {
let mut idx = LruIndex::new();
idx.push_front(1);
idx.push_front(2);
idx.push_front(3); idx.push_back(2);
assert_eq!(collect_lru_to_mru(&idx), vec![2, 1, 3]);
assert_eq!(idx.len(), 3);
}
#[test]
fn push_back_on_empty() {
let mut idx = LruIndex::new();
idx.push_back(5);
assert_eq!(collect_lru_to_mru(&idx), vec![5]);
assert_eq!(idx.head, Some(5));
assert_eq!(idx.tail, Some(5));
}
#[test]
fn churn_keeps_invariants() {
let mut idx = LruIndex::new();
for i in 0..100 {
idx.push_front(i);
}
assert_eq!(idx.len(), 100);
for i in 0..100 {
if i % 3 == 0 {
idx.remove(i);
}
}
for i in 0..100 {
if i % 5 == 0 && i % 3 != 0 {
idx.push_front(i);
}
}
let visited: Vec<u64> = collect_lru_to_mru(&idx);
assert_eq!(visited.len(), idx.len());
for id in &visited {
assert!(idx.contains(*id));
}
let mut count_present = 0;
for i in 0..100 {
if idx.contains(i) {
count_present += 1;
}
}
assert_eq!(count_present, idx.len());
}
}