use std::collections::{HashMap, HashSet, VecDeque};
pub struct TwoQueueCache<K: Eq + std::hash::Hash + Clone, V> {
capacity: usize,
a1in_capacity: usize,
a1out_capacity: usize,
a1in_map: HashMap<K, V>,
a1in_queue: VecDeque<K>,
am_map: HashMap<K, V>,
am_queue: VecDeque<K>,
a1out_queue: VecDeque<K>,
a1out_set: HashSet<K>,
hits: u64,
misses: u64,
evictions: u64,
}
#[derive(Debug, Clone)]
pub struct TwoQueueStats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub a1in_len: usize,
pub am_len: usize,
pub a1out_len: usize,
pub capacity: usize,
}
impl<K: Eq + std::hash::Hash + Clone, V> TwoQueueCache<K, V> {
pub fn new(capacity: usize) -> Self {
let cap = capacity.max(2);
let a1in_cap = (cap / 4).max(1);
let a1out_cap = (cap / 2).max(1);
Self {
capacity: cap,
a1in_capacity: a1in_cap,
a1out_capacity: a1out_cap,
a1in_map: HashMap::new(),
a1in_queue: VecDeque::new(),
am_map: HashMap::new(),
am_queue: VecDeque::new(),
a1out_queue: VecDeque::new(),
a1out_set: HashSet::new(),
hits: 0,
misses: 0,
evictions: 0,
}
}
pub fn with_queue_sizes(capacity: usize, a1in_capacity: usize, a1out_capacity: usize) -> Self {
let cap = capacity.max(2);
Self {
capacity: cap,
a1in_capacity: a1in_capacity.max(1),
a1out_capacity: a1out_capacity.max(1),
a1in_map: HashMap::new(),
a1in_queue: VecDeque::new(),
am_map: HashMap::new(),
am_queue: VecDeque::new(),
a1out_queue: VecDeque::new(),
a1out_set: HashSet::new(),
hits: 0,
misses: 0,
evictions: 0,
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
if self.am_map.contains_key(key) {
self.hits += 1;
self.am_queue.retain(|k| k != key);
self.am_queue.push_back(key.clone());
return self.am_map.get(key);
}
if self.a1in_map.contains_key(key) {
self.hits += 1;
return self.a1in_map.get(key);
}
self.misses += 1;
None
}
pub fn insert(&mut self, key: K, value: V) {
if self.am_map.contains_key(&key) {
self.am_map.insert(key.clone(), value);
self.am_queue.retain(|k| k != &key);
self.am_queue.push_back(key);
return;
}
if let std::collections::hash_map::Entry::Occupied(mut e) = self.a1in_map.entry(key.clone())
{
e.insert(value);
return;
}
if self.a1out_set.contains(&key) {
self.a1out_set.remove(&key);
self.a1out_queue.retain(|k| k != &key);
self.ensure_room_am();
self.am_map.insert(key.clone(), value);
self.am_queue.push_back(key);
return;
}
self.ensure_room_a1in();
self.a1in_map.insert(key.clone(), value);
self.a1in_queue.push_back(key);
}
pub fn remove(&mut self, key: &K) -> Option<V> {
if let Some(v) = self.am_map.remove(key) {
self.am_queue.retain(|k| k != key);
return Some(v);
}
if let Some(v) = self.a1in_map.remove(key) {
self.a1in_queue.retain(|k| k != key);
return Some(v);
}
self.a1out_set.remove(key);
self.a1out_queue.retain(|k| k != key);
None
}
pub fn contains(&self, key: &K) -> bool {
self.am_map.contains_key(key) || self.a1in_map.contains_key(key)
}
pub fn len(&self) -> usize {
self.am_map.len() + self.a1in_map.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn stats(&self) -> TwoQueueStats {
TwoQueueStats {
hits: self.hits,
misses: self.misses,
evictions: self.evictions,
a1in_len: self.a1in_map.len(),
am_len: self.am_map.len(),
a1out_len: self.a1out_set.len(),
capacity: self.capacity,
}
}
pub fn peek(&self, key: &K) -> Option<&V> {
if let Some(v) = self.am_map.get(key) {
return Some(v);
}
self.a1in_map.get(key)
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
if let Some(v) = self.am_map.get_mut(key) {
self.hits += 1;
return Some(v);
}
if let Some(v) = self.a1in_map.get_mut(key) {
self.hits += 1;
return Some(v);
}
self.misses += 1;
None
}
pub fn clear(&mut self) {
self.a1in_map.clear();
self.a1in_queue.clear();
self.am_map.clear();
self.am_queue.clear();
self.a1out_queue.clear();
self.a1out_set.clear();
}
pub fn is_ghost(&self, key: &K) -> bool {
self.a1out_set.contains(key)
}
pub fn a1in_len(&self) -> usize {
self.a1in_map.len()
}
pub fn am_len(&self) -> usize {
self.am_map.len()
}
pub fn a1out_len(&self) -> usize {
self.a1out_set.len()
}
fn ensure_room_a1in(&mut self) {
while self.len() >= self.capacity {
self.evict_from_am_or_a1in();
}
while self.a1in_map.len() >= self.a1in_capacity {
self.evict_a1in_to_ghost();
}
}
fn ensure_room_am(&mut self) {
while self.len() >= self.capacity {
self.evict_from_am_or_a1in();
}
}
fn evict_a1in_to_ghost(&mut self) {
if let Some(key) = self.a1in_queue.pop_front() {
self.a1in_map.remove(&key);
self.evictions += 1;
self.a1out_queue.push_back(key.clone());
self.a1out_set.insert(key);
while self.a1out_set.len() > self.a1out_capacity {
if let Some(ghost) = self.a1out_queue.pop_front() {
self.a1out_set.remove(&ghost);
}
}
}
}
fn evict_from_am_or_a1in(&mut self) {
if !self.a1in_map.is_empty() {
self.evict_a1in_to_ghost();
} else if let Some(key) = self.am_queue.pop_front() {
self.am_map.remove(&key);
self.evictions += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_and_get() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("a", 1);
cache.insert("b", 2);
assert_eq!(cache.get(&"a"), Some(&1));
assert_eq!(cache.get(&"b"), Some(&2));
}
#[test]
fn test_miss() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
assert_eq!(cache.get(&99), None);
}
#[test]
fn test_contains() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("hello", 1);
assert!(cache.contains(&"hello"));
assert!(!cache.contains(&"world"));
}
#[test]
fn test_len_and_is_empty() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
assert!(cache.is_empty());
cache.insert(1, 10);
cache.insert(2, 20);
assert_eq!(cache.len(), 2);
}
#[test]
fn test_remove() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("x", 42);
let removed = cache.remove(&"x");
assert_eq!(removed, Some(42));
assert!(!cache.contains(&"x"));
}
#[test]
fn test_scan_resistance() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(8, 2, 8);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30); cache.insert(4, 40); cache.insert(5, 50); assert!(cache.is_ghost(&1), "key 1 should be in ghost");
assert!(cache.is_ghost(&2), "key 2 should be in ghost");
assert!(cache.is_ghost(&3), "key 3 should be in ghost");
cache.insert(1, 100);
cache.insert(2, 200);
cache.insert(3, 300);
assert!(
cache.am_len() >= 3,
"Am should have 3 entries after ghost hits"
);
for i in 1000..1050 {
cache.insert(i, i);
}
assert!(
cache.am_len() >= 3,
"Am should still have entries after scan (got {})",
cache.am_len()
);
for &k in &[1, 2, 3] {
assert!(cache.contains(&k), "hot key {k} should survive scan");
}
}
#[test]
fn test_ghost_hit_promotes_to_am() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 2);
cache.insert(1, 10); cache.insert(2, 20); cache.insert(1, 100); let s = cache.stats();
assert_eq!(s.am_len, 1, "ghost hit should put key in Am");
assert_eq!(cache.get(&1), Some(&100));
}
#[test]
fn test_stats() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
cache.insert(1, 10);
cache.get(&1); cache.get(&99); let s = cache.stats();
assert_eq!(s.hits, 1);
assert_eq!(s.misses, 1);
}
#[test]
fn test_update_in_a1in() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("k", 1);
cache.insert("k", 2);
assert_eq!(cache.get(&"k"), Some(&2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_update_in_am() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 2);
cache.insert(1, 10);
cache.insert(2, 20); cache.insert(1, 100); cache.insert(1, 200); assert_eq!(cache.get(&1), Some(&200));
}
#[test]
fn test_capacity() {
let mut cache: TwoQueueCache<usize, usize> = TwoQueueCache::new(5);
for i in 0..100 {
cache.insert(i, i);
}
assert!(cache.len() <= 5, "len {} exceeds capacity 5", cache.len());
}
#[test]
fn test_remove_absent() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
assert_eq!(cache.remove(&42), None);
}
#[test]
fn test_custom_queue_sizes() {
let cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(100, 10, 20);
let s = cache.stats();
assert_eq!(s.capacity, 100);
}
#[test]
fn test_am_lru_eviction() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
for i in 0..10 {
cache.insert(i, i);
}
for i in 0..4 {
cache.insert(i, i * 100);
}
assert!(cache.len() <= 4);
}
#[test]
fn test_am_hit_promotes_to_mru() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(6, 1, 6);
for i in 0..6 {
cache.insert(i, i);
}
for i in 0..4 {
cache.insert(i, i * 10);
}
assert!(cache.am_len() > 0, "Am should have entries");
cache.get(&0);
let val = cache.get(&0);
assert!(val.is_some(), "key 0 should be promoted to MRU");
}
#[test]
fn test_peek_no_side_effects() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("a", 1);
let val = cache.peek(&"a");
assert_eq!(val, Some(&1));
let s = cache.stats();
assert_eq!(s.hits, 0);
assert_eq!(s.misses, 0);
}
#[test]
fn test_peek_absent() {
let cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
assert_eq!(cache.peek(&99), None);
}
#[test]
fn test_get_mut() {
let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
cache.insert("a", 1);
if let Some(v) = cache.get_mut(&"a") {
*v = 42;
}
assert_eq!(cache.get(&"a"), Some(&42));
}
#[test]
fn test_get_mut_absent() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
assert!(cache.get_mut(&99).is_none());
assert_eq!(cache.stats().misses, 1);
}
#[test]
fn test_clear() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
for i in 0..10 {
cache.insert(i, i);
}
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
assert_eq!(cache.a1out_len(), 0);
}
#[test]
fn test_is_ghost() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10); cache.insert(2, 20); assert!(cache.is_ghost(&1));
assert!(!cache.is_ghost(&2));
assert!(!cache.is_ghost(&99));
}
#[test]
fn test_queue_length_accessors() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10);
assert_eq!(cache.a1in_len(), 1);
assert_eq!(cache.am_len(), 0);
cache.insert(2, 20); assert_eq!(cache.a1in_len(), 1);
assert_eq!(cache.a1out_len(), 1);
cache.insert(1, 100);
assert_eq!(cache.am_len(), 1);
assert_eq!(cache.a1out_len(), 0);
}
#[test]
fn test_large_sequential_workload() {
let cap = 20;
let mut cache: TwoQueueCache<usize, usize> = TwoQueueCache::new(cap);
for i in 0..1000 {
cache.insert(i, i * 2);
}
assert!(
cache.len() <= cap,
"cache exceeds capacity: {}",
cache.len()
);
}
#[test]
fn test_ghost_list_bounded() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 3);
for i in 0..20 {
cache.insert(i, i);
}
assert!(cache.a1out_len() <= 3, "ghost list exceeds capacity");
}
#[test]
fn test_peek_am_item() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10);
cache.insert(2, 20); cache.insert(1, 100); assert_eq!(cache.peek(&1), Some(&100));
}
#[test]
fn test_remove_from_am() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10);
cache.insert(2, 20); cache.insert(1, 100); let removed = cache.remove(&1);
assert_eq!(removed, Some(100));
assert!(!cache.contains(&1));
}
#[test]
fn test_remove_clears_ghost() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10);
cache.insert(2, 20); assert!(cache.is_ghost(&1));
cache.remove(&1); assert!(!cache.is_ghost(&1));
}
#[test]
fn test_stats_evictions() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(4);
for i in 0..10 {
cache.insert(i, i);
}
assert!(cache.stats().evictions > 0, "should have evictions");
}
#[test]
fn test_mixed_access_pattern() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(6, 2, 4);
for i in 0..6 {
cache.insert(i, i * 10);
}
for i in 0..3 {
cache.insert(i, i * 100);
}
for i in 0..3 {
let val = cache.get(&i);
assert!(val.is_some(), "key {i} should be accessible");
}
assert!(cache.stats().hits >= 3);
}
#[test]
fn test_get_mut_am_hit() {
let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
cache.insert(1, 10);
cache.insert(2, 20); cache.insert(1, 100); if let Some(v) = cache.get_mut(&1) {
*v = 999;
}
assert_eq!(cache.peek(&1), Some(&999));
assert!(cache.stats().hits >= 1);
}
}