use std::hash::Hash;
use std::sync::Arc;
use crate::ds::{FrequencyBuckets, FrequencyBucketsHandle};
#[cfg(feature = "metrics")]
use crate::metrics::metrics_impl::LfuMetrics;
#[cfg(feature = "metrics")]
use crate::metrics::snapshot::LfuMetricsSnapshot;
#[cfg(feature = "metrics")]
use crate::metrics::traits::{
CoreMetricsRecorder, LfuMetricsReadRecorder, LfuMetricsRecorder, MetricsSnapshotProvider,
};
use crate::prelude::ReadOnlyCache;
use crate::store::hashmap::HashMapStore;
use crate::store::traits::{StoreCore, StoreMut};
use crate::traits::{CoreCache, LfuCacheTrait, MutableCache};
#[derive(Debug)]
pub struct LfuCache<K, V> {
store: HashMapStore<K, Arc<V>>,
buckets: FrequencyBuckets<K>,
#[cfg(feature = "metrics")]
metrics: LfuMetrics,
}
#[derive(Debug)]
pub struct LfuHandleCache<H, V> {
store: HashMapStore<H, Arc<V>>,
buckets: FrequencyBucketsHandle<H>,
#[cfg(feature = "metrics")]
metrics: LfuMetrics,
}
#[deprecated(since = "0.2.0", note = "renamed to LfuHandleCache per RFC 430")]
pub type LFUHandleCache<H, V> = LfuHandleCache<H, V>;
impl<K, V> LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
pub fn new(capacity: usize) -> Self {
LfuCache {
store: HashMapStore::new(capacity),
buckets: FrequencyBuckets::with_capacity(capacity),
#[cfg(feature = "metrics")]
metrics: LfuMetrics::default(),
}
}
pub fn with_bucket_hint(capacity: usize, bucket_hint: usize) -> Self {
LfuCache {
store: HashMapStore::new(capacity),
buckets: FrequencyBuckets::with_capacity_and_bucket_hint(capacity, bucket_hint),
#[cfg(feature = "metrics")]
metrics: LfuMetrics::default(),
}
}
pub fn insert_batch<I>(&mut self, entries: I) -> usize
where
I: IntoIterator<Item = (K, Arc<V>)>,
{
let mut count = 0;
for (key, value) in entries {
if self.insert(key, value).is_none() {
count += 1;
}
}
count
}
pub fn remove_batch<I>(&mut self, keys: I) -> usize
where
I: IntoIterator<Item = K>,
{
let mut removed = 0;
for key in keys {
if self.remove(&key).is_some() {
removed += 1;
}
}
removed
}
pub fn touch_batch<I>(&mut self, keys: I) -> usize
where
I: IntoIterator<Item = K>,
{
let mut touched = 0;
for key in keys {
if self.increment_frequency(&key).is_some() {
touched += 1;
}
}
touched
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &Arc<V>)> {
self.buckets
.iter()
.filter_map(|(_, meta)| self.store.peek(meta.key).map(|v| (meta.key, v)))
}
fn evict_min_freq(&mut self) -> Option<(K, Arc<V>)> {
let (key, _freq) = self.buckets.pop_min()?;
self.store.record_eviction();
let value = self.store.remove(&key)?;
Some((key, value))
}
}
impl<H, V> LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
pub fn new(capacity: usize) -> Self {
LfuHandleCache {
store: HashMapStore::new(capacity),
buckets: FrequencyBucketsHandle::with_capacity(capacity),
#[cfg(feature = "metrics")]
metrics: LfuMetrics::default(),
}
}
pub fn with_bucket_hint(capacity: usize, bucket_hint: usize) -> Self {
LfuHandleCache {
store: HashMapStore::new(capacity),
buckets: FrequencyBucketsHandle::with_capacity_and_bucket_hint(capacity, bucket_hint),
#[cfg(feature = "metrics")]
metrics: LfuMetrics::default(),
}
}
pub fn insert_batch<I>(&mut self, entries: I) -> usize
where
I: IntoIterator<Item = (H, Arc<V>)>,
{
let mut count = 0;
for (handle, value) in entries {
if self.insert(handle, value).is_none() {
count += 1;
}
}
count
}
pub fn remove_batch<I>(&mut self, handles: I) -> usize
where
I: IntoIterator<Item = H>,
{
let mut removed = 0;
for handle in handles {
if self.remove(&handle).is_some() {
removed += 1;
}
}
removed
}
pub fn touch_batch<I>(&mut self, handles: I) -> usize
where
I: IntoIterator<Item = H>,
{
let mut touched = 0;
for handle in handles {
if self.increment_frequency(&handle).is_some() {
touched += 1;
}
}
touched
}
pub fn iter(&self) -> impl Iterator<Item = (&H, &Arc<V>)> {
self.buckets
.iter()
.filter_map(|(_, meta)| self.store.peek(meta.key).map(|v| (meta.key, v)))
}
fn evict_min_freq(&mut self) -> Option<(H, Arc<V>)> {
let (handle, _freq) = self.buckets.pop_min()?;
self.store.record_eviction();
let value = self.store.remove(&handle)?;
Some((handle, value))
}
}
impl<K, V> ReadOnlyCache<K, Arc<V>> for LfuCache<K, V>
where
K: Clone + Eq + Hash,
{
fn contains(&self, key: &K) -> bool {
self.store.contains(key)
}
fn len(&self) -> usize {
self.store.len()
}
fn capacity(&self) -> usize {
self.store.capacity()
}
}
impl<K, V> CoreCache<K, Arc<V>> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn insert(&mut self, key: K, value: Arc<V>) -> Option<Arc<V>> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.buckets.contains(&key) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
return self.store.try_insert(key, value).ok().flatten();
}
if self.store.capacity() == 0 {
return None;
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
if self.buckets.len() >= self.store.capacity() {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if let Some((_key, _value)) = self.evict_min_freq() {
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
}
}
if self.store.try_insert(key.clone(), value).is_err() {
return None;
}
self.buckets.insert(key);
None
}
fn get(&mut self, key: &K) -> Option<&Arc<V>> {
if !self.buckets.contains(key) {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
let _ = self.store.get(key);
return None;
}
let _ = self.buckets.touch(key);
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
self.store.get(key)
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.store.clear();
self.buckets.clear();
}
}
impl<H, V> ReadOnlyCache<H, Arc<V>> for LfuHandleCache<H, V>
where
H: Copy + Eq + Hash,
{
fn contains(&self, handle: &H) -> bool {
self.store.contains(handle)
}
fn len(&self) -> usize {
self.store.len()
}
fn capacity(&self) -> usize {
self.store.capacity()
}
}
impl<H, V> CoreCache<H, Arc<V>> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn insert(&mut self, handle: H, value: Arc<V>) -> Option<Arc<V>> {
#[cfg(feature = "metrics")]
self.metrics.record_insert_call();
if self.buckets.contains(&handle) {
#[cfg(feature = "metrics")]
self.metrics.record_insert_update();
return self.store.try_insert(handle, value).ok().flatten();
}
if self.store.capacity() == 0 {
return None;
}
#[cfg(feature = "metrics")]
self.metrics.record_insert_new();
if self.buckets.len() >= self.store.capacity() {
#[cfg(feature = "metrics")]
self.metrics.record_evict_call();
if let Some((_handle, _value)) = self.evict_min_freq() {
#[cfg(feature = "metrics")]
self.metrics.record_evicted_entry();
}
}
if self.store.try_insert(handle, value).is_err() {
return None;
}
self.buckets.insert(handle);
None
}
fn get(&mut self, handle: &H) -> Option<&Arc<V>> {
if !self.buckets.contains(handle) {
#[cfg(feature = "metrics")]
self.metrics.record_get_miss();
let _ = self.store.get(handle);
return None;
}
let _ = self.buckets.touch(handle);
#[cfg(feature = "metrics")]
self.metrics.record_get_hit();
self.store.get(handle)
}
fn clear(&mut self) {
#[cfg(feature = "metrics")]
self.metrics.record_clear();
self.store.clear();
self.buckets.clear();
}
}
impl<K, V> MutableCache<K, Arc<V>> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn remove(&mut self, key: &K) -> Option<Arc<V>> {
let _ = self.buckets.remove(key)?;
self.store.remove(key)
}
}
impl<H, V> MutableCache<H, Arc<V>> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn remove(&mut self, handle: &H) -> Option<Arc<V>> {
let _ = self.buckets.remove(handle)?;
self.store.remove(handle)
}
}
impl<K, V> LfuCacheTrait<K, Arc<V>> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn pop_lfu(&mut self) -> Option<(K, Arc<V>)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lfu_call();
let result = self.evict_min_freq();
#[cfg(feature = "metrics")]
if result.is_some() {
self.metrics.record_pop_lfu_found();
}
result
}
fn peek_lfu(&self) -> Option<(&K, &Arc<V>)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lfu_call();
let (key, _freq) = self.buckets.peek_min()?;
let value = self.store.peek(key)?;
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lfu_found();
Some((key, value))
}
fn frequency(&self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
(&self.metrics).record_frequency_call();
let result = self.buckets.frequency(key);
#[cfg(feature = "metrics")]
if result.is_some() {
(&self.metrics).record_frequency_found();
}
result
}
fn reset_frequency(&mut self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
self.metrics.record_reset_frequency_call();
let previous_freq = self.buckets.remove(key)?;
self.buckets.insert(key.clone());
#[cfg(feature = "metrics")]
self.metrics.record_reset_frequency_found();
Some(previous_freq)
}
fn increment_frequency(&mut self, key: &K) -> Option<u64> {
#[cfg(feature = "metrics")]
self.metrics.record_increment_frequency_call();
let new_freq = self.buckets.touch(key)?;
#[cfg(feature = "metrics")]
self.metrics.record_increment_frequency_found();
Some(new_freq)
}
}
impl<H, V> LfuCacheTrait<H, Arc<V>> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn pop_lfu(&mut self) -> Option<(H, Arc<V>)> {
#[cfg(feature = "metrics")]
self.metrics.record_pop_lfu_call();
let result = self.evict_min_freq();
#[cfg(feature = "metrics")]
if result.is_some() {
self.metrics.record_pop_lfu_found();
}
result
}
fn peek_lfu(&self) -> Option<(&H, &Arc<V>)> {
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lfu_call();
let (handle, _freq) = self.buckets.peek_min_ref()?;
let value = self.store.peek(handle)?;
#[cfg(feature = "metrics")]
(&self.metrics).record_peek_lfu_found();
Some((handle, value))
}
fn frequency(&self, handle: &H) -> Option<u64> {
#[cfg(feature = "metrics")]
(&self.metrics).record_frequency_call();
let result = self.buckets.frequency(handle);
#[cfg(feature = "metrics")]
if result.is_some() {
(&self.metrics).record_frequency_found();
}
result
}
fn reset_frequency(&mut self, handle: &H) -> Option<u64> {
#[cfg(feature = "metrics")]
self.metrics.record_reset_frequency_call();
let previous_freq = self.buckets.remove(handle)?;
self.buckets.insert(*handle);
#[cfg(feature = "metrics")]
self.metrics.record_reset_frequency_found();
Some(previous_freq)
}
fn increment_frequency(&mut self, handle: &H) -> Option<u64> {
#[cfg(feature = "metrics")]
self.metrics.record_increment_frequency_call();
let new_freq = self.buckets.touch(handle)?;
#[cfg(feature = "metrics")]
self.metrics.record_increment_frequency_found();
Some(new_freq)
}
}
#[cfg(feature = "metrics")]
impl<K, V> LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
pub fn metrics_snapshot(&self) -> LfuMetricsSnapshot {
LfuMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
pop_lfu_calls: self.metrics.pop_lfu_calls,
pop_lfu_found: self.metrics.pop_lfu_found,
peek_lfu_calls: self.metrics.peek_lfu_calls.get(),
peek_lfu_found: self.metrics.peek_lfu_found.get(),
frequency_calls: self.metrics.frequency_calls.get(),
frequency_found: self.metrics.frequency_found.get(),
reset_frequency_calls: self.metrics.reset_frequency_calls,
reset_frequency_found: self.metrics.reset_frequency_found,
increment_frequency_calls: self.metrics.increment_frequency_calls,
increment_frequency_found: self.metrics.increment_frequency_found,
cache_len: self.store.len(),
capacity: self.store.capacity(),
}
}
#[cfg(debug_assertions)]
#[cfg(test)]
pub(crate) fn debug_validate_invariants(&self) {
assert!(self.len() <= self.capacity());
assert_eq!(self.len(), self.buckets.len());
self.buckets.debug_validate_invariants();
}
}
#[cfg(feature = "metrics")]
impl<H, V> LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
pub fn metrics_snapshot(&self) -> LfuMetricsSnapshot {
LfuMetricsSnapshot {
get_calls: self.metrics.get_calls,
get_hits: self.metrics.get_hits,
get_misses: self.metrics.get_misses,
insert_calls: self.metrics.insert_calls,
insert_updates: self.metrics.insert_updates,
insert_new: self.metrics.insert_new,
evict_calls: self.metrics.evict_calls,
evicted_entries: self.metrics.evicted_entries,
pop_lfu_calls: self.metrics.pop_lfu_calls,
pop_lfu_found: self.metrics.pop_lfu_found,
peek_lfu_calls: self.metrics.peek_lfu_calls.get(),
peek_lfu_found: self.metrics.peek_lfu_found.get(),
frequency_calls: self.metrics.frequency_calls.get(),
frequency_found: self.metrics.frequency_found.get(),
reset_frequency_calls: self.metrics.reset_frequency_calls,
reset_frequency_found: self.metrics.reset_frequency_found,
increment_frequency_calls: self.metrics.increment_frequency_calls,
increment_frequency_found: self.metrics.increment_frequency_found,
cache_len: self.store.len(),
capacity: self.store.capacity(),
}
}
#[cfg(debug_assertions)]
#[cfg(test)]
pub(crate) fn debug_validate_invariants(&self) {
assert!(self.len() <= self.capacity());
assert_eq!(self.len(), self.buckets.len());
self.buckets.debug_validate_invariants();
}
}
#[cfg(all(test, not(feature = "metrics")))]
impl<K, V> LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
#[cfg(debug_assertions)]
pub(crate) fn debug_validate_invariants(&self) {
assert!(self.len() <= self.capacity());
assert_eq!(self.len(), self.buckets.len());
self.buckets.debug_validate_invariants();
}
}
#[cfg(all(test, not(feature = "metrics")))]
impl<H, V> LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
#[cfg(debug_assertions)]
pub(crate) fn debug_validate_invariants(&self) {
assert!(self.len() <= self.capacity());
assert_eq!(self.len(), self.buckets.len());
self.buckets.debug_validate_invariants();
}
}
#[cfg(feature = "metrics")]
impl<K, V> MetricsSnapshotProvider<LfuMetricsSnapshot> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn snapshot(&self) -> LfuMetricsSnapshot {
self.metrics_snapshot()
}
}
#[cfg(feature = "metrics")]
impl<H, V> MetricsSnapshotProvider<LfuMetricsSnapshot> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn snapshot(&self) -> LfuMetricsSnapshot {
self.metrics_snapshot()
}
}
unsafe impl<K: Send, V: Send> Send for LfuCache<K, V> {}
unsafe impl<K: Sync, V: Sync> Sync for LfuCache<K, V> {}
unsafe impl<H: Send, V: Send> Send for LfuHandleCache<H, V> {}
unsafe impl<H: Sync, V: Sync> Sync for LfuHandleCache<H, V> {}
impl<K, V> Extend<(K, Arc<V>)> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn extend<I: IntoIterator<Item = (K, Arc<V>)>>(&mut self, iter: I) {
for (key, value) in iter {
self.insert(key, value);
}
}
}
impl<K, V> FromIterator<(K, Arc<V>)> for LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
fn from_iter<I: IntoIterator<Item = (K, Arc<V>)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut cache = LfuCache::new(lower);
cache.extend(iter);
cache
}
}
impl<H, V> Extend<(H, Arc<V>)> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn extend<I: IntoIterator<Item = (H, Arc<V>)>>(&mut self, iter: I) {
for (handle, value) in iter {
self.insert(handle, value);
}
}
}
impl<H, V> FromIterator<(H, Arc<V>)> for LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
fn from_iter<I: IntoIterator<Item = (H, Arc<V>)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut cache = LfuHandleCache::new(lower);
cache.extend(iter);
cache
}
}
impl<'a, K, V> IntoIterator for &'a LfuCache<K, V>
where
K: Eq + Hash + Clone,
{
type Item = (&'a K, &'a Arc<V>);
type IntoIter = Box<dyn Iterator<Item = (&'a K, &'a Arc<V>)> + 'a>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.iter())
}
}
impl<'a, H, V> IntoIterator for &'a LfuHandleCache<H, V>
where
H: Eq + Hash + Copy,
{
type Item = (&'a H, &'a Arc<V>);
type IntoIter = Box<dyn Iterator<Item = (&'a H, &'a Arc<V>)> + 'a>;
fn into_iter(self) -> Self::IntoIter {
Box::new(self.iter())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::CoreCache;
mod basic_behavior {
use super::*;
#[test]
fn test_basic_lfu_insertion_and_retrieval() {
let mut cache = LfuCache::new(3);
assert_eq!(cache.insert("key1".to_string(), Arc::new(100)), None);
assert_eq!(cache.insert("key2".to_string(), Arc::new(200)), None);
assert_eq!(cache.insert("key3".to_string(), Arc::new(300)), None);
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.get(&"key2".to_string()).map(Arc::as_ref), Some(&200));
assert_eq!(cache.get(&"key3".to_string()).map(Arc::as_ref), Some(&300));
assert_eq!(cache.get(&"nonexistent".to_string()), None);
assert_eq!(cache.frequency(&"key1".to_string()), Some(2)); assert_eq!(cache.frequency(&"key2".to_string()), Some(2)); assert_eq!(cache.frequency(&"key3".to_string()), Some(2)); }
#[test]
fn test_handle_lfu_basic_flow() {
let mut cache: LfuHandleCache<u64, i32> = LfuHandleCache::new(2);
assert_eq!(cache.insert(1, Arc::new(10)), None);
assert_eq!(cache.insert(2, Arc::new(20)), None);
assert_eq!(cache.get(&1).map(Arc::as_ref), Some(&10));
assert_eq!(cache.frequency(&1), Some(2));
cache.insert(3, Arc::new(30));
assert_eq!(cache.len(), 2);
#[cfg(debug_assertions)]
cache.debug_validate_invariants();
}
#[test]
fn test_lfu_batch_ops() {
let mut cache: LfuCache<String, i32> = LfuCache::new(3);
let inserted = cache.insert_batch([
("a".to_string(), Arc::new(1)),
("b".to_string(), Arc::new(2)),
]);
assert_eq!(inserted, 2);
assert_eq!(cache.touch_batch(["a".to_string(), "z".to_string()]), 1);
assert_eq!(cache.remove_batch(["b".to_string(), "z".to_string()]), 1);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_handle_lfu_batch_ops() {
let mut cache: LfuHandleCache<u64, i32> = LfuHandleCache::new(3);
let inserted = cache.insert_batch([(1, Arc::new(1)), (2, Arc::new(2))]);
assert_eq!(inserted, 2);
assert_eq!(cache.touch_batch([1, 3]), 1);
assert_eq!(cache.remove_batch([2, 3]), 1);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_lfu_eviction_order() {
let mut cache = LfuCache::new(3);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
cache.get(&"key2".to_string()); cache.get(&"key2".to_string()); cache.get(&"key3".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(1)); assert_eq!(cache.frequency(&"key2".to_string()), Some(3)); assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
cache.insert("key4".to_string(), Arc::new(400));
assert!(!cache.contains(&"key1".to_string()));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), None);
assert!(cache.contains(&"key2".to_string()));
assert!(cache.contains(&"key3".to_string()));
assert!(cache.contains(&"key4".to_string()));
assert_eq!(cache.len(), 3);
}
#[test]
fn test_capacity_enforcement() {
let mut cache = LfuCache::new(2);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 2);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.len(), 1);
assert!(cache.len() <= cache.capacity());
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.len(), 2);
assert!(cache.len() <= cache.capacity());
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 2); assert!(cache.len() <= cache.capacity());
for i in 4..=10 {
cache.insert(format!("key{}", i), Arc::new(i * 100));
assert!(cache.len() <= cache.capacity());
assert_eq!(cache.len(), 2);
}
let mut zero_cache = LfuCache::new(0);
assert_eq!(zero_cache.capacity(), 0);
assert_eq!(zero_cache.len(), 0);
zero_cache.insert("key".to_string(), Arc::new(100));
assert_eq!(zero_cache.len(), 0); assert!(zero_cache.len() <= zero_cache.capacity());
}
#[test]
fn test_update_existing_key() {
let mut cache = LfuCache::new(3);
assert_eq!(cache.insert("key1".to_string(), Arc::new(100)), None);
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
cache.get(&"key1".to_string());
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
let old_value = cache.insert("key1".to_string(), Arc::new(999));
assert_eq!(old_value.as_deref(), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&999));
assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
assert_eq!(cache.len(), 1);
cache.insert("key2".to_string(), Arc::new(200)); cache.insert("key3".to_string(), Arc::new(300));
cache.insert("key1".to_string(), Arc::new(1999));
assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
cache.insert("key4".to_string(), Arc::new(400));
assert!(cache.contains(&"key1".to_string()));
assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&1999));
}
#[test]
fn test_frequency_tracking() {
let mut cache = LfuCache::new(5);
cache.insert("a".to_string(), Arc::new(1));
cache.insert("b".to_string(), Arc::new(2));
cache.insert("c".to_string(), Arc::new(3));
assert_eq!(cache.frequency(&"a".to_string()), Some(1));
assert_eq!(cache.frequency(&"b".to_string()), Some(1));
assert_eq!(cache.frequency(&"c".to_string()), Some(1));
cache.get(&"a".to_string());
cache.get(&"a".to_string());
cache.get(&"a".to_string());
assert_eq!(cache.frequency(&"a".to_string()), Some(4));
cache.get(&"b".to_string());
assert_eq!(cache.frequency(&"b".to_string()), Some(2));
assert_eq!(cache.frequency(&"c".to_string()), Some(1));
let old_freq = cache.reset_frequency(&"a".to_string());
assert_eq!(old_freq, Some(4));
assert_eq!(cache.frequency(&"a".to_string()), Some(1));
let new_freq = cache.increment_frequency(&"b".to_string());
assert_eq!(new_freq, Some(3));
assert_eq!(cache.frequency(&"b".to_string()), Some(3));
assert_eq!(cache.frequency(&"nonexistent".to_string()), None);
assert_eq!(cache.reset_frequency(&"nonexistent".to_string()), None);
assert_eq!(cache.increment_frequency(&"nonexistent".to_string()), None);
let (lfu_key, _) = cache.peek_lfu().unwrap();
assert!(lfu_key == &"a".to_string() || lfu_key == &"c".to_string());
cache.remove(&"b".to_string());
assert_eq!(cache.frequency(&"b".to_string()), None);
cache.clear();
assert_eq!(cache.frequency(&"a".to_string()), None);
assert_eq!(cache.frequency(&"c".to_string()), None);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_key_operations_consistency() {
let mut cache = LfuCache::new(4);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"any_key".to_string()));
assert_eq!(cache.get(&"any_key".to_string()), None);
let keys = vec!["key1", "key2", "key3"];
let values = [100, 200, 300];
for (i, (&key, &value)) in keys.iter().zip(values.iter()).enumerate() {
cache.insert(key.to_string(), Arc::new(value));
assert_eq!(cache.len(), i + 1);
assert!(cache.contains(&key.to_string()));
assert_eq!(cache.get(&key.to_string()).map(Arc::as_ref), Some(&value));
}
for (&key, &value) in keys.iter().zip(values.iter()) {
assert!(cache.contains(&key.to_string()));
assert_eq!(cache.get(&key.to_string()).map(Arc::as_ref), Some(&value));
assert!(cache.frequency(&key.to_string()).is_some());
}
cache.remove(&"key2".to_string());
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&"key2".to_string()));
assert_eq!(cache.get(&"key2".to_string()), None);
assert_eq!(cache.frequency(&"key2".to_string()), None);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key3".to_string()));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.get(&"key3".to_string()).map(Arc::as_ref), Some(&300));
cache.insert("key4".to_string(), Arc::new(400));
cache.insert("key5".to_string(), Arc::new(500));
assert_eq!(cache.len(), 4);
let mut remaining_count = 0;
for &key in &keys {
if cache.contains(&key.to_string()) {
remaining_count += 1;
assert!(cache.get(&key.to_string()).is_some());
} else {
assert_eq!(cache.get(&key.to_string()), None);
}
}
assert!(remaining_count < keys.len());
assert!(cache.contains(&"key4".to_string()));
assert!(cache.contains(&"key5".to_string()));
cache.clear();
assert_eq!(cache.len(), 0);
for &key in &["key1", "key3", "key4", "key5"] {
assert!(!cache.contains(&key.to_string()));
assert_eq!(cache.get(&key.to_string()), None);
assert_eq!(cache.frequency(&key.to_string()), None);
}
}
}
mod edge_cases {
use super::*;
#[test]
fn test_empty_cache_operations() {
let mut cache = LfuCache::<String, i32>::new(5);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 5);
assert!(!cache.contains(&"nonexistent".to_string()));
assert_eq!(cache.get(&"nonexistent".to_string()), None);
assert_eq!(cache.frequency(&"nonexistent".to_string()), None);
assert_eq!(cache.remove(&"nonexistent".to_string()), None);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.increment_frequency(&"nonexistent".to_string()), None);
assert_eq!(cache.reset_frequency(&"nonexistent".to_string()), None);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_single_item_cache() {
let mut cache = LfuCache::new(1);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 1);
assert_eq!(cache.insert("key1".to_string(), Arc::new(100)), None);
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"key1".to_string()));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(2));
assert_eq!(cache.insert("key2".to_string(), Arc::new(200)), None);
assert_eq!(cache.len(), 1);
assert!(!cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key2".to_string()));
assert_eq!(cache.get(&"key2".to_string()).map(Arc::as_ref), Some(&200));
let old_value = cache.insert("key2".to_string(), Arc::new(999));
assert_eq!(old_value.as_deref(), Some(&200));
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"key2".to_string()).map(Arc::as_ref), Some(&999));
assert_eq!(
cache.peek_lfu().map(|(key, value)| (key.clone(), **value)),
Some(("key2".to_string(), 999))
);
assert_eq!(
cache.pop_lfu().map(|(key, value)| (key, *value)),
Some(("key2".to_string(), 999))
);
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_lfu(), None);
}
#[test]
fn test_zero_capacity_cache() {
let mut cache = LfuCache::<String, i32>::new(0);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 0);
assert_eq!(cache.insert("key1".to_string(), Arc::new(100)), None);
assert_eq!(cache.insert("key2".to_string(), Arc::new(200)), None);
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&"key1".to_string()));
assert_eq!(cache.get(&"key1".to_string()), None);
assert_eq!(cache.frequency(&"key1".to_string()), None);
assert_eq!(cache.remove(&"key1".to_string()), None);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.increment_frequency(&"key1".to_string()), None);
assert_eq!(cache.reset_frequency(&"key1".to_string()), None);
cache.clear();
assert_eq!(cache.len(), 0);
}
#[test]
fn test_same_frequency_items() {
let mut cache = LfuCache::new(3);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
assert_eq!(cache.frequency(&"key3".to_string()), Some(1));
let initial_keys = ["key1", "key2", "key3"];
cache.insert("key4".to_string(), Arc::new(400));
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"key4".to_string()));
assert_eq!(cache.frequency(&"key4".to_string()), Some(1));
let remaining_count = initial_keys
.iter()
.map(|k| cache.contains(&k.to_string()))
.filter(|&exists| exists)
.count();
assert_eq!(remaining_count, 2);
if let Some((key, _)) = cache.peek_lfu() {
assert_eq!(cache.frequency(key), Some(1));
}
if let Some((key, _)) = cache.pop_lfu() {
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&key));
}
}
#[test]
fn test_frequency_overflow_protection() {
let mut cache = LfuCache::new(2);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
for _ in 0..1000 {
cache.get(&"key1".to_string());
}
let freq = cache.frequency(&"key1".to_string()).unwrap();
assert!(freq > 1000);
let freq_before = cache.frequency(&"key1".to_string()).unwrap();
let freq_after_increment = cache.increment_frequency(&"key1".to_string()).unwrap();
let freq_after = cache.frequency(&"key1".to_string()).unwrap();
assert_eq!(freq_after_increment, freq_before + 1);
assert_eq!(freq_after, freq_before + 1);
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.len(), 2);
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 2);
assert!(cache.contains(&"key1".to_string())); assert!(!cache.contains(&"key2".to_string())); assert!(cache.contains(&"key3".to_string())); }
#[test]
fn test_duplicate_key_insertion() {
let mut cache = LfuCache::new(3);
assert_eq!(cache.insert("key1".to_string(), Arc::new(100)), None);
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
cache.get(&"key1".to_string());
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(
cache.insert("key1".to_string(), Arc::new(999)).as_deref(),
Some(&100)
);
assert_eq!(cache.len(), 1); assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&999)); assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
assert_eq!(
cache.insert("key1".to_string(), Arc::new(777)).as_deref(),
Some(&999)
);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&777));
assert_eq!(cache.frequency(&"key1".to_string()), Some(5));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 3);
cache.insert("key4".to_string(), Arc::new(400));
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"key1".to_string()));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&777));
let key2_exists = cache.contains(&"key2".to_string());
let key3_exists = cache.contains(&"key3".to_string());
assert!(!(key2_exists && key3_exists)); assert!(cache.contains(&"key4".to_string())); }
#[test]
#[cfg_attr(miri, ignore)]
fn test_large_cache_operations() {
let capacity = 10000;
let mut cache = LfuCache::new(capacity);
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), capacity);
for i in 0..capacity {
let key = format!("key_{}", i);
assert_eq!(cache.insert(key, Arc::new(i)), None);
}
assert_eq!(cache.len(), capacity);
for i in 0..capacity {
let key = format!("key_{}", i);
assert!(cache.contains(&key));
assert_eq!(cache.get(&key).map(Arc::as_ref), Some(&i));
assert_eq!(cache.frequency(&key), Some(2)); }
let new_key = "new_key".to_string();
assert_eq!(cache.insert(new_key.clone(), Arc::new(99999)), None);
assert_eq!(cache.len(), capacity); assert!(cache.contains(&new_key));
let remaining_original = (0..capacity)
.map(|i| format!("key_{}", i))
.filter(|key| cache.contains(key))
.count();
assert_eq!(remaining_original, capacity - 1);
cache.clear();
assert_eq!(cache.len(), 0);
assert!(!cache.contains(&new_key));
cache.insert("after_clear".to_string(), Arc::new(42));
assert_eq!(cache.len(), 1);
assert!(cache.contains(&"after_clear".to_string()));
}
}
mod lfu_operations {
use super::*;
#[test]
fn test_pop_lfu_basic() {
let mut cache = LfuCache::new(4);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
cache.get(&"key2".to_string());
cache.get(&"key2".to_string());
cache.get(&"key3".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.frequency(&"key2".to_string()), Some(3));
assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
let (key, value) = cache.pop_lfu().unwrap();
assert_eq!(key, "key1".to_string());
assert_eq!(*value, 100);
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&"key1".to_string()));
let (key, value) = cache.pop_lfu().unwrap();
assert_eq!(key, "key3".to_string());
assert_eq!(*value, 300);
assert_eq!(cache.len(), 1);
let (key, value) = cache.pop_lfu().unwrap();
assert_eq!(key, "key2".to_string());
assert_eq!(*value, 200);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_peek_lfu_basic() {
let mut cache = LfuCache::new(4);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
cache.get(&"key2".to_string());
cache.get(&"key2".to_string());
cache.get(&"key3".to_string());
let (key, value) = cache.peek_lfu().unwrap();
assert_eq!(key, &"key1".to_string());
assert_eq!(value.as_ref(), &100);
assert_eq!(cache.len(), 3); assert!(cache.contains(&"key1".to_string()));
let (key2, value2) = cache.peek_lfu().unwrap();
assert_eq!(key2, &"key1".to_string());
assert_eq!(value2.as_ref(), &100);
cache.remove(&"key1".to_string());
let (key, value) = cache.peek_lfu().unwrap();
assert_eq!(key, &"key3".to_string());
assert_eq!(value.as_ref(), &300);
assert_eq!(cache.len(), 2);
cache.remove(&"key3".to_string());
let (key, value) = cache.peek_lfu().unwrap();
assert_eq!(key, &"key2".to_string());
assert_eq!(value.as_ref(), &200);
assert_eq!(cache.len(), 1);
}
#[test]
fn test_frequency_retrieval() {
let mut cache = LfuCache::new(5);
assert_eq!(cache.frequency(&"nonexistent".to_string()), None);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(2));
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
for _ in 0..5 {
cache.get(&"key2".to_string());
}
assert_eq!(cache.frequency(&"key2".to_string()), Some(6));
cache.insert("key1".to_string(), Arc::new(999));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
cache.remove(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), None);
assert_eq!(cache.frequency(&"key2".to_string()), Some(6)); }
#[test]
fn test_reset_frequency() {
let mut cache = LfuCache::new(3);
assert_eq!(cache.reset_frequency(&"nonexistent".to_string()), None);
cache.insert("key1".to_string(), Arc::new(100));
cache.get(&"key1".to_string());
cache.get(&"key1".to_string());
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
let old_freq = cache.reset_frequency(&"key1".to_string());
assert_eq!(old_freq, Some(4));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
let old_freq = cache.reset_frequency(&"key1".to_string());
assert_eq!(old_freq, Some(1));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
cache.insert("key2".to_string(), Arc::new(200));
for _ in 0..10 {
cache.get(&"key2".to_string());
}
assert_eq!(cache.frequency(&"key2".to_string()), Some(11));
let old_freq = cache.reset_frequency(&"key2".to_string());
assert_eq!(old_freq, Some(11));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 3);
cache.insert("key4".to_string(), Arc::new(400)); assert_eq!(cache.len(), 3);
}
#[test]
fn test_increment_frequency() {
let mut cache = LfuCache::new(3);
assert_eq!(cache.increment_frequency(&"nonexistent".to_string()), None);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
let new_freq = cache.increment_frequency(&"key1".to_string());
assert_eq!(new_freq, Some(2));
assert_eq!(cache.frequency(&"key1".to_string()), Some(2));
for i in 3..=7 {
let freq = cache.increment_frequency(&"key1".to_string());
assert_eq!(freq, Some(i));
assert_eq!(cache.frequency(&"key1".to_string()), Some(i));
}
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
let freq = cache.increment_frequency(&"key2".to_string());
assert_eq!(freq, Some(2));
assert_eq!(cache.frequency(&"key1".to_string()), Some(7));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.frequency(&"key3".to_string()), Some(1));
let (key, _) = cache.peek_lfu().unwrap();
assert_eq!(key, &"key3".to_string());
cache.increment_frequency(&"key3".to_string());
assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
let (key, _) = cache.peek_lfu().unwrap();
assert!(key == &"key2".to_string() || key == &"key3".to_string());
assert_eq!(cache.frequency(key).unwrap(), 2);
}
#[test]
fn test_pop_lfu_empty_cache() {
let mut cache = LfuCache::<String, i32>::new(5);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.len(), 0);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.len(), 1);
let (key, value) = cache.pop_lfu().unwrap();
assert_eq!(key, "key1".to_string());
assert_eq!(*value, 100);
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_lfu(), None);
cache.insert("a".to_string(), Arc::new(1));
cache.insert("b".to_string(), Arc::new(2));
cache.insert("c".to_string(), Arc::new(3));
assert_eq!(cache.len(), 3);
assert!(cache.pop_lfu().is_some());
assert!(cache.pop_lfu().is_some());
assert!(cache.pop_lfu().is_some());
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_lfu(), None);
}
#[test]
fn test_peek_lfu_empty_cache() {
let cache = LfuCache::<String, i32>::new(5);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.len(), 0);
let zero_cache = LfuCache::<String, i32>::new(0);
assert_eq!(zero_cache.peek_lfu(), None);
assert_eq!(zero_cache.len(), 0);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.peek_lfu(), None);
let mut cache2 = LfuCache::new(3);
cache2.insert("temp".to_string(), Arc::new(999));
assert!(cache2.peek_lfu().is_some());
cache2.clear();
assert_eq!(cache2.peek_lfu(), None);
assert_eq!(cache2.len(), 0);
let mut cache3 = LfuCache::new(2);
cache3.insert("a".to_string(), Arc::new(1));
cache3.insert("b".to_string(), Arc::new(2));
assert!(cache3.peek_lfu().is_some());
cache3.remove(&"a".to_string());
cache3.remove(&"b".to_string());
assert_eq!(cache3.peek_lfu(), None);
assert_eq!(cache3.len(), 0);
}
#[test]
fn test_lfu_tie_breaking() {
let mut cache = LfuCache::new(5);
cache.insert("low1".to_string(), Arc::new(1)); cache.insert("low2".to_string(), Arc::new(2)); cache.insert("medium".to_string(), Arc::new(3)); cache.insert("high".to_string(), Arc::new(4));
cache.get(&"medium".to_string()); cache.get(&"high".to_string()); cache.get(&"high".to_string());
assert_eq!(cache.frequency(&"low1".to_string()), Some(1));
assert_eq!(cache.frequency(&"low2".to_string()), Some(1));
assert_eq!(cache.frequency(&"medium".to_string()), Some(2));
assert_eq!(cache.frequency(&"high".to_string()), Some(3));
let (peek_key, peek_value) = cache.peek_lfu().unwrap();
let peek_key_owned = peek_key.clone();
let peek_value_owned = **peek_value;
let (pop_key, pop_value) = cache.pop_lfu().unwrap();
assert_eq!(peek_key_owned, pop_key);
assert_eq!(peek_value_owned, *pop_value);
assert!(pop_key == "low1" || pop_key == "low2");
assert_eq!(cache.len(), 3);
let (second_key, _) = cache.pop_lfu().unwrap();
assert!(second_key == "low1" || second_key == "low2");
assert_ne!(pop_key, second_key); assert_eq!(cache.len(), 2);
let (third_key, third_value) = cache.pop_lfu().unwrap();
assert_eq!(third_key, "medium".to_string());
assert_eq!(*third_value, 3);
assert_eq!(cache.len(), 1);
let (last_key, last_value) = cache.pop_lfu().unwrap();
assert_eq!(last_key, "high".to_string());
assert_eq!(*last_value, 4);
assert_eq!(cache.len(), 0);
cache.insert("a".to_string(), Arc::new(1));
cache.insert("b".to_string(), Arc::new(2));
cache.insert("c".to_string(), Arc::new(3));
assert_eq!(cache.frequency(&"a".to_string()), Some(1));
assert_eq!(cache.frequency(&"b".to_string()), Some(1));
assert_eq!(cache.frequency(&"c".to_string()), Some(1));
let mut popped_keys = vec![
cache.pop_lfu().unwrap().0,
cache.pop_lfu().unwrap().0,
cache.pop_lfu().unwrap().0,
];
popped_keys.sort();
assert_eq!(
popped_keys,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_frequency_after_removal() {
let mut cache = LfuCache::new(5);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
for _ in 0..5 {
cache.get(&"key1".to_string());
}
for _ in 0..3 {
cache.get(&"key2".to_string());
}
cache.get(&"key3".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(6)); assert_eq!(cache.frequency(&"key2".to_string()), Some(4)); assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
let removed_value = cache.remove(&"key1".to_string());
assert_eq!(removed_value.as_deref(), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), None);
assert_eq!(cache.len(), 2);
assert_eq!(cache.frequency(&"key2".to_string()), Some(4));
assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
let (lfu_key, _) = cache.peek_lfu().unwrap();
assert_eq!(lfu_key, &"key3".to_string());
let (popped_key, popped_value) = cache.pop_lfu().unwrap();
assert_eq!(popped_key, "key3".to_string());
assert_eq!(*popped_value, 300);
assert_eq!(cache.frequency(&"key3".to_string()), None);
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"key2".to_string()), Some(4));
assert!(cache.contains(&"key2".to_string()));
cache.remove(&"key2".to_string());
assert_eq!(cache.frequency(&"key2".to_string()), None);
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.pop_lfu(), None);
cache.insert("key1".to_string(), Arc::new(999));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1)); }
#[test]
fn test_frequency_after_clear() {
let mut cache = LfuCache::new(5);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
for _ in 0..10 {
cache.get(&"key1".to_string());
}
for _ in 0..5 {
cache.get(&"key2".to_string());
}
for _ in 0..7 {
cache.get(&"key3".to_string());
}
assert_eq!(cache.frequency(&"key1".to_string()), Some(11)); assert_eq!(cache.frequency(&"key2".to_string()), Some(6)); assert_eq!(cache.frequency(&"key3".to_string()), Some(8)); assert_eq!(cache.len(), 3);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.frequency(&"key1".to_string()), None);
assert_eq!(cache.frequency(&"key2".to_string()), None);
assert_eq!(cache.frequency(&"key3".to_string()), None);
assert!(!cache.contains(&"key1".to_string()));
assert!(!cache.contains(&"key2".to_string()));
assert!(!cache.contains(&"key3".to_string()));
cache.insert("key1".to_string(), Arc::new(999));
cache.insert("new_key".to_string(), Arc::new(888));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.frequency(&"new_key".to_string()), Some(1));
assert_eq!(cache.len(), 2);
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(2));
let (lfu_key, _) = cache.peek_lfu().unwrap();
assert_eq!(lfu_key, &"new_key".to_string());
cache.clear();
assert_eq!(cache.len(), 0);
cache.clear(); assert_eq!(cache.len(), 0);
}
#[test]
fn test_bucket_link_updates_on_middle_removal() {
let mut cache = LfuCache::new(4);
cache.insert("low".to_string(), Arc::new(1));
cache.insert("mid".to_string(), Arc::new(2));
cache.insert("high".to_string(), Arc::new(3));
cache.get(&"mid".to_string()); cache.get(&"high".to_string());
cache.get(&"high".to_string());
#[cfg(debug_assertions)]
#[cfg(debug_assertions)]
cache.debug_validate_invariants();
cache.remove(&"mid".to_string());
#[cfg(debug_assertions)]
#[cfg(debug_assertions)]
cache.debug_validate_invariants();
let (lfu_key, _) = cache.peek_lfu().unwrap();
assert_eq!(lfu_key, &"low".to_string());
}
}
mod state_consistency {
use super::*;
#[test]
fn test_cache_frequency_consistency() {
let mut cache = LfuCache::new(5);
assert_eq!(cache.len(), 0);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
assert_eq!(cache.frequency(&"key3".to_string()), Some(1));
cache.get(&"key1".to_string()); cache.get(&"key1".to_string()); cache.get(&"key2".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(cache.frequency(&"key2".to_string()), Some(2));
assert_eq!(cache.frequency(&"key3".to_string()), Some(1));
cache.insert("key1".to_string(), Arc::new(999));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
cache.increment_frequency(&"key3".to_string());
assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
cache.reset_frequency(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key2".to_string()));
assert!(cache.contains(&"key3".to_string()));
let (lfu_key, _) = cache.peek_lfu().unwrap();
let lfu_freq = cache.frequency(lfu_key).unwrap();
assert_eq!(lfu_freq, 1); }
#[test]
fn test_len_consistency() {
let mut cache = LfuCache::new(4);
assert_eq!(cache.len(), 0);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.len(), 1);
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.len(), 2);
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 3);
cache.insert("key1".to_string(), Arc::new(999));
assert_eq!(cache.len(), 3);
cache.insert("key4".to_string(), Arc::new(400));
assert_eq!(cache.len(), 4);
cache.insert("key5".to_string(), Arc::new(500));
assert_eq!(cache.len(), 4);
cache.remove(&"key5".to_string());
assert_eq!(cache.len(), 3);
for candidate in ["key1", "key2", "key3", "key4"] {
if cache.contains(&candidate.to_string()) {
cache.remove(&candidate.to_string());
break;
}
}
assert_eq!(cache.len(), 2);
cache.remove(&"nonexistent".to_string());
assert_eq!(cache.len(), 2);
let _ = cache.pop_lfu();
assert_eq!(cache.len(), 1);
let _ = cache.pop_lfu();
assert_eq!(cache.len(), 0);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.len(), 0);
cache.insert("test1".to_string(), Arc::new(1));
cache.insert("test2".to_string(), Arc::new(2));
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
assert_eq!(cache.len(), 2);
cache.get(&"key1".to_string());
cache.get(&"key2".to_string());
cache.get(&"nonexistent".to_string());
assert_eq!(cache.len(), 2); }
#[test]
fn test_capacity_consistency() {
let capacities = [0, 1, 3, 10, 100];
for &capacity in &capacities {
let mut cache = LfuCache::<String, i32>::new(capacity);
assert_eq!(cache.capacity(), capacity);
if capacity > 0 {
for i in 0..capacity {
cache.insert(format!("key{}", i), Arc::new(i as i32));
assert_eq!(cache.capacity(), capacity); assert!(cache.len() <= capacity); }
for i in capacity..(capacity + 5) {
cache.insert(format!("key{}", i), Arc::new(i as i32));
assert_eq!(cache.capacity(), capacity); assert_eq!(cache.len(), capacity); }
cache.get(&format!("key{}", capacity - 1));
assert_eq!(cache.capacity(), capacity);
cache.remove(&format!("key{}", capacity - 1));
assert_eq!(cache.capacity(), capacity);
let _ = cache.pop_lfu();
assert_eq!(cache.capacity(), capacity);
cache.clear();
assert_eq!(cache.capacity(), capacity);
} else {
assert_eq!(cache.capacity(), 0);
cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(cache.len(), 0); assert_eq!(cache.capacity(), 0); }
}
let mut cache = LfuCache::new(5);
let original_capacity = cache.capacity();
for i in 0..100 {
match i % 4 {
0 => {
cache.insert(format!("key{}", i % 10), Arc::new(i));
},
1 => {
cache.get(&format!("key{}", i % 10));
},
2 => {
cache.remove(&format!("key{}", i % 10));
},
3 => {
let _ = cache.pop_lfu();
},
_ => unreachable!(),
}
assert_eq!(cache.capacity(), original_capacity);
assert!(cache.len() <= cache.capacity());
}
}
#[test]
fn test_clear_resets_all_state() {
let mut cache = LfuCache::new(5);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
cache.insert("key4".to_string(), Arc::new(400));
cache.insert("key5".to_string(), Arc::new(500));
for _ in 0..10 {
cache.get(&"key1".to_string());
}
for _ in 0..5 {
cache.get(&"key2".to_string());
}
for _ in 0..3 {
cache.get(&"key3".to_string());
}
cache.get(&"key4".to_string());
assert_eq!(cache.len(), 5);
assert_eq!(cache.frequency(&"key1".to_string()), Some(11)); assert_eq!(cache.frequency(&"key2".to_string()), Some(6)); assert_eq!(cache.frequency(&"key3".to_string()), Some(4)); assert_eq!(cache.frequency(&"key4".to_string()), Some(2)); assert_eq!(cache.frequency(&"key5".to_string()), Some(1));
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 5);
assert!(!cache.contains(&"key1".to_string()));
assert!(!cache.contains(&"key2".to_string()));
assert!(!cache.contains(&"key3".to_string()));
assert!(!cache.contains(&"key4".to_string()));
assert!(!cache.contains(&"key5".to_string()));
assert_eq!(cache.frequency(&"key1".to_string()), None);
assert_eq!(cache.frequency(&"key2".to_string()), None);
assert_eq!(cache.frequency(&"key3".to_string()), None);
assert_eq!(cache.frequency(&"key4".to_string()), None);
assert_eq!(cache.frequency(&"key5".to_string()), None);
assert_eq!(cache.get(&"key1".to_string()), None);
assert_eq!(cache.get(&"key2".to_string()), None);
assert_eq!(cache.pop_lfu(), None);
assert_eq!(cache.peek_lfu(), None);
cache.insert("new_key".to_string(), Arc::new(999));
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"new_key".to_string()), Some(1));
assert_eq!(
cache.get(&"new_key".to_string()).map(Arc::as_ref),
Some(&999)
);
cache.clear();
assert_eq!(cache.len(), 0);
cache.clear(); assert_eq!(cache.len(), 0);
assert_eq!(cache.capacity(), 5);
cache.insert("test1".to_string(), Arc::new(1));
cache.insert("test2".to_string(), Arc::new(2));
assert_eq!(cache.len(), 2);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.frequency(&"test1".to_string()), None);
assert_eq!(cache.frequency(&"test2".to_string()), None);
}
#[test]
fn test_remove_consistency() {
let mut cache = LfuCache::new(5);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
cache.insert("key4".to_string(), Arc::new(400));
cache.get(&"key1".to_string()); cache.get(&"key1".to_string()); cache.get(&"key2".to_string()); cache.get(&"key3".to_string());
assert_eq!(cache.len(), 4);
let removed_value = cache.remove(&"key2".to_string());
assert_eq!(removed_value.as_deref(), Some(&200));
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"key2".to_string()));
assert_eq!(cache.get(&"key2".to_string()), None);
assert_eq!(cache.frequency(&"key2".to_string()), None);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key3".to_string()));
assert!(cache.contains(&"key4".to_string()));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(cache.frequency(&"key3".to_string()), Some(2));
assert_eq!(cache.frequency(&"key4".to_string()), Some(1));
let removed_none = cache.remove(&"nonexistent".to_string());
assert_eq!(removed_none, None);
assert_eq!(cache.len(), 3);
let removed_high_freq = cache.remove(&"key1".to_string());
assert_eq!(removed_high_freq.as_deref(), Some(&100));
assert_eq!(cache.len(), 2);
assert_eq!(cache.frequency(&"key1".to_string()), None);
let removed_low_freq = cache.remove(&"key4".to_string());
assert_eq!(removed_low_freq.as_deref(), Some(&400));
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"key4".to_string()), None);
let (lfu_key, lfu_value) = cache.peek_lfu().unwrap();
assert_eq!(lfu_key, &"key3".to_string());
assert_eq!(lfu_value.as_ref(), &300);
let removed_last = cache.remove(&"key3".to_string());
assert_eq!(removed_last.as_deref(), Some(&300));
assert_eq!(cache.len(), 0);
assert_eq!(cache.peek_lfu(), None);
assert_eq!(cache.pop_lfu(), None);
let removed_from_empty = cache.remove(&"key1".to_string());
assert_eq!(removed_from_empty, None);
assert_eq!(cache.len(), 0);
cache.insert("new_key".to_string(), Arc::new(999));
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"new_key".to_string()), Some(1));
cache.remove(&"new_key".to_string());
assert_eq!(cache.len(), 0);
cache.insert("new_key".to_string(), Arc::new(888));
assert_eq!(cache.len(), 1);
assert_eq!(cache.frequency(&"new_key".to_string()), Some(1)); assert_eq!(
cache.get(&"new_key".to_string()).map(Arc::as_ref),
Some(&888)
);
}
#[test]
fn test_eviction_consistency() {
let mut cache = LfuCache::new(3);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.len(), 3);
cache.get(&"key1".to_string()); cache.get(&"key1".to_string()); cache.get(&"key2".to_string());
cache.insert("key4".to_string(), Arc::new(400));
assert_eq!(cache.len(), 3);
assert!(!cache.contains(&"key3".to_string()));
assert_eq!(cache.frequency(&"key3".to_string()), None);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key2".to_string()));
assert!(cache.contains(&"key4".to_string()));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(cache.frequency(&"key2".to_string()), Some(2));
assert_eq!(cache.frequency(&"key4".to_string()), Some(1));
cache.insert("key5".to_string(), Arc::new(500));
assert_eq!(cache.len(), 3);
let has_key4 = cache.contains(&"key4".to_string());
let has_key5 = cache.contains(&"key5".to_string());
assert!(has_key4 ^ has_key5);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key2".to_string()));
cache.insert("key6".to_string(), Arc::new(600));
cache.insert("key7".to_string(), Arc::new(700));
assert_eq!(cache.len(), 3);
assert!(cache.contains(&"key1".to_string()));
assert!(cache.contains(&"key2".to_string()));
#[cfg(debug_assertions)]
#[cfg(debug_assertions)]
cache.debug_validate_invariants();
let mut zero_cache = LfuCache::<String, i32>::new(0);
zero_cache.insert("key1".to_string(), Arc::new(100));
assert_eq!(zero_cache.len(), 0); assert!(!zero_cache.contains(&"key1".to_string()));
let mut test_cache = LfuCache::new(2);
test_cache.insert("low".to_string(), Arc::new(1));
test_cache.insert("high".to_string(), Arc::new(2));
for _ in 0..5 {
test_cache.get(&"high".to_string());
}
test_cache.insert("new".to_string(), Arc::new(3));
assert_eq!(test_cache.len(), 2);
assert!(!test_cache.contains(&"low".to_string()));
assert!(test_cache.contains(&"high".to_string()));
assert!(test_cache.contains(&"new".to_string()));
assert_eq!(test_cache.frequency(&"low".to_string()), None);
assert!(test_cache.frequency(&"high".to_string()).unwrap() > 1);
assert_eq!(test_cache.frequency(&"new".to_string()), Some(1));
}
#[test]
fn test_frequency_increment_on_get() {
let mut cache = LfuCache::new(5);
cache.insert("key1".to_string(), Arc::new(100));
cache.insert("key2".to_string(), Arc::new(200));
cache.insert("key3".to_string(), Arc::new(300));
assert_eq!(cache.frequency(&"key1".to_string()), Some(1));
assert_eq!(cache.frequency(&"key2".to_string()), Some(1));
assert_eq!(cache.frequency(&"key3".to_string()), Some(1));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(2));
assert_eq!(cache.get(&"key2".to_string()).map(Arc::as_ref), Some(&200));
assert_eq!(cache.frequency(&"key2".to_string()), Some(2));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(3));
assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&100));
assert_eq!(cache.frequency(&"key1".to_string()), Some(4));
assert_eq!(cache.get(&"nonexistent".to_string()), None);
assert_eq!(cache.frequency(&"nonexistent".to_string()), None);
assert_eq!(cache.len(), 3);
for _ in 0..10 {
cache.get(&"key2".to_string());
}
for _ in 0..5 {
cache.get(&"key3".to_string());
}
assert_eq!(cache.frequency(&"key1".to_string()), Some(4)); assert_eq!(cache.frequency(&"key2".to_string()), Some(12)); assert_eq!(cache.frequency(&"key3".to_string()), Some(6));
cache.insert("key1".to_string(), Arc::new(999)); assert_eq!(cache.frequency(&"key1".to_string()), Some(4)); assert_eq!(cache.get(&"key1".to_string()).map(Arc::as_ref), Some(&999)); assert_eq!(cache.frequency(&"key1".to_string()), Some(5));
cache.insert("key4".to_string(), Arc::new(400));
assert_eq!(cache.frequency(&"key4".to_string()), Some(1));
let (lfu_key, _) = cache.peek_lfu().unwrap();
assert_eq!(lfu_key, &"key4".to_string());
cache.get(&"key4".to_string());
cache.get(&"key4".to_string());
assert_eq!(cache.frequency(&"key4".to_string()), Some(3));
cache.insert("key5".to_string(), Arc::new(500));
assert_eq!(cache.frequency(&"key5".to_string()), Some(1));
let (new_lfu_key, _) = cache.peek_lfu().unwrap();
assert_eq!(new_lfu_key, &"key5".to_string());
let new_lfu_freq = cache.frequency(new_lfu_key).unwrap();
assert_eq!(new_lfu_freq, 1);
let initial_freq = cache.frequency(&"key1".to_string()).unwrap();
for i in 1..=100 {
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key1".to_string()), Some(initial_freq + i));
}
let key2_freq_before = cache.frequency(&"key2".to_string()).unwrap();
let key3_freq_before = cache.frequency(&"key3".to_string()).unwrap();
let key4_freq_before = cache.frequency(&"key4".to_string()).unwrap();
cache.get(&"key1".to_string());
assert_eq!(cache.frequency(&"key2".to_string()), Some(key2_freq_before));
assert_eq!(cache.frequency(&"key3".to_string()), Some(key3_freq_before));
assert_eq!(cache.frequency(&"key4".to_string()), Some(key4_freq_before));
}
#[test]
fn test_invariants_after_operations() {
let mut cache = LfuCache::new(4);
let verify_invariants = |cache: &mut LfuCache<String, i32>| {
if cache.len() > 0 {
assert!(cache.peek_lfu().is_some());
} else {
assert!(cache.peek_lfu().is_none());
}
let test_keys = ["key1", "key2", "key3", "key4", "key5", "nonexistent"];
for key in test_keys {
let contains_result = cache.contains(&key.to_string());
let get_result = cache.get(&key.to_string()).is_some();
assert_eq!(contains_result, get_result);
}
#[cfg(debug_assertions)]
#[cfg(debug_assertions)]
cache.debug_validate_invariants();
};
verify_invariants(&mut cache);
cache.insert("key1".to_string(), Arc::new(100));
verify_invariants(&mut cache);
cache.insert("key2".to_string(), Arc::new(200));
verify_invariants(&mut cache);
cache.insert("key3".to_string(), Arc::new(300));
verify_invariants(&mut cache);
cache.insert("key4".to_string(), Arc::new(400));
verify_invariants(&mut cache);
cache.get(&"key1".to_string());
verify_invariants(&mut cache);
cache.get(&"key1".to_string());
cache.get(&"key2".to_string());
verify_invariants(&mut cache);
cache.insert("key5".to_string(), Arc::new(500));
verify_invariants(&mut cache);
for i in 0..20 {
match i % 5 {
0 => {
cache.insert(format!("temp{}", i), Arc::new(i));
},
1 => {
cache.get(&"key1".to_string());
},
2 => {
cache.remove(&format!("temp{}", i - 1));
},
3 => {
let _ = cache.pop_lfu();
},
4 => {
cache.increment_frequency(&"key2".to_string());
},
_ => unreachable!(),
}
verify_invariants(&mut cache);
}
cache.reset_frequency(&"key1".to_string());
verify_invariants(&mut cache);
cache.increment_frequency(&"key2".to_string());
verify_invariants(&mut cache);
for candidate in ["key1", "key2", "key3", "key4"] {
if cache.contains(&candidate.to_string()) {
cache.remove(&candidate.to_string());
verify_invariants(&mut cache);
}
}
while cache.len() > 0 {
let _ = cache.pop_lfu();
verify_invariants(&mut cache);
}
cache.insert("test1".to_string(), Arc::new(1));
cache.insert("test2".to_string(), Arc::new(2));
verify_invariants(&mut cache);
cache.clear();
verify_invariants(&mut cache);
let mut zero_cache = LfuCache::<String, i32>::new(0);
verify_invariants(&mut zero_cache);
zero_cache.insert("test".to_string(), Arc::new(1));
verify_invariants(&mut zero_cache);
let mut single_cache = LfuCache::new(1);
verify_invariants(&mut single_cache);
single_cache.insert("only".to_string(), Arc::new(1));
verify_invariants(&mut single_cache);
single_cache.insert("replace".to_string(), Arc::new(2));
verify_invariants(&mut single_cache);
let mut complex_cache = LfuCache::new(3);
complex_cache.insert("a".to_string(), Arc::new(1));
complex_cache.insert("b".to_string(), Arc::new(2));
complex_cache.insert("c".to_string(), Arc::new(3));
for i in 1..=10 {
for _ in 0..i {
complex_cache.get(&"a".to_string());
}
for _ in 0..(i / 2) {
complex_cache.get(&"b".to_string());
}
verify_invariants(&mut complex_cache);
}
}
}
}