#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LruCacheConfig {
pub capacity: usize,
}
#[allow(dead_code)]
impl LruCacheConfig {
fn new() -> Self {
Self { capacity: 16 }
}
}
#[allow(dead_code)]
pub fn default_lru_config() -> LruCacheConfig {
LruCacheConfig::new()
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LruEntry {
pub key: String,
pub value: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LruCache {
config: LruCacheConfig,
entries: Vec<LruEntry>,
}
#[allow(dead_code)]
pub fn new_lru_cache(config: LruCacheConfig) -> LruCache {
LruCache {
config,
entries: Vec::new(),
}
}
#[allow(dead_code)]
pub fn lru_get(cache: &mut LruCache, key: &str) -> Option<f32> {
if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
let entry = cache.entries.remove(pos);
let value = entry.value;
cache.entries.push(entry);
Some(value)
} else {
None
}
}
#[allow(dead_code)]
pub fn lru_insert(cache: &mut LruCache, key: &str, value: f32) {
if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
let mut entry = cache.entries.remove(pos);
entry.value = value;
cache.entries.push(entry);
return;
}
if cache.entries.len() >= cache.config.capacity {
cache.entries.remove(0);
}
cache.entries.push(LruEntry {
key: key.to_string(),
value,
});
}
#[allow(dead_code)]
pub fn lru_remove(cache: &mut LruCache, key: &str) -> bool {
if let Some(pos) = cache.entries.iter().position(|e| e.key == key) {
cache.entries.remove(pos);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn lru_contains(cache: &LruCache, key: &str) -> bool {
cache.entries.iter().any(|e| e.key == key)
}
#[allow(dead_code)]
pub fn lru_len(cache: &LruCache) -> usize {
cache.entries.len()
}
#[allow(dead_code)]
pub fn lru_capacity(cache: &LruCache) -> usize {
cache.config.capacity
}
#[allow(dead_code)]
pub fn lru_evict_oldest(cache: &mut LruCache) -> Option<LruEntry> {
if cache.entries.is_empty() {
return None;
}
Some(cache.entries.remove(0))
}
#[allow(dead_code)]
pub fn lru_to_json(cache: &LruCache) -> String {
let items: Vec<String> = cache
.entries
.iter()
.map(|e| format!("{{\"key\":\"{}\",\"value\":{:.4}}}", e.key, e.value))
.collect();
format!(
"{{\"capacity\":{},\"len\":{},\"entries\":[{}]}}",
cache.config.capacity,
cache.entries.len(),
items.join(",")
)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_cache() -> LruCache {
let mut c = new_lru_cache(LruCacheConfig { capacity: 3 });
lru_insert(&mut c, "a", 1.0);
lru_insert(&mut c, "b", 2.0);
lru_insert(&mut c, "c", 3.0);
c
}
#[test]
fn test_basic_insert_get() {
let mut c = make_cache();
assert!((lru_get(&mut c, "a").expect("should succeed") - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_len() {
let c = make_cache();
assert_eq!(lru_len(&c), 3);
}
#[test]
fn test_capacity() {
let c = make_cache();
assert_eq!(lru_capacity(&c), 3);
}
#[test]
fn test_eviction_on_overflow() {
let mut c = make_cache();
lru_insert(&mut c, "d", 4.0);
assert!(!lru_contains(&c, "a"));
assert!(lru_contains(&c, "d"));
}
#[test]
fn test_get_updates_recency() {
let mut c = make_cache();
lru_get(&mut c, "a");
lru_insert(&mut c, "d", 4.0);
assert!(!lru_contains(&c, "b"));
assert!(lru_contains(&c, "a"));
}
#[test]
fn test_remove() {
let mut c = make_cache();
assert!(lru_remove(&mut c, "b"));
assert!(!lru_contains(&c, "b"));
assert_eq!(lru_len(&c), 2);
}
#[test]
fn test_remove_nonexistent() {
let mut c = make_cache();
assert!(!lru_remove(&mut c, "z"));
}
#[test]
fn test_evict_oldest() {
let mut c = make_cache();
let evicted = lru_evict_oldest(&mut c).expect("should succeed");
assert_eq!(evicted.key, "a");
assert_eq!(lru_len(&c), 2);
}
#[test]
fn test_update_existing_key() {
let mut c = make_cache();
lru_insert(&mut c, "a", 99.0);
assert_eq!(lru_len(&c), 3); assert!((lru_get(&mut c, "a").expect("should succeed") - 99.0).abs() < f32::EPSILON);
}
#[test]
fn test_to_json() {
let c = make_cache();
let json = lru_to_json(&c);
assert!(json.contains("capacity"));
assert!(json.contains("entries"));
}
}