use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct LruCache<K, V>
where
K: Clone + Hash + Eq,
V: Clone,
{
capacity: usize,
cache: RwLock<HashMap<K, CacheEntry<V>>>,
access_order: RwLock<Vec<K>>,
}
#[derive(Debug, Clone)]
struct CacheEntry<V> {
value: V,
created_at: Instant,
access_count: u64,
}
impl<K, V> LruCache<K, V>
where
K: Clone + Hash + Eq,
V: Clone,
{
pub fn new(capacity: usize) -> Self {
Self {
capacity,
cache: RwLock::new(HashMap::new()),
access_order: RwLock::new(Vec::new()),
}
}
pub fn get(&self, key: &K) -> Option<V> {
if let Ok(mut cache) = self.cache.write() {
if let Some(entry) = cache.get_mut(key) {
entry.access_count += 1;
self.update_access_order(key);
return Some(entry.value.clone());
}
}
None
}
pub fn insert(&self, key: K, value: V) {
if let Ok(mut cache) = self.cache.write() {
if cache.len() >= self.capacity && !cache.contains_key(&key) {
self.evict_lru();
}
let entry = CacheEntry {
value,
created_at: Instant::now(),
access_count: 1,
};
cache.insert(key.clone(), entry);
self.update_access_order(&key);
}
}
pub fn stats(&self) -> CacheStats {
if let Ok(cache) = self.cache.read() {
let total_entries = cache.len();
let total_accesses: u64 = cache.values().map(|entry| entry.access_count).sum();
let oldest_entry = cache.values()
.min_by_key(|entry| entry.created_at)
.map(|entry| entry.created_at);
CacheStats {
total_entries,
capacity: self.capacity,
total_accesses,
oldest_entry_age: oldest_entry.map(|instant| instant.elapsed()),
}
} else {
CacheStats::default()
}
}
pub fn clear(&self) {
if let Ok(mut cache) = self.cache.write() {
cache.clear();
}
if let Ok(mut order) = self.access_order.write() {
order.clear();
}
}
fn update_access_order(&self, key: &K) {
if let Ok(mut order) = self.access_order.write() {
if let Some(pos) = order.iter().position(|k| k == key) {
order.remove(pos);
}
order.push(key.clone());
}
}
fn evict_lru(&self) {
if let Ok(mut order) = self.access_order.write() {
if let Some(lru_key) = order.first().cloned() {
order.remove(0);
if let Ok(mut cache) = self.cache.write() {
cache.remove(&lru_key);
}
}
}
}
}
#[derive(Debug)]
pub struct MemoCache<K, V>
where
K: Clone + Hash + Eq,
V: Clone,
{
cache: RwLock<HashMap<K, V>>,
max_size: usize,
hits: RwLock<u64>,
misses: RwLock<u64>,
}
impl<K, V> MemoCache<K, V>
where
K: Clone + Hash + Eq,
V: Clone,
{
pub fn new(max_size: usize) -> Self {
Self {
cache: RwLock::new(HashMap::new()),
max_size,
hits: RwLock::new(0),
misses: RwLock::new(0),
}
}
pub fn get_or_compute<F>(&self, key: K, compute: F) -> V
where
F: FnOnce() -> V,
{
if let Ok(cache) = self.cache.read() {
if let Some(value) = cache.get(&key) {
if let Ok(mut hits) = self.hits.write() {
*hits += 1;
}
return value.clone();
}
}
let value = compute();
if let Ok(mut misses) = self.misses.write() {
*misses += 1;
}
if let Ok(mut cache) = self.cache.write() {
if cache.len() >= self.max_size {
cache.clear();
}
cache.insert(key, value.clone());
}
value
}
pub fn hit_rate(&self) -> f64 {
let hits = if let Ok(hits) = self.hits.read() { *hits } else { 0 };
let misses = if let Ok(misses) = self.misses.read() { *misses } else { 0 };
let total = hits + misses;
if total > 0 {
(hits as f64 / total as f64) * 100.0
} else {
0.0
}
}
pub fn clear(&self) {
if let Ok(mut cache) = self.cache.write() {
cache.clear();
}
if let Ok(mut hits) = self.hits.write() {
*hits = 0;
}
if let Ok(mut misses) = self.misses.write() {
*misses = 0;
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub total_entries: usize,
pub capacity: usize,
pub total_accesses: u64,
pub oldest_entry_age: Option<Duration>,
}
pub mod global {
use super::*;
use once_cell::sync::Lazy;
pub static SYMBOL_CACHE: Lazy<MemoCache<String, u64>> =
Lazy::new(|| MemoCache::new(1000));
pub static NUMERIC_CACHE: Lazy<MemoCache<String, f64>> =
Lazy::new(|| MemoCache::new(500));
pub static TYPE_CACHE: Lazy<LruCache<String, String>> =
Lazy::new(|| LruCache::new(200));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lru_cache_basic() {
let cache = LruCache::new(2);
cache.insert(1, "one");
cache.insert(2, "two");
assert_eq!(cache.get(&1), Some("one"));
assert_eq!(cache.get(&2), Some("two"));
assert_eq!(cache.get(&3), None);
}
#[test]
fn test_lru_cache_eviction() {
let cache = LruCache::new(2);
cache.insert(1, "one");
cache.insert(2, "two");
cache.insert(3, "three");
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), Some("two"));
assert_eq!(cache.get(&3), Some("three"));
}
#[test]
fn test_memo_cache() {
use std::cell::RefCell;
use std::rc::Rc;
let cache = MemoCache::new(10);
let call_count = Rc::new(RefCell::new(0));
{
let call_count_clone = call_count.clone();
let compute = || {
*call_count_clone.borrow_mut() += 1;
format!("computed_{}", *call_count_clone.borrow())
};
let _result1 = cache.get_or_compute(1, compute);
}
{
let call_count_clone = call_count.clone();
let compute = || {
*call_count_clone.borrow_mut() += 1;
format!("computed_{}", *call_count_clone.borrow())
};
let _result2 = cache.get_or_compute(1, compute);
}
assert_eq!(*call_count.borrow(), 1); assert!(cache.hit_rate() > 0.0);
}
#[test]
fn test_cache_stats() {
let cache = LruCache::new(5);
cache.insert(1, "test");
cache.get(&1);
cache.get(&1);
let stats = cache.stats();
assert_eq!(stats.total_entries, 1);
assert_eq!(stats.capacity, 5);
assert_eq!(stats.total_accesses, 3); }
}