use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug)]
pub(crate) struct SieveCache<K, V> {
entries: Vec<Option<Entry<K, V>>>,
index: HashMap<K, usize>,
hand: usize,
capacity: usize,
len: usize,
}
#[derive(Debug, Clone)]
struct Entry<K, V> {
key: K,
value: V,
visited: bool,
}
impl<K, V> SieveCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
pub(crate) fn new(capacity: usize) -> Self {
assert!(capacity > 0, "SIEVE cache capacity must be > 0");
let entries = (0..capacity).map(|_| None).collect();
Self {
entries,
index: HashMap::with_capacity(capacity),
hand: 0,
capacity,
len: 0,
}
}
pub(crate) fn get(&mut self, key: &K) -> Option<&V> {
let &idx = self.index.get(key)?;
if let Some(entry) = &mut self.entries[idx] {
entry.visited = true;
Some(&entry.value)
} else {
None
}
}
pub(crate) fn insert(&mut self, key: K, value: V) {
if let Some(&idx) = self.index.get(&key) {
if let Some(entry) = &mut self.entries[idx] {
entry.value = value;
entry.visited = true;
return;
}
}
if self.len < self.capacity {
for i in 0..self.capacity {
if self.entries[i].is_none() {
self.entries[i] = Some(Entry {
key: key.clone(),
value,
visited: false,
});
self.index.insert(key, i);
self.len += 1;
return;
}
}
}
let evict_idx = self.find_eviction_target();
if let Some(old_entry) = &self.entries[evict_idx] {
self.index.remove(&old_entry.key);
}
self.entries[evict_idx] = Some(Entry {
key: key.clone(),
value,
visited: false,
});
self.index.insert(key, evict_idx);
}
#[allow(dead_code)]
pub(crate) fn remove(&mut self, key: &K) -> Option<V> {
let idx = self.index.remove(key)?;
let entry = self.entries[idx].take()?;
self.len -= 1;
Some(entry.value)
}
#[allow(dead_code)]
pub(crate) fn len(&self) -> usize {
self.len
}
fn find_eviction_target(&mut self) -> usize {
let max_iterations = self.capacity * 2;
for _ in 0..max_iterations {
if let Some(entry) = &mut self.entries[self.hand] {
if entry.visited {
entry.visited = false;
self.hand = (self.hand + 1) % self.capacity;
} else {
let target = self.hand;
self.hand = (self.hand + 1) % self.capacity;
return target;
}
} else {
let target = self.hand;
self.hand = (self.hand + 1) % self.capacity;
return target;
}
}
let target = self.hand;
self.hand = (self.hand + 1) % self.capacity;
target
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_insert_and_get() {
let mut cache = SieveCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.get(&"a"), Some(&1));
assert_eq!(cache.get(&"b"), Some(&2));
assert_eq!(cache.get(&"c"), Some(&3));
assert_eq!(cache.len(), 3);
}
#[test]
fn eviction_prefers_unvisited() {
let mut cache = SieveCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.get(&"a");
cache.get(&"c");
cache.insert("d", 4);
assert_eq!(cache.get(&"a"), Some(&1));
assert_eq!(cache.get(&"b"), None); assert_eq!(cache.get(&"c"), Some(&3));
assert_eq!(cache.get(&"d"), Some(&4));
}
#[test]
fn update_existing_key() {
let mut cache = SieveCache::new(2);
cache.insert("a", 1);
cache.insert("a", 10);
assert_eq!(cache.get(&"a"), Some(&10));
assert_eq!(cache.len(), 1);
}
#[test]
fn remove_entry() {
let mut cache = SieveCache::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.remove(&"a"), Some(1));
assert_eq!(cache.get(&"a"), None);
assert_eq!(cache.len(), 1);
}
#[test]
fn capacity_one() {
let mut cache = SieveCache::new(1);
cache.insert("a", 1);
assert_eq!(cache.get(&"a"), Some(&1));
cache.insert("b", 2);
assert_eq!(cache.get(&"a"), None);
assert_eq!(cache.get(&"b"), Some(&2));
}
#[test]
#[should_panic(expected = "capacity must be > 0")]
fn zero_capacity_panics() {
let _cache: SieveCache<&str, i32> = SieveCache::new(0);
}
#[test]
fn eviction_wraps_around() {
let mut cache = SieveCache::new(4);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
cache.insert("d", 4);
cache.get(&"a");
cache.get(&"b");
cache.get(&"c");
cache.get(&"d");
cache.insert("e", 5);
assert_eq!(cache.len(), 4);
assert_eq!(cache.get(&"e"), Some(&5));
}
}