#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvictionStrategy {
Lru,
Lfu,
Fifo,
}
pub fn create_eviction_fn<K>(
policy: EvictionStrategy,
) -> Box<dyn Fn(&Vec<(K, u64)>) -> Option<K>>
where
K: Clone + Eq + 'static,
{
match policy {
EvictionStrategy::Lru | EvictionStrategy::Lfu => Box::new(|entries: &Vec<(K, u64)>| {
entries
.iter()
.min_by_key(|(_, ts)| *ts)
.map(|(k, _)| k.clone())
}),
EvictionStrategy::Fifo => Box::new(|entries: &Vec<(K, u64)>| {
entries.first().map(|(k, _)| k.clone())
}),
}
}
pub struct EvictionChooser<K: Clone + Eq + 'static> {
pub strategy: EvictionStrategy,
evict: Box<dyn Fn(&Vec<(K, u64)>) -> Option<K>>,
}
impl<K: Clone + Eq + 'static> EvictionChooser<K> {
#[must_use]
pub fn new(strategy: EvictionStrategy) -> Self {
Self {
strategy,
evict: create_eviction_fn(strategy),
}
}
#[must_use]
pub fn choose(&self, entries: &Vec<(K, u64)>) -> Option<K> {
(self.evict)(entries)
}
}
impl<K: Clone + Eq + std::fmt::Debug + 'static> std::fmt::Debug for EvictionChooser<K> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EvictionChooser")
.field("strategy", &self.strategy)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
type Entries = Vec<(String, u64)>;
fn e(key: &str, ts: u64) -> (String, u64) {
(key.to_string(), ts)
}
#[test]
fn test_lru_evicts_oldest() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Lru);
let entries: Entries = vec![e("b", 1000), e("c", 500), e("a", 2000)];
assert_eq!(evict(&entries), Some("c".to_string()));
}
#[test]
fn test_lru_single_entry() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Lru);
let entries: Entries = vec![e("only", 999)];
assert_eq!(evict(&entries), Some("only".to_string()));
}
#[test]
fn test_lru_empty_returns_none() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Lru);
let entries: Entries = vec![];
assert_eq!(evict(&entries), None);
}
#[test]
fn test_lfu_evicts_lowest_count() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Lfu);
let entries: Entries = vec![e("hot", 100), e("cold", 1), e("warm", 50)];
assert_eq!(evict(&entries), Some("cold".to_string()));
}
#[test]
fn test_fifo_evicts_first_inserted() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Fifo);
let entries: Entries = vec![e("first", 9999), e("second", 1), e("third", 5000)];
assert_eq!(evict(&entries), Some("first".to_string()));
}
#[test]
fn test_fifo_empty_returns_none() {
let evict = create_eviction_fn::<String>(EvictionStrategy::Fifo);
let entries: Entries = vec![];
assert_eq!(evict(&entries), None);
}
#[test]
fn test_eviction_chooser_lru() {
let chooser: EvictionChooser<String> = EvictionChooser::new(EvictionStrategy::Lru);
let entries: Entries = vec![e("x", 300), e("y", 100), e("z", 200)];
assert_eq!(chooser.choose(&entries), Some("y".to_string()));
}
#[test]
fn test_eviction_chooser_fifo() {
let chooser: EvictionChooser<String> = EvictionChooser::new(EvictionStrategy::Fifo);
let entries: Entries = vec![e("alpha", 1), e("beta", 2)];
assert_eq!(chooser.choose(&entries), Some("alpha".to_string()));
}
#[test]
fn test_eviction_chooser_strategy_field() {
let chooser: EvictionChooser<String> = EvictionChooser::new(EvictionStrategy::Lfu);
assert_eq!(chooser.strategy, EvictionStrategy::Lfu);
}
}