use rustc_hash::FxHashMap;
use std::hash::Hash;
use std::mem;
use std::ptr::NonNull;
pub struct FastLru<K, V> {
map: FxHashMap<K, NonNull<Node<K, V>>>,
head: Option<NonNull<Node<K, V>>>,
tail: Option<NonNull<Node<K, V>>>,
capacity: usize,
}
#[repr(C)]
struct Node<K, V> {
prev: Option<NonNull<Node<K, V>>>,
next: Option<NonNull<Node<K, V>>>,
key: K,
value: V,
}
impl<K, V> FastLru<K, V>
where
K: Eq + Hash + Clone,
{
#[inline]
pub fn new(capacity: usize) -> Self {
Self {
map: FxHashMap::with_capacity_and_hasher(capacity, Default::default()),
head: None,
tail: None,
capacity,
}
}
#[inline]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
#[inline(always)]
pub fn get(&mut self, key: &K) -> Option<&V> {
let node_ptr = *self.map.get(key)?;
self.detach(node_ptr);
self.attach_front(node_ptr);
Some(unsafe { &(*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
let node_ptr = *self.map.get(key)?;
self.detach(node_ptr);
self.attach_front(node_ptr);
Some(unsafe { &mut (*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn peek(&self, key: &K) -> Option<&V> {
self.map
.get(key)
.map(|node_ptr| unsafe { &(*node_ptr.as_ptr()).value })
}
#[inline(always)]
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
if let Some(&node_ptr) = self.map.get(&key) {
let old_value = unsafe {
let node = node_ptr.as_ptr();
mem::replace(&mut (*node).value, value)
};
self.detach(node_ptr);
self.attach_front(node_ptr);
return Some(old_value);
}
if self.capacity > 0 && self.map.len() >= self.capacity {
self.pop_lru();
}
if self.capacity == 0 {
return None;
}
let node = Box::new(Node {
prev: None,
next: None,
key: key.clone(),
value,
});
let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
self.map.insert(key, node_ptr);
self.attach_front(node_ptr);
None
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let node_ptr = self.map.remove(key)?;
self.detach(node_ptr);
let node = unsafe { Box::from_raw(node_ptr.as_ptr()) };
Some(node.value)
}
pub fn pop_lru(&mut self) -> Option<(K, V)> {
let tail_ptr = self.tail?;
let key = unsafe { (*tail_ptr.as_ptr()).key.clone() };
self.map.remove(&key);
self.detach(tail_ptr);
let node = unsafe { Box::from_raw(tail_ptr.as_ptr()) };
Some((node.key, node.value))
}
pub fn peek_lru(&self) -> Option<(&K, &V)> {
self.tail.map(|node_ptr| unsafe {
let node = node_ptr.as_ptr();
(&(*node).key, &(*node).value)
})
}
pub fn clear(&mut self) {
while self.pop_lru().is_some() {}
}
#[inline(always)]
pub fn touch(&mut self, key: &K) -> bool {
if let Some(&node_ptr) = self.map.get(key) {
self.detach(node_ptr);
self.attach_front(node_ptr);
true
} else {
false
}
}
#[inline(always)]
fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ptr();
let prev = (*node).prev;
let next = (*node).next;
match prev {
Some(prev_ptr) => (*prev_ptr.as_ptr()).next = next,
None => self.head = next,
}
match next {
Some(next_ptr) => (*next_ptr.as_ptr()).prev = prev,
None => self.tail = prev,
}
(*node).prev = None;
(*node).next = None;
}
}
#[inline(always)]
fn attach_front(&mut self, node_ptr: NonNull<Node<K, V>>) {
unsafe {
let node = node_ptr.as_ptr();
(*node).prev = None;
(*node).next = self.head;
match self.head {
Some(head_ptr) => (*head_ptr.as_ptr()).prev = Some(node_ptr),
None => self.tail = Some(node_ptr),
}
self.head = Some(node_ptr);
}
}
}
impl<K, V> Drop for FastLru<K, V> {
fn drop(&mut self) {
let mut current = self.head;
while let Some(node_ptr) = current {
unsafe {
let node = Box::from_raw(node_ptr.as_ptr());
current = node.next;
}
}
}
}
unsafe impl<K: Send, V: Send> Send for FastLru<K, V> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache = FastLru::new(3);
assert!(cache.is_empty());
assert_eq!(cache.capacity(), 3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&1), Some(&"one"));
assert_eq!(cache.get(&2), Some(&"two"));
assert_eq!(cache.get(&3), Some(&"three"));
}
#[test]
fn test_eviction() {
let mut cache = FastLru::new(2);
cache.insert(1, "one");
cache.insert(2, "two");
cache.get(&1);
cache.insert(3, "three");
assert!(cache.contains(&1));
assert!(!cache.contains(&2));
assert!(cache.contains(&3));
}
#[test]
fn test_update() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
let old = cache.insert(1, "ONE");
assert_eq!(old, Some("one"));
assert_eq!(cache.get(&1), Some(&"ONE"));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_remove() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
assert_eq!(cache.remove(&1), Some("one"));
assert_eq!(cache.remove(&1), None);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_pop_lru() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.pop_lru(), Some((1, "one")));
assert_eq!(cache.pop_lru(), Some((2, "two")));
assert_eq!(cache.pop_lru(), Some((3, "three")));
assert_eq!(cache.pop_lru(), None);
}
#[test]
fn test_touch() {
let mut cache = FastLru::new(3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert!(cache.touch(&1));
cache.insert(4, "four");
assert!(cache.contains(&1));
assert!(!cache.contains(&2));
}
#[test]
fn test_zero_capacity() {
let mut cache: FastLru<i32, &str> = FastLru::new(0);
assert_eq!(cache.insert(1, "one"), None);
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
}
#[test]
fn test_clear() {
let mut cache = FastLru::new(10);
cache.insert(1, "one");
cache.insert(2, "two");
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
}
#[test]
fn test_peek() {
let mut cache = FastLru::new(3);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.peek(&1), Some(&"one"));
cache.insert(4, "four");
assert!(!cache.contains(&1));
}
}