use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub struct LRUCache<K, V> {
capacity: usize,
map: HashMap<K, (V, usize)>, order: VecDeque<K>, hits: u64,
misses: u64,
}
impl<K: Hash + Eq + Clone, V> LRUCache<K, V> {
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
capacity,
map: HashMap::with_capacity(capacity),
order: VecDeque::with_capacity(capacity),
hits: 0,
misses: 0,
}
}
pub fn insert(&mut self, key: K, value: V) {
if self.map.contains_key(&key) {
self.remove_from_order(&key);
}
while self.map.len() >= self.capacity {
if let Some(lru_key) = self.order.pop_back() {
self.map.remove(&lru_key);
}
}
let token = self.order.len(); self.order.push_front(key.clone());
self.map.insert(key, (value, token));
}
pub fn get(&mut self, key: &K) -> Option<&V> {
if self.map.contains_key(key) {
self.hits += 1;
self.remove_from_order(key);
self.order.push_front(key.clone());
self.map.get(key).map(|(v, _)| v)
} else {
self.misses += 1;
None
}
}
pub fn peek(&self, key: &K) -> Option<&V> {
self.map.get(key).map(|(v, _)| v)
}
pub fn remove(&mut self, key: &K) -> Option<V> {
if let Some((value, _)) = self.map.remove(key) {
self.remove_from_order(key);
Some(value)
} else {
None
}
}
pub fn contains(&self, key: &K) -> bool {
self.map.contains_key(key)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn clear(&mut self) {
self.map.clear();
self.order.clear();
self.hits = 0;
self.misses = 0;
}
fn remove_from_order(&mut self, key: &K) {
if let Some(pos) = self.order.iter().position(|k| k == key) {
self.order.remove(pos);
}
}
}
struct TTLEntry<V> {
value: V,
expires_at: Instant,
}
pub struct TTLCache<K, V> {
capacity: usize,
ttl: Duration,
map: HashMap<K, TTLEntry<V>>,
hits: u64,
misses: u64,
expired: u64,
}
impl<K: Hash + Eq + Clone, V: Clone> TTLCache<K, V> {
pub fn new(capacity: usize, ttl: Duration) -> Self {
let capacity = capacity.max(1);
Self {
capacity,
ttl,
map: HashMap::with_capacity(capacity),
hits: 0,
misses: 0,
expired: 0,
}
}
pub fn insert(&mut self, key: K, value: V) {
if self.map.len() >= self.capacity {
let expired_key = self
.map
.iter()
.find(|(_, e)| e.expires_at <= Instant::now())
.map(|(k, _)| k.clone());
if let Some(k) = expired_key {
self.map.remove(&k);
self.expired += 1;
} else {
if let Some(k) = self.map.keys().next().cloned() {
self.map.remove(&k);
}
}
}
self.map.insert(
key,
TTLEntry {
value,
expires_at: Instant::now() + self.ttl,
},
);
}
pub fn get(&mut self, key: &K) -> Option<V> {
let now = Instant::now();
match self.map.get(key) {
Some(e) if e.expires_at > now => {
self.hits += 1;
Some(e.value.clone())
}
Some(_) => {
self.map.remove(key);
self.expired += 1;
self.misses += 1;
None
}
None => {
self.misses += 1;
None
}
}
}
pub fn remove(&mut self, key: &K) -> Option<V> {
self.map.remove(key).map(|e| e.value)
}
pub fn purge_expired(&mut self) -> usize {
let now = Instant::now();
let before = self.map.len();
self.map.retain(|_, e| e.expires_at > now);
let removed = before - self.map.len();
self.expired += removed as u64;
removed
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn expired_count(&self) -> u64 {
self.expired
}
}
pub struct MemoizedFn<A: Hash + Eq + Clone + Send + 'static, R: Clone + Send + 'static> {
cache: Arc<Mutex<HashMap<A, R>>>,
func: Arc<dyn Fn(A) -> R + Send + Sync>,
}
impl<A: Hash + Eq + Clone + Send + 'static, R: Clone + Send + 'static> MemoizedFn<A, R> {
pub fn new<F>(f: F) -> Self
where
F: Fn(A) -> R + Send + Sync + 'static,
{
Self {
cache: Arc::new(Mutex::new(HashMap::new())),
func: Arc::new(f),
}
}
pub fn call(&self, arg: A) -> R {
if let Ok(guard) = self.cache.lock() {
if let Some(v) = guard.get(&arg) {
return v.clone();
}
}
let result = (self.func)(arg.clone());
if let Ok(mut guard) = self.cache.lock() {
guard.entry(arg).or_insert_with(|| result.clone());
}
result
}
pub fn clear(&self) {
if let Ok(mut guard) = self.cache.lock() {
guard.clear();
}
}
pub fn len(&self) -> usize {
self.cache.lock().map(|g| g.len()).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<A, R> Clone for MemoizedFn<A, R>
where
A: Hash + Eq + Clone + Send + 'static,
R: Clone + Send + 'static,
{
fn clone(&self) -> Self {
Self {
cache: Arc::clone(&self.cache),
func: Arc::clone(&self.func),
}
}
}
pub struct ComputeCache<K: Hash + Eq + Clone, V: Clone> {
inner: Arc<Mutex<HashMap<K, V>>>,
}
impl<K: Hash + Eq + Clone + Send + 'static, V: Clone + Send + 'static> ComputeCache<K, V> {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn get_or_compute<F>(&self, key: K, factory: F) -> V
where
F: FnOnce(&K) -> V,
{
if let Ok(guard) = self.inner.lock() {
if let Some(v) = guard.get(&key) {
return v.clone();
}
}
let value = factory(&key);
if let Ok(mut guard) = self.inner.lock() {
guard.entry(key).or_insert_with(|| value.clone());
}
value
}
pub fn invalidate(&self, key: &K) -> Option<V> {
self.inner.lock().ok()?.remove(key)
}
pub fn clear(&self) {
if let Ok(mut guard) = self.inner.lock() {
guard.clear();
}
}
pub fn len(&self) -> usize {
self.inner.lock().map(|g| g.len()).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<K: Hash + Eq + Clone, V: Clone> Clone for ComputeCache<K, V> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
pub struct TieredCache<K: Hash + Eq + Clone, V: Clone> {
l1: LRUCache<K, V>,
l2: LRUCache<K, V>,
l1_hits: u64,
l2_hits: u64,
full_misses: u64,
}
impl<K: Hash + Eq + Clone, V: Clone> TieredCache<K, V> {
pub fn new(l1_capacity: usize, l2_capacity: usize) -> Self {
Self {
l1: LRUCache::new(l1_capacity),
l2: LRUCache::new(l2_capacity),
l1_hits: 0,
l2_hits: 0,
full_misses: 0,
}
}
pub fn insert(&mut self, key: K, value: V) {
self.l1.insert(key.clone(), value.clone());
self.l2.insert(key, value);
}
pub fn get(&mut self, key: &K) -> Option<V>
where
V: Clone,
{
if let Some(v) = self.l1.get(key) {
self.l1_hits += 1;
return Some(v.clone());
}
if let Some(v) = self.l2.get(key) {
self.l2_hits += 1;
let owned = v.clone();
self.l1.insert(key.clone(), owned.clone());
return Some(owned);
}
self.full_misses += 1;
None
}
pub fn get_or_compute<F>(&mut self, key: K, factory: F) -> V
where
F: FnOnce(&K) -> V,
{
if let Some(v) = self.get(&key) {
return v;
}
let value = factory(&key);
self.insert(key, value.clone());
value
}
pub fn remove(&mut self, key: &K) {
self.l1.remove(key);
self.l2.remove(key);
}
pub fn l1_hits(&self) -> u64 {
self.l1_hits
}
pub fn l2_hits(&self) -> u64 {
self.l2_hits
}
pub fn full_misses(&self) -> u64 {
self.full_misses
}
pub fn hit_rate(&self) -> f64 {
let total = self.l1_hits + self.l2_hits + self.full_misses;
if total == 0 {
0.0
} else {
(self.l1_hits + self.l2_hits) as f64 / total as f64
}
}
pub fn l1_utilisation(&self) -> f64 {
self.l1.len() as f64 / self.l1.capacity() as f64
}
pub fn l2_utilisation(&self) -> f64 {
self.l2.len() as f64 / self.l2.capacity() as f64
}
pub fn clear(&mut self) {
self.l1.clear();
self.l2.clear();
self.l1_hits = 0;
self.l2_hits = 0;
self.full_misses = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lru_basic_insert_get() {
let mut c = LRUCache::<i32, &str>::new(3);
c.insert(1, "a");
c.insert(2, "b");
c.insert(3, "c");
assert_eq!(c.get(&1), Some(&"a"));
assert_eq!(c.get(&2), Some(&"b"));
assert_eq!(c.get(&3), Some(&"c"));
}
#[test]
fn lru_evicts_least_recently_used() {
let mut c = LRUCache::<i32, i32>::new(3);
c.insert(1, 10);
c.insert(2, 20);
c.insert(3, 30);
c.get(&1);
c.insert(4, 40);
assert!(c.get(&1).is_some(), "1 was MRU, should survive");
assert_eq!(c.len(), 3);
}
#[test]
fn lru_hit_miss_counters() {
let mut c = LRUCache::<i32, i32>::new(4);
c.insert(1, 1);
c.get(&1); c.get(&2); assert_eq!(c.hits(), 1);
assert_eq!(c.misses(), 1);
}
#[test]
fn lru_remove() {
let mut c = LRUCache::<i32, i32>::new(4);
c.insert(1, 100);
assert_eq!(c.remove(&1), Some(100));
assert!(c.get(&1).is_none());
}
#[test]
fn ttl_cache_insert_get() {
let mut c = TTLCache::<i32, i32>::new(4, Duration::from_secs(60));
c.insert(1, 42);
assert_eq!(c.get(&1), Some(42));
}
#[test]
fn ttl_cache_expiry() {
let mut c = TTLCache::<i32, i32>::new(4, Duration::from_millis(10));
c.insert(1, 7);
std::thread::sleep(Duration::from_millis(20));
assert_eq!(c.get(&1), None, "entry should have expired");
}
#[test]
fn ttl_purge_expired() {
let mut c = TTLCache::<i32, i32>::new(8, Duration::from_millis(10));
c.insert(1, 1);
c.insert(2, 2);
std::thread::sleep(Duration::from_millis(20));
let removed = c.purge_expired();
assert_eq!(removed, 2);
assert_eq!(c.len(), 0);
}
#[test]
fn memoized_fn_caches_result() {
let call_count = Arc::new(Mutex::new(0u32));
let cc = Arc::clone(&call_count);
let memo = MemoizedFn::new(move |n: u64| {
if let Ok(mut g) = cc.lock() {
*g += 1;
}
n * n
});
assert_eq!(memo.call(5), 25);
assert_eq!(memo.call(5), 25); assert_eq!(memo.call(6), 36);
let count = *call_count.lock().expect("lock");
assert_eq!(count, 2, "closure should be called only for new inputs");
}
#[test]
fn memoized_fn_clear() {
let memo = MemoizedFn::new(|n: u64| n + 1);
memo.call(10);
memo.call(20);
assert_eq!(memo.len(), 2);
memo.clear();
assert_eq!(memo.len(), 0);
}
#[test]
fn compute_cache_get_or_compute() {
let cache = ComputeCache::<u64, String>::new();
let v = cache.get_or_compute(3, |k| format!("v-{k}"));
assert_eq!(v, "v-3");
let v2 = cache.get_or_compute(3, |_| panic!("factory should not be called again"));
assert_eq!(v2, "v-3");
}
#[test]
fn compute_cache_invalidate() {
let cache = ComputeCache::<u64, u64>::new();
cache.get_or_compute(1, |k| *k * 2);
cache.invalidate(&1);
assert_eq!(cache.len(), 0);
}
#[test]
fn tiered_cache_l1_hit() {
let mut tc = TieredCache::<i32, i32>::new(4, 16);
tc.insert(1, 100);
assert_eq!(tc.get(&1), Some(100));
assert_eq!(tc.l1_hits(), 1);
assert_eq!(tc.l2_hits(), 0);
}
#[test]
fn tiered_cache_l2_promotion() {
let mut tc = TieredCache::<i32, i32>::new(1, 4);
tc.insert(1, 111);
tc.insert(2, 222); let v = tc.get(&1);
assert_eq!(v, Some(111));
assert_eq!(tc.l2_hits(), 1);
}
#[test]
fn tiered_cache_full_miss() {
let mut tc = TieredCache::<i32, i32>::new(2, 4);
assert_eq!(tc.get(&99), None);
assert_eq!(tc.full_misses(), 1);
}
#[test]
fn tiered_cache_get_or_compute() {
let mut tc = TieredCache::<i32, i32>::new(4, 16);
let v = tc.get_or_compute(7, |k| k * 3);
assert_eq!(v, 21);
let v2 = tc.get_or_compute(7, |_| panic!("should not compute again"));
assert_eq!(v2, 21);
}
}