use std::collections::VecDeque;
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use hashbrown::HashMap;
pub struct SecondChanceLru<K, V> {
cache: HashMap<K, (V, AtomicBool)>,
queue: VecDeque<K>,
capacity: usize,
}
impl<K: Hash + Eq + Clone, V> SecondChanceLru<K, V> {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
cache: HashMap::with_capacity(capacity),
queue: VecDeque::with_capacity(capacity),
capacity,
}
}
#[must_use]
pub fn get(&self, key: &K) -> Option<&V> {
self.cache.get(key).map(|(val, accessed)| {
accessed.store(true, Ordering::Relaxed);
val
})
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.cache.get_mut(key).map(|(val, accessed)| {
accessed.store(true, Ordering::Relaxed);
val
})
}
#[must_use]
pub fn contains_key(&self, key: &K) -> bool {
self.cache.contains_key(key)
}
pub fn insert(&mut self, key: K, value: V) {
if let Some((existing, accessed)) = self.cache.get_mut(&key) {
*existing = value;
accessed.store(true, Ordering::Relaxed);
return;
}
if self.cache.len() >= self.capacity {
self.evict_one();
}
self.cache
.insert(key.clone(), (value, AtomicBool::new(false)));
self.queue.push_back(key);
}
pub fn remove(&mut self, key: &K) -> Option<V> {
self.cache.remove(key).map(|(v, _)| v)
}
fn evict_one(&mut self) {
let max_iterations = self.queue.len() * 2;
for _ in 0..max_iterations {
if let Some(key) = self.queue.pop_front() {
if let Some((_, accessed)) = self.cache.get(&key) {
if accessed.swap(false, Ordering::Relaxed) {
self.queue.push_back(key);
} else {
self.cache.remove(&key);
return;
}
}
} else {
return; }
}
if let Some(key) = self.queue.pop_front() {
self.cache.remove(&key);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
pub fn clear(&mut self) {
self.cache.clear();
self.queue.clear();
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.cache.keys()
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.cache.values().map(|(v, _)| v)
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.cache.iter().map(|(k, (v, _))| (k, v))
}
}
impl<K: Hash + Eq + Clone, V> Default for SecondChanceLru<K, V> {
fn default() -> Self {
Self::new(16)
}
}
impl<K: Hash + Eq + Clone, V: Clone> Clone for SecondChanceLru<K, V> {
fn clone(&self) -> Self {
let mut cache = HashMap::with_capacity(self.capacity);
for (k, (v, accessed)) in &self.cache {
cache.insert(
k.clone(),
(v.clone(), AtomicBool::new(accessed.load(Ordering::Relaxed))),
);
}
Self {
cache,
queue: self.queue.clone(),
capacity: self.capacity,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 3);
assert_eq!(*cache.get(&"a").unwrap(), 1);
assert_eq!(*cache.get(&"b").unwrap(), 2);
assert_eq!(*cache.get(&"c").unwrap(), 3);
assert!(cache.get(&"d").is_none());
}
#[test]
fn test_second_chance_eviction() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
let _ = cache.get(&"a");
cache.insert("d", 4);
assert!(
cache.get(&"a").is_some(),
"a should survive (second chance)"
);
assert!(cache.get(&"b").is_none(), "b should be evicted");
assert!(cache.get(&"c").is_some(), "c should survive");
assert!(cache.get(&"d").is_some(), "d was just inserted");
}
#[test]
fn test_capacity_respected() {
let mut cache = SecondChanceLru::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
cache.insert("c", 3);
assert_eq!(cache.len(), 2);
assert_eq!(cache.capacity(), 2);
}
#[test]
fn test_update_existing() {
let mut cache = SecondChanceLru::new(2);
cache.insert("a", 1);
cache.insert("a", 2);
assert_eq!(cache.len(), 1);
assert_eq!(*cache.get(&"a").unwrap(), 2);
}
#[test]
fn test_remove() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
let removed = cache.remove(&"a");
assert_eq!(removed, Some(1));
assert!(cache.get(&"a").is_none());
assert_eq!(cache.len(), 1);
}
#[test]
fn test_clear() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_get_mut() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
if let Some(val) = cache.get_mut(&"a") {
*val = 10;
}
assert_eq!(*cache.get(&"a").unwrap(), 10);
}
#[test]
fn test_contains_key() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
assert!(cache.contains_key(&"a"));
assert!(!cache.contains_key(&"b"));
}
#[test]
fn test_all_accessed_eviction() {
let mut cache = SecondChanceLru::new(2);
cache.insert("a", 1);
cache.insert("b", 2);
let _ = cache.get(&"a");
let _ = cache.get(&"b");
cache.insert("c", 3);
assert_eq!(cache.len(), 2);
assert!(cache.get(&"c").is_some());
}
#[test]
fn test_iterators() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
let keys: Vec<_> = cache.keys().collect();
assert_eq!(keys.len(), 2);
let values: Vec<_> = cache.values().collect();
assert_eq!(values.len(), 2);
let pairs: Vec<_> = cache.iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn test_clone() {
let mut cache = SecondChanceLru::new(3);
cache.insert("a", 1);
cache.insert("b", 2);
let _ = cache.get(&"a");
let cloned = cache.clone();
assert_eq!(cloned.len(), 2);
assert_eq!(*cloned.get(&"a").unwrap(), 1);
assert_eq!(*cloned.get(&"b").unwrap(), 2);
}
}