use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug)]
pub struct LruCache<K, V> {
capacity: usize,
entries: HashMap<K, CacheEntry<V>>,
access_counter: u64,
}
#[derive(Debug)]
struct CacheEntry<V> {
value: V,
last_access: u64,
}
impl<K: Eq + Hash + Clone, V: Clone> LruCache<K, V> {
pub fn new(capacity: usize) -> Self {
Self {
capacity,
entries: HashMap::with_capacity(capacity),
access_counter: 0,
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
if let Some(entry) = self.entries.get_mut(key) {
self.access_counter += 1;
entry.last_access = self.access_counter;
Some(&entry.value)
} else {
None
}
}
pub fn get_cloned(&mut self, key: &K) -> Option<V> {
self.get(key).cloned()
}
pub fn insert(&mut self, key: K, value: V) {
if let Some(entry) = self.entries.get_mut(&key) {
self.access_counter += 1;
entry.value = value;
entry.last_access = self.access_counter;
return;
}
if self.entries.len() >= self.capacity {
self.evict_oldest();
}
self.access_counter += 1;
self.entries.insert(
key,
CacheEntry {
value,
last_access: self.access_counter,
},
);
}
pub fn invalidate(&mut self, key: &K) {
self.entries.remove(key);
}
pub fn clear(&mut self) {
self.entries.clear();
self.access_counter = 0;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn contains(&self, key: &K) -> bool {
self.entries.contains_key(key)
}
fn evict_oldest(&mut self) {
if self.entries.is_empty() {
return;
}
let oldest_key = self
.entries
.iter()
.min_by_key(|(_, entry)| entry.last_access)
.map(|(k, _)| k.clone());
if let Some(key) = oldest_key {
self.entries.remove(&key);
}
}
}
impl<K: Eq + Hash + Clone, V: Clone> Default for LruCache<K, V> {
fn default() -> Self {
Self::new(100)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache: LruCache<String, i32> = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.insert("c".to_string(), 3);
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()), Some(&3));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_eviction() {
let mut cache: LruCache<String, i32> = 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.insert("d".to_string(), 4);
assert!(cache.contains(&"a".to_string()));
assert!(!cache.contains(&"b".to_string())); assert!(cache.contains(&"c".to_string()));
assert!(cache.contains(&"d".to_string()));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_update_existing() {
let mut cache: LruCache<String, i32> = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("a".to_string(), 10);
assert_eq!(cache.get(&"a".to_string()), Some(&10));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_invalidate() {
let mut cache: LruCache<String, i32> = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.invalidate(&"a".to_string());
assert!(!cache.contains(&"a".to_string()));
assert!(cache.contains(&"b".to_string()));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_clear() {
let mut cache: LruCache<String, i32> = LruCache::new(3);
cache.insert("a".to_string(), 1);
cache.insert("b".to_string(), 2);
cache.clear();
assert!(cache.is_empty());
}
}