#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct ExpiryEntry<V> {
value: V,
expires_at: u64,
}
#[derive(Debug, Clone, Default)]
pub struct ExpiryMap<K, V> {
entries: HashMap<K, ExpiryEntry<V>>,
now: u64,
}
impl<K: std::hash::Hash + Eq + Clone, V: Clone> ExpiryMap<K, V> {
pub fn new() -> Self {
ExpiryMap { entries: HashMap::new(), now: 0 }
}
pub fn tick(&mut self, ticks: u64) {
self.now += ticks;
self.entries.retain(|_, e| e.expires_at > self.now);
}
pub fn insert(&mut self, key: K, value: V, ttl: u64) {
self.entries.insert(key, ExpiryEntry { value, expires_at: self.now + ttl });
}
pub fn get(&self, key: &K) -> Option<&V> {
self.entries.get(key).and_then(|e| {
if e.expires_at > self.now { Some(&e.value) } else { None }
})
}
pub fn remove(&mut self, key: &K) -> Option<V> {
self.entries.remove(key).map(|e| e.value)
}
pub fn len(&self) -> usize {
self.entries.values().filter(|e| e.expires_at > self.now).count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn now(&self) -> u64 {
self.now
}
pub fn evict_expired(&mut self) {
self.entries.retain(|_, e| e.expires_at > self.now);
}
}
pub fn new_expiry_map<K: std::hash::Hash + Eq + Clone, V: Clone>() -> ExpiryMap<K, V> {
ExpiryMap::new()
}
pub fn em_insert<K: std::hash::Hash + Eq + Clone, V: Clone>(
map: &mut ExpiryMap<K, V>,
key: K,
value: V,
ttl: u64,
) {
map.insert(key, value, ttl);
}
pub fn em_get<'a, K: std::hash::Hash + Eq + Clone, V: Clone>(
map: &'a ExpiryMap<K, V>,
key: &K,
) -> Option<&'a V> {
map.get(key)
}
pub fn em_tick<K: std::hash::Hash + Eq + Clone, V: Clone>(map: &mut ExpiryMap<K, V>, ticks: u64) {
map.tick(ticks);
}
pub fn em_len<K: std::hash::Hash + Eq + Clone, V: Clone>(map: &ExpiryMap<K, V>) -> usize {
map.len()
}
pub fn em_remove<K: std::hash::Hash + Eq + Clone, V: Clone>(
map: &mut ExpiryMap<K, V>,
key: &K,
) -> Option<V> {
map.remove(key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_insert_get() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "k", 99, 10);
assert_eq!(em_get(&m, &"k"), Some(&99) );
}
#[test]
fn test_expiry_after_tick() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "k", 1, 5);
em_tick(&mut m, 5);
assert_eq!(em_get(&m, &"k"), None );
}
#[test]
fn test_not_expired_before_ttl() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "k", 1, 10);
em_tick(&mut m, 4);
assert_eq!(em_get(&m, &"k"), Some(&1) );
}
#[test]
fn test_remove() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "k", 7, 100);
assert_eq!(em_remove(&mut m, &"k"), Some(7) );
}
#[test]
fn test_len_counts_live() {
let mut m = new_expiry_map::<i32, i32>();
em_insert(&mut m, 1, 10, 5);
em_insert(&mut m, 2, 20, 100);
em_tick(&mut m, 5);
assert_eq!(em_len(&m), 1 );
}
#[test]
fn test_is_empty_after_expiry() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "x", 0, 1);
em_tick(&mut m, 2);
assert!(m.is_empty() );
}
#[test]
fn test_now() {
let mut m = new_expiry_map::<&str, i32>();
em_tick(&mut m, 7);
assert_eq!(m.now(), 7 );
}
#[test]
fn test_evict_expired() {
let mut m = new_expiry_map::<&str, i32>();
em_insert(&mut m, "old", 1, 1);
m.now += 5;
m.evict_expired();
assert_eq!(em_len(&m), 0 );
}
#[test]
fn test_multiple_entries() {
let mut m = new_expiry_map::<i32, &str>();
for i in 0..5 {
em_insert(&mut m, i, "v", 100);
}
assert_eq!(em_len(&m), 5 );
}
#[test]
fn test_missing_key() {
let m = new_expiry_map::<&str, i32>();
assert_eq!(em_get(&m, &"ghost"), None );
}
}