1use std::collections::{HashMap, HashSet, VecDeque};
27
28pub struct TwoQueueCache<K: Eq + std::hash::Hash + Clone, V> {
36 capacity: usize,
38 a1in_capacity: usize,
41 a1out_capacity: usize,
43
44 a1in_map: HashMap<K, V>,
47 a1in_queue: VecDeque<K>,
49
50 am_map: HashMap<K, V>,
53 am_queue: VecDeque<K>,
55
56 a1out_queue: VecDeque<K>,
59 a1out_set: HashSet<K>,
61
62 hits: u64,
64 misses: u64,
65 evictions: u64,
66}
67
68#[derive(Debug, Clone)]
70pub struct TwoQueueStats {
71 pub hits: u64,
73 pub misses: u64,
75 pub evictions: u64,
77 pub a1in_len: usize,
79 pub am_len: usize,
81 pub a1out_len: usize,
83 pub capacity: usize,
85}
86
87impl<K: Eq + std::hash::Hash + Clone, V> TwoQueueCache<K, V> {
88 pub fn new(capacity: usize) -> Self {
92 let cap = capacity.max(2);
93 let a1in_cap = (cap / 4).max(1);
94 let a1out_cap = (cap / 2).max(1);
95 Self {
96 capacity: cap,
97 a1in_capacity: a1in_cap,
98 a1out_capacity: a1out_cap,
99 a1in_map: HashMap::new(),
100 a1in_queue: VecDeque::new(),
101 am_map: HashMap::new(),
102 am_queue: VecDeque::new(),
103 a1out_queue: VecDeque::new(),
104 a1out_set: HashSet::new(),
105 hits: 0,
106 misses: 0,
107 evictions: 0,
108 }
109 }
110
111 pub fn with_queue_sizes(capacity: usize, a1in_capacity: usize, a1out_capacity: usize) -> Self {
113 let cap = capacity.max(2);
114 Self {
115 capacity: cap,
116 a1in_capacity: a1in_capacity.max(1),
117 a1out_capacity: a1out_capacity.max(1),
118 a1in_map: HashMap::new(),
119 a1in_queue: VecDeque::new(),
120 am_map: HashMap::new(),
121 am_queue: VecDeque::new(),
122 a1out_queue: VecDeque::new(),
123 a1out_set: HashSet::new(),
124 hits: 0,
125 misses: 0,
126 evictions: 0,
127 }
128 }
129
130 pub fn get(&mut self, key: &K) -> Option<&V> {
133 if self.am_map.contains_key(key) {
135 self.hits += 1;
136 self.am_queue.retain(|k| k != key);
138 self.am_queue.push_back(key.clone());
139 return self.am_map.get(key);
140 }
141 if self.a1in_map.contains_key(key) {
143 self.hits += 1;
144 return self.a1in_map.get(key);
146 }
147 self.misses += 1;
148 None
149 }
150
151 pub fn insert(&mut self, key: K, value: V) {
157 if self.am_map.contains_key(&key) {
159 self.am_map.insert(key.clone(), value);
160 self.am_queue.retain(|k| k != &key);
161 self.am_queue.push_back(key);
162 return;
163 }
164 if let std::collections::hash_map::Entry::Occupied(mut e) = self.a1in_map.entry(key.clone())
166 {
167 e.insert(value);
168 return;
169 }
170 if self.a1out_set.contains(&key) {
172 self.a1out_set.remove(&key);
173 self.a1out_queue.retain(|k| k != &key);
174 self.ensure_room_am();
175 self.am_map.insert(key.clone(), value);
176 self.am_queue.push_back(key);
177 return;
178 }
179 self.ensure_room_a1in();
181 self.a1in_map.insert(key.clone(), value);
182 self.a1in_queue.push_back(key);
183 }
184
185 pub fn remove(&mut self, key: &K) -> Option<V> {
187 if let Some(v) = self.am_map.remove(key) {
189 self.am_queue.retain(|k| k != key);
190 return Some(v);
191 }
192 if let Some(v) = self.a1in_map.remove(key) {
194 self.a1in_queue.retain(|k| k != key);
195 return Some(v);
196 }
197 self.a1out_set.remove(key);
199 self.a1out_queue.retain(|k| k != key);
200 None
201 }
202
203 pub fn contains(&self, key: &K) -> bool {
205 self.am_map.contains_key(key) || self.a1in_map.contains_key(key)
206 }
207
208 pub fn len(&self) -> usize {
210 self.am_map.len() + self.a1in_map.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
215 self.len() == 0
216 }
217
218 pub fn stats(&self) -> TwoQueueStats {
220 TwoQueueStats {
221 hits: self.hits,
222 misses: self.misses,
223 evictions: self.evictions,
224 a1in_len: self.a1in_map.len(),
225 am_len: self.am_map.len(),
226 a1out_len: self.a1out_set.len(),
227 capacity: self.capacity,
228 }
229 }
230
231 pub fn peek(&self, key: &K) -> Option<&V> {
233 if let Some(v) = self.am_map.get(key) {
234 return Some(v);
235 }
236 self.a1in_map.get(key)
237 }
238
239 pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
242 if let Some(v) = self.am_map.get_mut(key) {
243 self.hits += 1;
244 return Some(v);
245 }
246 if let Some(v) = self.a1in_map.get_mut(key) {
247 self.hits += 1;
248 return Some(v);
249 }
250 self.misses += 1;
251 None
252 }
253
254 pub fn clear(&mut self) {
256 self.a1in_map.clear();
257 self.a1in_queue.clear();
258 self.am_map.clear();
259 self.am_queue.clear();
260 self.a1out_queue.clear();
261 self.a1out_set.clear();
262 }
263
264 pub fn is_ghost(&self, key: &K) -> bool {
266 self.a1out_set.contains(key)
267 }
268
269 pub fn a1in_len(&self) -> usize {
271 self.a1in_map.len()
272 }
273
274 pub fn am_len(&self) -> usize {
276 self.am_map.len()
277 }
278
279 pub fn a1out_len(&self) -> usize {
281 self.a1out_set.len()
282 }
283
284 fn ensure_room_a1in(&mut self) {
288 while self.len() >= self.capacity {
290 self.evict_from_am_or_a1in();
291 }
292 while self.a1in_map.len() >= self.a1in_capacity {
293 self.evict_a1in_to_ghost();
294 }
295 }
296
297 fn ensure_room_am(&mut self) {
299 while self.len() >= self.capacity {
300 self.evict_from_am_or_a1in();
301 }
302 }
303
304 fn evict_a1in_to_ghost(&mut self) {
306 if let Some(key) = self.a1in_queue.pop_front() {
307 self.a1in_map.remove(&key);
308 self.evictions += 1;
309 self.a1out_queue.push_back(key.clone());
311 self.a1out_set.insert(key);
312 while self.a1out_set.len() > self.a1out_capacity {
314 if let Some(ghost) = self.a1out_queue.pop_front() {
315 self.a1out_set.remove(&ghost);
316 }
317 }
318 }
319 }
320
321 fn evict_from_am_or_a1in(&mut self) {
327 if !self.a1in_map.is_empty() {
328 self.evict_a1in_to_ghost();
329 } else if let Some(key) = self.am_queue.pop_front() {
330 self.am_map.remove(&key);
331 self.evictions += 1;
332 }
333 }
334}
335
336#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
344 fn test_insert_and_get() {
345 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
346 cache.insert("a", 1);
347 cache.insert("b", 2);
348 assert_eq!(cache.get(&"a"), Some(&1));
349 assert_eq!(cache.get(&"b"), Some(&2));
350 }
351
352 #[test]
354 fn test_miss() {
355 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
356 assert_eq!(cache.get(&99), None);
357 }
358
359 #[test]
361 fn test_contains() {
362 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
363 cache.insert("hello", 1);
364 assert!(cache.contains(&"hello"));
365 assert!(!cache.contains(&"world"));
366 }
367
368 #[test]
370 fn test_len_and_is_empty() {
371 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
372 assert!(cache.is_empty());
373 cache.insert(1, 10);
374 cache.insert(2, 20);
375 assert_eq!(cache.len(), 2);
376 }
377
378 #[test]
380 fn test_remove() {
381 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
382 cache.insert("x", 42);
383 let removed = cache.remove(&"x");
384 assert_eq!(removed, Some(42));
385 assert!(!cache.contains(&"x"));
386 }
387
388 #[test]
390 fn test_scan_resistance() {
391 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(8, 2, 8);
393 cache.insert(1, 10);
396 cache.insert(2, 20);
397 cache.insert(3, 30); cache.insert(4, 40); cache.insert(5, 50); assert!(cache.is_ghost(&1), "key 1 should be in ghost");
402 assert!(cache.is_ghost(&2), "key 2 should be in ghost");
403 assert!(cache.is_ghost(&3), "key 3 should be in ghost");
404 cache.insert(1, 100);
406 cache.insert(2, 200);
407 cache.insert(3, 300);
408 assert!(
409 cache.am_len() >= 3,
410 "Am should have 3 entries after ghost hits"
411 );
412 for i in 1000..1050 {
414 cache.insert(i, i);
415 }
416 assert!(
418 cache.am_len() >= 3,
419 "Am should still have entries after scan (got {})",
420 cache.am_len()
421 );
422 for &k in &[1, 2, 3] {
424 assert!(cache.contains(&k), "hot key {k} should survive scan");
425 }
426 }
427
428 #[test]
430 fn test_ghost_hit_promotes_to_am() {
431 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 2);
432 cache.insert(1, 10); cache.insert(2, 20); cache.insert(1, 100); let s = cache.stats();
437 assert_eq!(s.am_len, 1, "ghost hit should put key in Am");
438 assert_eq!(cache.get(&1), Some(&100));
439 }
440
441 #[test]
443 fn test_stats() {
444 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
445 cache.insert(1, 10);
446 cache.get(&1); cache.get(&99); let s = cache.stats();
449 assert_eq!(s.hits, 1);
450 assert_eq!(s.misses, 1);
451 }
452
453 #[test]
455 fn test_update_in_a1in() {
456 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
457 cache.insert("k", 1);
458 cache.insert("k", 2);
459 assert_eq!(cache.get(&"k"), Some(&2));
460 assert_eq!(cache.len(), 1);
461 }
462
463 #[test]
465 fn test_update_in_am() {
466 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 2);
467 cache.insert(1, 10);
468 cache.insert(2, 20); cache.insert(1, 100); cache.insert(1, 200); assert_eq!(cache.get(&1), Some(&200));
472 }
473
474 #[test]
476 fn test_capacity() {
477 let mut cache: TwoQueueCache<usize, usize> = TwoQueueCache::new(5);
478 for i in 0..100 {
479 cache.insert(i, i);
480 }
481 assert!(cache.len() <= 5, "len {} exceeds capacity 5", cache.len());
482 }
483
484 #[test]
486 fn test_remove_absent() {
487 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
488 assert_eq!(cache.remove(&42), None);
489 }
490
491 #[test]
493 fn test_custom_queue_sizes() {
494 let cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(100, 10, 20);
495 let s = cache.stats();
496 assert_eq!(s.capacity, 100);
497 }
498
499 #[test]
501 fn test_am_lru_eviction() {
502 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
503 for i in 0..10 {
505 cache.insert(i, i);
506 }
507 for i in 0..4 {
509 cache.insert(i, i * 100);
510 }
511 assert!(cache.len() <= 4);
513 }
514
515 #[test]
517 fn test_am_hit_promotes_to_mru() {
518 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(6, 1, 6);
520 for i in 0..6 {
522 cache.insert(i, i);
523 }
524 for i in 0..4 {
526 cache.insert(i, i * 10);
527 }
528 assert!(cache.am_len() > 0, "Am should have entries");
529 cache.get(&0);
531 let val = cache.get(&0);
533 assert!(val.is_some(), "key 0 should be promoted to MRU");
534 }
535
536 #[test]
540 fn test_peek_no_side_effects() {
541 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
542 cache.insert("a", 1);
543 let val = cache.peek(&"a");
544 assert_eq!(val, Some(&1));
545 let s = cache.stats();
547 assert_eq!(s.hits, 0);
548 assert_eq!(s.misses, 0);
549 }
550
551 #[test]
553 fn test_peek_absent() {
554 let cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
555 assert_eq!(cache.peek(&99), None);
556 }
557
558 #[test]
560 fn test_get_mut() {
561 let mut cache: TwoQueueCache<&str, i32> = TwoQueueCache::new(10);
562 cache.insert("a", 1);
563 if let Some(v) = cache.get_mut(&"a") {
564 *v = 42;
565 }
566 assert_eq!(cache.get(&"a"), Some(&42));
567 }
568
569 #[test]
571 fn test_get_mut_absent() {
572 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
573 assert!(cache.get_mut(&99).is_none());
574 assert_eq!(cache.stats().misses, 1);
575 }
576
577 #[test]
579 fn test_clear() {
580 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(10);
581 for i in 0..10 {
582 cache.insert(i, i);
583 }
584 cache.clear();
585 assert!(cache.is_empty());
586 assert_eq!(cache.len(), 0);
587 assert_eq!(cache.a1out_len(), 0);
588 }
589
590 #[test]
592 fn test_is_ghost() {
593 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
594 cache.insert(1, 10); cache.insert(2, 20); assert!(cache.is_ghost(&1));
597 assert!(!cache.is_ghost(&2));
598 assert!(!cache.is_ghost(&99));
599 }
600
601 #[test]
603 fn test_queue_length_accessors() {
604 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
605 cache.insert(1, 10);
606 assert_eq!(cache.a1in_len(), 1);
607 assert_eq!(cache.am_len(), 0);
608 cache.insert(2, 20); assert_eq!(cache.a1in_len(), 1);
610 assert_eq!(cache.a1out_len(), 1);
611 cache.insert(1, 100);
613 assert_eq!(cache.am_len(), 1);
614 assert_eq!(cache.a1out_len(), 0);
615 }
616
617 #[test]
619 fn test_large_sequential_workload() {
620 let cap = 20;
621 let mut cache: TwoQueueCache<usize, usize> = TwoQueueCache::new(cap);
622 for i in 0..1000 {
623 cache.insert(i, i * 2);
624 }
625 assert!(
626 cache.len() <= cap,
627 "cache exceeds capacity: {}",
628 cache.len()
629 );
630 }
631
632 #[test]
634 fn test_ghost_list_bounded() {
635 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 3);
636 for i in 0..20 {
638 cache.insert(i, i);
639 }
640 assert!(cache.a1out_len() <= 3, "ghost list exceeds capacity");
641 }
642
643 #[test]
645 fn test_peek_am_item() {
646 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
647 cache.insert(1, 10);
648 cache.insert(2, 20); cache.insert(1, 100); assert_eq!(cache.peek(&1), Some(&100));
651 }
652
653 #[test]
655 fn test_remove_from_am() {
656 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
657 cache.insert(1, 10);
658 cache.insert(2, 20); cache.insert(1, 100); let removed = cache.remove(&1);
661 assert_eq!(removed, Some(100));
662 assert!(!cache.contains(&1));
663 }
664
665 #[test]
667 fn test_remove_clears_ghost() {
668 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
669 cache.insert(1, 10);
670 cache.insert(2, 20); assert!(cache.is_ghost(&1));
672 cache.remove(&1); assert!(!cache.is_ghost(&1));
674 }
675
676 #[test]
678 fn test_stats_evictions() {
679 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::new(4);
680 for i in 0..10 {
681 cache.insert(i, i);
682 }
683 assert!(cache.stats().evictions > 0, "should have evictions");
684 }
685
686 #[test]
688 fn test_mixed_access_pattern() {
689 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(6, 2, 4);
690 for i in 0..6 {
692 cache.insert(i, i * 10);
693 }
694 for i in 0..3 {
696 cache.insert(i, i * 100);
697 }
698 for i in 0..3 {
700 let val = cache.get(&i);
701 assert!(val.is_some(), "key {i} should be accessible");
702 }
703 assert!(cache.stats().hits >= 3);
704 }
705
706 #[test]
708 fn test_get_mut_am_hit() {
709 let mut cache: TwoQueueCache<i32, i32> = TwoQueueCache::with_queue_sizes(4, 1, 4);
710 cache.insert(1, 10);
711 cache.insert(2, 20); cache.insert(1, 100); if let Some(v) = cache.get_mut(&1) {
714 *v = 999;
715 }
716 assert_eq!(cache.peek(&1), Some(&999));
717 assert!(cache.stats().hits >= 1);
718 }
719}