use std::{hash::Hash,
sync::{Arc, Mutex}};
use rustc_hash::{FxBuildHasher, FxHashMap};
#[derive(Clone, Debug)]
struct CacheEntry<V> {
value: V,
access_count: u64,
}
#[derive(Debug)]
pub struct LruCache<K, V> {
map: FxHashMap<K, CacheEntry<V>>,
capacity: usize,
access_counter: u64,
}
impl<K, V> LruCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
#[must_use]
pub fn new(capacity: usize) -> Self {
assert!(capacity > 0, "Cache capacity must be greater than 0");
Self {
map: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
capacity,
access_counter: 0,
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
self.access_counter += 1;
if let Some(entry) = self.map.get_mut(key) {
entry.access_count = self.access_counter;
Some(&entry.value)
} else {
None
}
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.access_counter += 1;
if let Some(entry) = self.map.get_mut(key) {
entry.access_count = self.access_counter;
Some(&mut entry.value)
} else {
None
}
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
self.access_counter += 1;
if self.map.len() >= self.capacity
&& !self.map.contains_key(&key)
&& let Some(lru_key) = self
.map
.iter()
.min_by_key(|(_, entry)| entry.access_count)
.map(|(k, _)| k.clone())
{
self.map.remove(&lru_key);
}
let entry = CacheEntry {
value,
access_count: self.access_counter,
};
self.map.insert(key, entry).map(|e| e.value)
}
pub fn remove(&mut self, key: &K) -> Option<V> {
self.map.remove(key).map(|e| e.value)
}
pub fn clear(&mut self) {
self.map.clear();
self.access_counter = 0;
}
#[must_use]
pub fn len(&self) -> usize { self.map.len() }
#[must_use]
pub fn is_empty(&self) -> bool { self.map.is_empty() }
#[must_use]
pub fn capacity(&self) -> usize { self.capacity }
#[must_use]
pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) }
}
pub type ThreadSafeLruCache<K, V> = Arc<Mutex<LruCache<K, V>>>;
#[must_use]
pub fn new_threadsafe_lru_cache<K, V>(capacity: usize) -> ThreadSafeLruCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
Arc::new(Mutex::new(LruCache::new(capacity)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache = LruCache::new(3);
assert_eq!(cache.insert("a".to_string(), 1), None);
assert_eq!(cache.get(&"a".to_string()), Some(&1));
assert_eq!(cache.len(), 1);
assert_eq!(cache.insert("a".to_string(), 2), Some(1));
assert_eq!(cache.get(&"a".to_string()), Some(&2));
assert_eq!(cache.len(), 1);
cache.insert("b".to_string(), 3);
cache.insert("c".to_string(), 4);
assert_eq!(cache.len(), 3);
assert_eq!(cache.capacity(), 3);
}
#[test]
fn test_lru_eviction() {
let mut cache = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.insert("c".to_string(), 3);
cache.get(&"a".to_string());
cache.get(&"b".to_string());
cache.insert("d".to_string(), 4);
assert_eq!(cache.get(&"a".to_string()), Some(&1));
assert_eq!(cache.get(&"b".to_string()), Some(&2));
assert_eq!(cache.get(&"c".to_string()), None); assert_eq!(cache.get(&"d".to_string()), Some(&4));
}
#[test]
fn test_get_mut() {
let mut cache = LruCache::new(2);
cache.insert("key".to_string(), vec![1, 2, 3]);
if let Some(val) = cache.get_mut(&"key".to_string()) {
val.push(4);
}
assert_eq!(cache.get(&"key".to_string()), Some(&vec![1, 2, 3, 4]));
}
#[test]
fn test_remove() {
let mut cache = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
assert_eq!(cache.remove(&"a".to_string()), Some(1));
assert_eq!(cache.get(&"a".to_string()), None);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_clear() {
let mut cache = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(cache.is_empty());
assert_eq!(cache.get(&"a".to_string()), None);
}
#[test]
fn test_contains_key() {
let mut cache = LruCache::new(2);
cache.insert("a".to_string(), 1);
assert!(cache.contains_key(&"a".to_string()));
assert!(!cache.contains_key(&"b".to_string()));
}
#[test]
#[should_panic(expected = "Cache capacity must be greater than 0")]
fn test_zero_capacity_panics() { let _cache = LruCache::<String, i32>::new(0); }
#[test]
fn test_thread_safe_cache() {
let cache = new_threadsafe_lru_cache(10);
{
let mut cache_guard = cache.lock().unwrap();
cache_guard.insert("key".to_string(), 42);
}
{
let mut cache_guard = cache.lock().unwrap();
assert_eq!(cache_guard.get(&"key".to_string()), Some(&42));
}
}
}