#![allow(dead_code)]
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ResultCacheConfig {
pub max_entries: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ResultCache {
config: ResultCacheConfig,
entries: HashMap<String, f32>,
stats: CacheStats,
}
#[allow(dead_code)]
pub fn new_result_cache(config: ResultCacheConfig) -> ResultCache {
ResultCache {
config,
entries: HashMap::new(),
stats: CacheStats { hits: 0, misses: 0, evictions: 0 },
}
}
#[allow(dead_code)]
pub fn cache_get(cache: &mut ResultCache, key: &str) -> Option<f32> {
if let Some(&v) = cache.entries.get(key) {
cache.stats.hits += 1;
Some(v)
} else {
cache.stats.misses += 1;
None
}
}
#[allow(dead_code)]
pub fn cache_insert(cache: &mut ResultCache, key: String, value: f32) {
if cache.entries.len() >= cache.config.max_entries && !cache.entries.contains_key(&key) {
if let Some(k) = cache.entries.keys().next().cloned() {
cache.entries.remove(&k);
cache.stats.evictions += 1;
}
}
cache.entries.insert(key, value);
}
#[allow(dead_code)]
pub fn cache_remove(cache: &mut ResultCache, key: &str) -> Option<f32> {
cache.entries.remove(key)
}
#[allow(dead_code)]
pub fn cache_stats(cache: &ResultCache) -> CacheStats {
cache.stats.clone()
}
#[allow(dead_code)]
pub fn cache_hit_rate(cache: &ResultCache) -> f32 {
let total = cache.stats.hits + cache.stats.misses;
if total == 0 {
0.0
} else {
cache.stats.hits as f32 / total as f32
}
}
#[allow(dead_code)]
pub fn cache_len(cache: &ResultCache) -> usize {
cache.entries.len()
}
#[allow(dead_code)]
pub fn cache_clear(cache: &mut ResultCache) {
cache.entries.clear();
cache.stats = CacheStats { hits: 0, misses: 0, evictions: 0 };
}
#[cfg(test)]
mod tests {
use super::*;
fn make_cache(max: usize) -> ResultCache {
new_result_cache(ResultCacheConfig { max_entries: max })
}
#[test]
fn test_new_cache_empty() {
let cache = make_cache(10);
assert_eq!(cache_len(&cache), 0);
}
#[test]
fn test_insert_and_get() {
let mut cache = make_cache(10);
cache_insert(&mut cache, "pi".to_string(), std::f32::consts::PI);
let val = cache_get(&mut cache, "pi");
assert!(val.is_some());
assert!((val.expect("should succeed") - std::f32::consts::PI).abs() < 1e-6);
}
#[test]
fn test_miss_increments_counter() {
let mut cache = make_cache(10);
cache_get(&mut cache, "missing");
assert_eq!(cache_stats(&cache).misses, 1);
}
#[test]
fn test_hit_increments_counter() {
let mut cache = make_cache(10);
cache_insert(&mut cache, "x".to_string(), 1.0);
cache_get(&mut cache, "x");
assert_eq!(cache_stats(&cache).hits, 1);
}
#[test]
fn test_hit_rate() {
let mut cache = make_cache(10);
cache_insert(&mut cache, "a".to_string(), 1.0);
cache_get(&mut cache, "a"); cache_get(&mut cache, "b"); let rate = cache_hit_rate(&cache);
assert!((rate - 0.5).abs() < 1e-6);
}
#[test]
fn test_remove() {
let mut cache = make_cache(10);
cache_insert(&mut cache, "r".to_string(), 99.0);
let old = cache_remove(&mut cache, "r");
assert_eq!(old, Some(99.0));
assert_eq!(cache_len(&cache), 0);
}
#[test]
fn test_clear() {
let mut cache = make_cache(10);
cache_insert(&mut cache, "k".to_string(), 1.0);
cache_clear(&mut cache);
assert_eq!(cache_len(&cache), 0);
assert_eq!(cache_stats(&cache).hits, 0);
}
#[test]
fn test_eviction_on_full() {
let mut cache = make_cache(2);
cache_insert(&mut cache, "a".to_string(), 1.0);
cache_insert(&mut cache, "b".to_string(), 2.0);
cache_insert(&mut cache, "c".to_string(), 3.0); assert_eq!(cache_stats(&cache).evictions, 1);
assert_eq!(cache_len(&cache), 2);
}
#[test]
fn test_hit_rate_zero_on_no_lookups() {
let cache = make_cache(10);
assert_eq!(cache_hit_rate(&cache), 0.0);
}
}