use std::cmp::{Eq, Ordering};
use std::collections::BTreeSet;
use std::hash::Hash;
use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Eq, PartialEq)]
struct PriorityItem<T> {
item: T,
counter: i64,
}
impl<T: Eq> Ord for PriorityItem<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.counter.cmp(&other.counter)
}
}
impl<T: Eq> PartialOrd for PriorityItem<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug)]
pub struct LRUEvictor<T: Clone + Eq + Hash> {
free_table: FxHashMap<T, i64>,
priority_queue: BTreeSet<PriorityItem<T>>,
positive_counter: i64,
negative_counter: i64,
}
impl<T: Clone + Eq + Hash> Default for LRUEvictor<T> {
fn default() -> Self {
Self {
free_table: FxHashMap::default(),
priority_queue: BTreeSet::new(),
positive_counter: 0,
negative_counter: 0,
}
}
}
impl<T: Clone + Eq + Hash> LRUEvictor<T> {
pub fn new(_cleanup_threshold: usize) -> Self {
Self::default()
}
pub fn keys(&self) -> std::collections::hash_map::Keys<'_, T, i64> {
self.free_table.keys()
}
fn update(&mut self, object: T, counter: i64) {
self.free_table.insert(object.clone(), counter);
self.priority_queue.insert(PriorityItem {
item: object,
counter,
});
}
pub fn insert(&mut self, object: T) {
if let Some(&old_counter) = self.free_table.get(&object) {
self.priority_queue.remove(&PriorityItem {
item: object.clone(),
counter: old_counter,
});
}
self.positive_counter += 1;
let counter = self.positive_counter;
self.update(object, counter);
}
pub fn push_front(&mut self, object: T) {
if let Some(&old_counter) = self.free_table.get(&object) {
self.priority_queue.remove(&PriorityItem {
item: object.clone(),
counter: old_counter,
});
}
self.negative_counter -= 1;
let counter = self.negative_counter;
self.update(object, counter);
}
pub fn contains(&self, object: &T) -> bool {
self.free_table.contains_key(object)
}
pub fn evict(&mut self) -> Option<T> {
self.priority_queue.pop_first().map(|item| {
self.free_table.remove(&item.item);
item.item
})
}
pub fn remove(&mut self, object: &T) -> bool {
let Some(&counter) = self.free_table.get(object) else {
return false;
};
self.free_table.remove(object);
self.priority_queue.remove(&PriorityItem {
item: object.clone(),
counter,
});
true
}
pub fn len(&self) -> usize {
self.free_table.len()
}
pub fn is_empty(&self) -> bool {
self.free_table.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lru_evictor_eviction_order() {
let mut evictor = LRUEvictor::<i32>::new(1);
evictor.insert(4);
evictor.insert(3);
evictor.insert(2);
evictor.insert(1);
evictor.insert(5);
evictor.insert(1); evictor.insert(4); evictor.insert(2); evictor.push_front(4);
let evicted = evictor.evict().unwrap();
assert_eq!(evicted, 4);
let evicted = evictor.evict().unwrap();
assert_eq!(evicted, 3);
let evicted = evictor.evict().unwrap();
assert_eq!(evicted, 5);
let evicted = evictor.evict().unwrap();
assert_eq!(evicted, 1);
let evicted = evictor.evict().unwrap();
assert_eq!(evicted, 2);
let evicted = evictor.evict();
assert_eq!(evicted, None);
assert_eq!(evictor.len(), 0);
}
}