sieve_cache/lib.rs
1#![doc = include_str!("../README.md")]
2
3//! # SIEVE Cache for Rust
4//!
5//! This crate provides implementations of the SIEVE cache replacement algorithm:
6//!
7//! * [`SieveCache`] - The core single-threaded implementation (always available)
8
9#[cfg(feature = "sync")]
10pub mod _docs_sync {}
11
12#[cfg(feature = "sharded")]
13pub mod _docs_sharded {}
14
15pub mod _sieve_algorithm {
16 //! ## The SIEVE Algorithm
17 //!
18 //! SIEVE (Simple, space-efficient, In-memory, EViction mEchanism) is a cache eviction
19 //! algorithm that maintains a single bit per entry to track whether an item has been
20 //! "visited" since it was last considered for eviction. This approach requires less
21 //! state than LRU but achieves excellent performance, especially on skewed workloads.
22}
23
24pub mod _cache_implementation {
25 //! ## Cache Implementation Details
26 //!
27 //! The cache is implemented as a combination of:
28 //!
29 //! 1. A `HashMap` for O(1) key lookups
30 //! 2. A vector-based ordered collection for managing the eviction order
31 //! 3. A "visited" flag on each entry to track recent access
32 //!
33 //! When the cache is full and a new item is inserted, the eviction algorithm:
34 //! 1. Starts from the "hand" position (eviction candidate)
35 //! 2. Finds the first non-visited entry, evicting it
36 //! 3. Marks all visited entries as non-visited while searching
37}
38
39pub mod _implementation_choice {
40 //! ## Choosing the Right Implementation
41 //!
42 //! - Use [`SieveCache`] for single-threaded applications
43}
44
45#[cfg(feature = "sync")]
46pub mod _docs_sync_usage {
47 //! - Use [`SyncSieveCache`] for multi-threaded applications with moderate concurrency
48}
49
50#[cfg(feature = "sharded")]
51pub mod _docs_sharded_usage {
52 //! - Use [`ShardedSieveCache`] for applications with high concurrency where operations
53 //! are distributed across many different keys
54}
55
56use std::borrow::Borrow;
57use std::collections::HashMap;
58use std::hash::Hash;
59
60#[cfg(feature = "sharded")]
61mod sharded;
62#[cfg(feature = "sync")]
63mod sync;
64
65#[cfg(feature = "sharded")]
66pub use sharded::ShardedSieveCache;
67#[cfg(feature = "sync")]
68pub use sync::SyncSieveCache;
69
70/// Internal representation of a cache entry
71#[derive(Clone)]
72struct Node<K: Eq + Hash + Clone, V> {
73 key: K,
74 value: V,
75 visited: bool,
76}
77
78impl<K: Eq + Hash + Clone, V> Node<K, V> {
79 fn new(key: K, value: V) -> Self {
80 Self {
81 key,
82 value,
83 visited: false,
84 }
85 }
86}
87
88/// A cache based on the SIEVE eviction algorithm.
89///
90/// `SieveCache` provides an efficient in-memory cache with a carefully designed eviction
91/// strategy that balances simplicity with good performance characteristics, especially on
92/// skewed workloads common in real-world applications.
93///
94/// This is the single-threaded implementation.
95#[cfg(feature = "sync")]
96/// For thread-safe variants, see [`SyncSieveCache`] (with the `sync` feature)
97#[cfg(feature = "sharded")]
98/// and [`ShardedSieveCache`] (with the `sharded` feature).
99///
100/// # Type Parameters
101///
102/// * `K` - The key type, which must implement `Eq`, `Hash`, and `Clone`
103/// * `V` - The value type, with no constraints
104///
105/// # Example
106///
107/// ```rust
108/// # #[cfg(feature = "doctest")]
109/// # {
110/// use sieve_cache::SieveCache;
111///
112/// // Create a new cache with capacity for 1000 items
113/// let mut cache = SieveCache::new(1000).unwrap();
114///
115/// // Insert some values
116/// cache.insert("key1".to_string(), "value1".to_string());
117/// cache.insert("key2".to_string(), "value2".to_string());
118///
119/// // Retrieve values - returns references to the values
120/// assert_eq!(cache.get("key1"), Some(&"value1".to_string()));
121///
122/// // Check if the cache contains a key
123/// assert!(cache.contains_key("key1"));
124/// assert!(!cache.contains_key("missing_key"));
125///
126/// // Get a mutable reference to modify a value
127/// if let Some(value) = cache.get_mut("key1") {
128/// *value = "modified".to_string();
129/// }
130///
131/// // Verify the modification
132/// assert_eq!(cache.get("key1"), Some(&"modified".to_string()));
133///
134/// // Remove a value
135/// let removed = cache.remove("key2");
136/// assert_eq!(removed, Some("value2".to_string()));
137/// # }
138/// ```
139pub struct SieveCache<K: Eq + Hash + Clone, V> {
140 /// Map of keys to indices in the nodes vector
141 map: HashMap<K, usize>,
142 /// Vector of all cache nodes
143 nodes: Vec<Node<K, V>>,
144 /// Index to the "hand" pointer used by the SIEVE algorithm for eviction
145 hand: Option<usize>,
146 /// Maximum number of entries the cache can hold
147 capacity: usize,
148}
149
150impl<K: Eq + Hash + Clone, V> SieveCache<K, V> {
151 /// Creates a new cache with the given capacity.
152 ///
153 /// The capacity represents the maximum number of key-value pairs
154 /// that can be stored in the cache. When this limit is reached,
155 /// the cache will use the SIEVE algorithm to evict entries.
156 ///
157 /// # Errors
158 ///
159 /// Returns an error if the capacity is zero.
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// # #[cfg(feature = "doctest")]
165 /// # {
166 /// use sieve_cache::SieveCache;
167 ///
168 /// // Create a cache with space for 100 entries
169 /// let cache: SieveCache<String, u32> = SieveCache::new(100).unwrap();
170 ///
171 /// // Capacity of zero is invalid
172 /// let invalid = SieveCache::<String, u32>::new(0);
173 /// assert!(invalid.is_err());
174 /// # }
175 /// ```
176 pub fn new(capacity: usize) -> Result<Self, &'static str> {
177 if capacity == 0 {
178 return Err("capacity must be greater than 0");
179 }
180 Ok(Self {
181 map: HashMap::with_capacity(capacity),
182 nodes: Vec::with_capacity(capacity),
183 hand: None,
184 capacity,
185 })
186 }
187
188 /// Returns the capacity of the cache.
189 ///
190 /// This is the maximum number of entries that can be stored before
191 /// the cache begins evicting old entries.
192 ///
193 /// # Examples
194 ///
195 /// ```rust
196 /// # #[cfg(feature = "doctest")]
197 /// # {
198 /// use sieve_cache::SieveCache;
199 ///
200 /// let cache = SieveCache::<String, u32>::new(100).unwrap();
201 /// assert_eq!(cache.capacity(), 100);
202 /// # }
203 /// ```
204 #[inline]
205 pub fn capacity(&self) -> usize {
206 self.capacity
207 }
208
209 /// Returns the number of cached values.
210 ///
211 /// This value will never exceed the capacity.
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// # #[cfg(feature = "doctest")]
217 /// # {
218 /// use sieve_cache::SieveCache;
219 ///
220 /// let mut cache = SieveCache::new(100).unwrap();
221 /// assert_eq!(cache.len(), 0);
222 ///
223 /// cache.insert("key".to_string(), "value".to_string());
224 /// assert_eq!(cache.len(), 1);
225 /// # }
226 /// ```
227 #[inline]
228 pub fn len(&self) -> usize {
229 self.nodes.len()
230 }
231
232 /// Returns `true` when no values are currently cached.
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// # #[cfg(feature = "doctest")]
238 /// # {
239 /// use sieve_cache::SieveCache;
240 ///
241 /// let mut cache = SieveCache::<String, u32>::new(100).unwrap();
242 /// assert!(cache.is_empty());
243 ///
244 /// cache.insert("key".to_string(), "value".to_string());
245 /// assert!(!cache.is_empty());
246 ///
247 /// cache.remove("key");
248 /// assert!(cache.is_empty());
249 /// # }
250 /// ```
251 #[inline]
252 pub fn is_empty(&self) -> bool {
253 self.nodes.is_empty()
254 }
255
256 /// Returns `true` if there is a value in the cache mapped to by `key`.
257 ///
258 /// This operation does not count as an access for the SIEVE algorithm's
259 /// visited flag.
260 ///
261 /// # Examples
262 ///
263 /// ```rust
264 /// # #[cfg(feature = "doctest")]
265 /// # {
266 /// use sieve_cache::SieveCache;
267 ///
268 /// let mut cache = SieveCache::new(100).unwrap();
269 /// cache.insert("key".to_string(), "value".to_string());
270 ///
271 /// assert!(cache.contains_key("key"));
272 /// assert!(!cache.contains_key("missing"));
273 /// # }
274 /// ```
275 #[inline]
276 pub fn contains_key<Q>(&self, key: &Q) -> bool
277 where
278 Q: Hash + Eq + ?Sized,
279 K: Borrow<Q>,
280 {
281 self.map.contains_key(key)
282 }
283
284 /// Gets an immutable reference to the value in the cache mapped to by `key`.
285 ///
286 /// If no value exists for `key`, this returns `None`.
287 ///
288 /// This operation marks the entry as "visited" in the SIEVE algorithm,
289 /// which affects eviction decisions.
290 ///
291 /// # Examples
292 ///
293 /// ```rust
294 /// # #[cfg(feature = "doctest")]
295 /// # {
296 /// use sieve_cache::SieveCache;
297 ///
298 /// let mut cache = SieveCache::new(100).unwrap();
299 /// cache.insert("key".to_string(), "value".to_string());
300 ///
301 /// assert_eq!(cache.get("key"), Some(&"value".to_string()));
302 /// assert_eq!(cache.get("missing"), None);
303 /// # }
304 /// ```
305 pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
306 where
307 Q: Hash + Eq + ?Sized,
308 K: Borrow<Q>,
309 {
310 if let Some(&idx) = self.map.get(key) {
311 // Mark as visited for the SIEVE algorithm
312 self.nodes[idx].visited = true;
313 Some(&self.nodes[idx].value)
314 } else {
315 None
316 }
317 }
318
319 /// Gets a mutable reference to the value in the cache mapped to by `key`.
320 ///
321 /// If no value exists for `key`, this returns `None`.
322 ///
323 /// This operation marks the entry as "visited" in the SIEVE algorithm,
324 /// which affects eviction decisions.
325 ///
326 /// # Examples
327 ///
328 /// ```rust
329 /// # #[cfg(feature = "doctest")]
330 /// # {
331 /// use sieve_cache::SieveCache;
332 ///
333 /// let mut cache = SieveCache::new(100).unwrap();
334 /// cache.insert("key".to_string(), "value".to_string());
335 ///
336 /// // Modify the value in-place
337 /// if let Some(value) = cache.get_mut("key") {
338 /// *value = "new_value".to_string();
339 /// }
340 ///
341 /// assert_eq!(cache.get("key"), Some(&"new_value".to_string()));
342 /// # }
343 /// ```
344 pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
345 where
346 Q: Hash + Eq + ?Sized,
347 K: Borrow<Q>,
348 {
349 if let Some(&idx) = self.map.get(key) {
350 // Mark as visited for the SIEVE algorithm
351 self.nodes[idx].visited = true;
352 Some(&mut self.nodes[idx].value)
353 } else {
354 None
355 }
356 }
357
358 /// Maps `key` to `value` in the cache, possibly evicting old entries.
359 ///
360 /// If the key already exists, its value is updated and the entry is marked as visited.
361 /// If the key doesn't exist and the cache is at capacity, the SIEVE algorithm is used
362 /// to evict an entry before the new key-value pair is inserted.
363 ///
364 /// # Return Value
365 ///
366 /// Returns `true` when this is a new entry, and `false` if an existing entry was updated.
367 ///
368 /// # Examples
369 ///
370 /// ```rust
371 /// # #[cfg(feature = "doctest")]
372 /// # {
373 /// use sieve_cache::SieveCache;
374 ///
375 /// let mut cache = SieveCache::new(100).unwrap();
376 ///
377 /// // Insert a new entry
378 /// let is_new = cache.insert("key1".to_string(), "value1".to_string());
379 /// assert!(is_new); // Returns true for new entries
380 ///
381 /// // Update an existing entry
382 /// let is_new = cache.insert("key1".to_string(), "updated".to_string());
383 /// assert!(!is_new); // Returns false for updates
384 ///
385 /// assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
386 /// # }
387 /// ```
388 pub fn insert(&mut self, key: K, value: V) -> bool {
389 // Check if key already exists
390 if let Some(&idx) = self.map.get(&key) {
391 // Update existing entry
392 self.nodes[idx].visited = true;
393 self.nodes[idx].value = value;
394 return false;
395 }
396
397 // Evict if at capacity
398 if self.nodes.len() >= self.capacity {
399 self.evict();
400 }
401
402 // Add new node to the front
403 let node = Node::new(key.clone(), value);
404 self.nodes.push(node);
405 let idx = self.nodes.len() - 1;
406 self.map.insert(key, idx);
407 true
408 }
409
410 /// Removes the cache entry mapped to by `key`.
411 ///
412 /// This method explicitly removes an entry from the cache regardless of its
413 /// "visited" status.
414 ///
415 /// # Return Value
416 ///
417 /// Returns the value removed from the cache. If `key` did not map to any value,
418 /// then this returns `None`.
419 ///
420 /// # Examples
421 ///
422 /// ```rust
423 /// # #[cfg(feature = "doctest")]
424 /// # {
425 /// use sieve_cache::SieveCache;
426 ///
427 /// let mut cache = SieveCache::new(100).unwrap();
428 /// cache.insert("key1".to_string(), "value1".to_string());
429 /// cache.insert("key2".to_string(), "value2".to_string());
430 ///
431 /// // Remove an existing entry
432 /// let removed = cache.remove("key1");
433 /// assert_eq!(removed, Some("value1".to_string()));
434 ///
435 /// // Try to remove a non-existent entry
436 /// let removed = cache.remove("key1");
437 /// assert_eq!(removed, None);
438 ///
439 /// assert_eq!(cache.len(), 1); // Only one entry remains
440 /// # }
441 /// ```
442 pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
443 where
444 K: Borrow<Q>,
445 Q: Eq + Hash + ?Sized,
446 {
447 // Find the node index
448 let idx = self.map.remove(key)?;
449
450 // If this is the last element, just remove it
451 if idx == self.nodes.len() - 1 {
452 return Some(self.nodes.pop().unwrap().value);
453 }
454
455 // Update hand if needed
456 if let Some(hand_idx) = self.hand {
457 if hand_idx == idx {
458 // Move hand to the previous node or wrap to end
459 self.hand = if idx > 0 {
460 Some(idx - 1)
461 } else {
462 Some(self.nodes.len() - 2)
463 };
464 } else if hand_idx == self.nodes.len() - 1 {
465 // If hand points to the last element (which will be moved to idx)
466 self.hand = Some(idx);
467 }
468 }
469
470 // Remove the node by replacing it with the last one and updating the map
471 let last_node = self.nodes.pop().unwrap();
472 let removed_value = if idx < self.nodes.len() {
473 // Only need to swap and update map if not the last element
474 let last_key = last_node.key.clone(); // Clone the key before moving
475 let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
476 self.map.insert(last_key, idx); // Update map for swapped node
477 removed_node.value
478 } else {
479 last_node.value
480 };
481
482 Some(removed_value)
483 }
484
485 /// Removes and returns a value from the cache that was not recently accessed.
486 ///
487 /// This method implements the SIEVE eviction algorithm:
488 /// 1. Starting from the "hand" pointer or the end of the vector, look for an entry
489 /// that hasn't been visited since the last scan
490 /// 2. Mark all visited entries as non-visited during the scan
491 /// 3. If a non-visited entry is found, evict it
492 /// 4. Update the hand to point to the previous node
493 ///
494 /// # Return Value
495 ///
496 /// If a suitable entry is found, it is removed from the cache and its value is returned.
497 /// If all entries have been recently accessed or the cache is empty, this returns `None`.
498 ///
499 /// # Note
500 ///
501 /// This method is automatically called by `insert` when the cache is at capacity,
502 /// but it can also be called manually to proactively free space.
503 ///
504 /// # Examples
505 ///
506 /// ```rust
507 /// # #[cfg(feature = "doctest")]
508 /// # {
509 /// use sieve_cache::SieveCache;
510 ///
511 /// let mut cache = SieveCache::new(3).unwrap();
512 /// cache.insert("key1".to_string(), "value1".to_string());
513 /// cache.insert("key2".to_string(), "value2".to_string());
514 /// cache.insert("key3".to_string(), "value3".to_string());
515 ///
516 /// // Access key1 and key2 to mark them as visited
517 /// cache.get("key1");
518 /// cache.get("key2");
519 ///
520 /// // key3 hasn't been accessed, so it should be evicted
521 /// let evicted = cache.evict();
522 /// assert!(evicted.is_some());
523 /// assert!(!cache.contains_key("key3")); // key3 was evicted
524 /// # }
525 /// ```
526 pub fn evict(&mut self) -> Option<V> {
527 if self.nodes.is_empty() {
528 return None;
529 }
530
531 // Start from the hand pointer or the end if no hand
532 let mut current_idx = self.hand.unwrap_or(self.nodes.len() - 1);
533 let start_idx = current_idx;
534
535 // Track whether we've wrapped around and whether we found a node to evict
536 let mut wrapped = false;
537 let mut found_idx = None;
538
539 // Scan for a non-visited entry
540 loop {
541 // If current node is not visited, mark it for eviction
542 if !self.nodes[current_idx].visited {
543 found_idx = Some(current_idx);
544 break;
545 }
546
547 // Mark as non-visited for next scan
548 self.nodes[current_idx].visited = false;
549
550 // Move to previous node or wrap to end
551 current_idx = if current_idx > 0 {
552 current_idx - 1
553 } else {
554 // Wrap around to end of vector
555 if wrapped {
556 // If we've already wrapped, break to avoid infinite loop
557 break;
558 }
559 wrapped = true;
560 self.nodes.len() - 1
561 };
562
563 // If we've looped back to start, we've checked all nodes
564 if current_idx == start_idx {
565 break;
566 }
567 }
568
569 // If we found a node to evict
570 if let Some(idx) = found_idx {
571 // Update the hand pointer to the previous node or wrap to end
572 self.hand = if idx > 0 {
573 Some(idx - 1)
574 } else if self.nodes.len() > 1 {
575 Some(self.nodes.len() - 2)
576 } else {
577 None
578 };
579
580 // Remove the key from the map
581 let key = &self.nodes[idx].key;
582 self.map.remove(key);
583
584 // Remove the node and return its value
585 if idx == self.nodes.len() - 1 {
586 // If last node, just pop it
587 return Some(self.nodes.pop().unwrap().value);
588 } else {
589 // Otherwise swap with the last node
590 let last_node = self.nodes.pop().unwrap();
591 let last_key = last_node.key.clone(); // Clone the key before moving
592 let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
593 // Update the map for the moved node
594 self.map.insert(last_key, idx);
595 return Some(removed_node.value);
596 }
597 }
598
599 None
600 }
601
602 /// Removes all entries from the cache.
603 ///
604 /// This operation clears all stored values and resets the cache to an empty state,
605 /// while maintaining the original capacity.
606 ///
607 /// # Examples
608 ///
609 /// ```rust
610 /// # #[cfg(feature = "doctest")]
611 /// # {
612 /// use sieve_cache::SieveCache;
613 ///
614 /// let mut cache = SieveCache::new(100).unwrap();
615 /// cache.insert("key1".to_string(), "value1".to_string());
616 /// cache.insert("key2".to_string(), "value2".to_string());
617 ///
618 /// assert_eq!(cache.len(), 2);
619 ///
620 /// cache.clear();
621 /// assert_eq!(cache.len(), 0);
622 /// assert!(cache.is_empty());
623 /// # }
624 /// ```
625 pub fn clear(&mut self) {
626 self.map.clear();
627 self.nodes.clear();
628 self.hand = None;
629 }
630
631 /// Returns an iterator over all keys in the cache.
632 ///
633 /// The order of keys is not specified and should not be relied upon.
634 ///
635 /// # Examples
636 ///
637 /// ```rust
638 /// # #[cfg(feature = "doctest")]
639 /// # {
640 /// use sieve_cache::SieveCache;
641 /// use std::collections::HashSet;
642 ///
643 /// let mut cache = SieveCache::new(100).unwrap();
644 /// cache.insert("key1".to_string(), "value1".to_string());
645 /// cache.insert("key2".to_string(), "value2".to_string());
646 ///
647 /// let keys: HashSet<_> = cache.keys().collect();
648 /// assert_eq!(keys.len(), 2);
649 /// assert!(keys.contains(&"key1".to_string()));
650 /// assert!(keys.contains(&"key2".to_string()));
651 /// # }
652 /// ```
653 pub fn keys(&self) -> impl Iterator<Item = &K> {
654 self.nodes.iter().map(|node| &node.key)
655 }
656
657 /// Returns an iterator over all values in the cache.
658 ///
659 /// The order of values is not specified and should not be relied upon.
660 ///
661 /// # Examples
662 ///
663 /// ```rust
664 /// # #[cfg(feature = "doctest")]
665 /// # {
666 /// use sieve_cache::SieveCache;
667 /// use std::collections::HashSet;
668 ///
669 /// let mut cache = SieveCache::new(100).unwrap();
670 /// cache.insert("key1".to_string(), "value1".to_string());
671 /// cache.insert("key2".to_string(), "value2".to_string());
672 ///
673 /// let values: HashSet<_> = cache.values().collect();
674 /// assert_eq!(values.len(), 2);
675 /// assert!(values.contains(&"value1".to_string()));
676 /// assert!(values.contains(&"value2".to_string()));
677 /// # }
678 /// ```
679 pub fn values(&self) -> impl Iterator<Item = &V> {
680 self.nodes.iter().map(|node| &node.value)
681 }
682
683 /// Returns an iterator over all mutable values in the cache.
684 ///
685 /// The order of values is not specified and should not be relied upon.
686 /// Note that iterating through this will mark all entries as visited.
687 ///
688 /// # Examples
689 ///
690 /// ```rust
691 /// # #[cfg(feature = "doctest")]
692 /// # {
693 /// use sieve_cache::SieveCache;
694 ///
695 /// let mut cache = SieveCache::new(100).unwrap();
696 /// cache.insert("key1".to_string(), "value1".to_string());
697 /// cache.insert("key2".to_string(), "value2".to_string());
698 ///
699 /// // Update all values by appending text
700 /// for value in cache.values_mut() {
701 /// *value = format!("{}_updated", value);
702 /// }
703 ///
704 /// assert_eq!(cache.get("key1"), Some(&"value1_updated".to_string()));
705 /// assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
706 /// # }
707 /// ```
708 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
709 for node in &mut self.nodes {
710 node.visited = true;
711 }
712 self.nodes.iter_mut().map(|node| &mut node.value)
713 }
714
715 /// Returns an iterator over all key-value pairs in the cache.
716 ///
717 /// The order of pairs is not specified and should not be relied upon.
718 ///
719 /// # Examples
720 ///
721 /// ```rust
722 /// # #[cfg(feature = "doctest")]
723 /// # {
724 /// use sieve_cache::SieveCache;
725 /// use std::collections::HashMap;
726 ///
727 /// let mut cache = SieveCache::new(100).unwrap();
728 /// cache.insert("key1".to_string(), "value1".to_string());
729 /// cache.insert("key2".to_string(), "value2".to_string());
730 ///
731 /// let entries: HashMap<_, _> = cache.iter().collect();
732 /// assert_eq!(entries.len(), 2);
733 /// assert_eq!(entries.get(&"key1".to_string()), Some(&&"value1".to_string()));
734 /// assert_eq!(entries.get(&"key2".to_string()), Some(&&"value2".to_string()));
735 /// # }
736 /// ```
737 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
738 self.nodes.iter().map(|node| (&node.key, &node.value))
739 }
740
741 /// Returns an iterator over all key-value pairs in the cache, with mutable references to values.
742 ///
743 /// The order of pairs is not specified and should not be relied upon.
744 /// Note that iterating through this will mark all entries as visited.
745 ///
746 /// # Examples
747 ///
748 /// ```rust
749 /// # #[cfg(feature = "doctest")]
750 /// # {
751 /// use sieve_cache::SieveCache;
752 ///
753 /// let mut cache = SieveCache::new(100).unwrap();
754 /// cache.insert("key1".to_string(), "value1".to_string());
755 /// cache.insert("key2".to_string(), "value2".to_string());
756 ///
757 /// // Update all values associated with keys containing '1'
758 /// for (key, value) in cache.iter_mut() {
759 /// if key.contains('1') {
760 /// *value = format!("{}_special", value);
761 /// }
762 /// }
763 ///
764 /// assert_eq!(cache.get("key1"), Some(&"value1_special".to_string()));
765 /// assert_eq!(cache.get("key2"), Some(&"value2".to_string()));
766 /// # }
767 /// ```
768 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
769 for node in &mut self.nodes {
770 node.visited = true;
771 }
772 self.nodes
773 .iter_mut()
774 .map(|node| (&node.key, &mut node.value))
775 }
776
777 /// Retains only the elements specified by the predicate.
778 ///
779 /// Removes all entries for which the provided function returns `false`.
780 /// The elements are visited in arbitrary, unspecified order.
781 ///
782 /// # Examples
783 ///
784 /// ```rust
785 /// # #[cfg(feature = "doctest")]
786 /// # {
787 /// use sieve_cache::SieveCache;
788 ///
789 /// let mut cache = SieveCache::new(100).unwrap();
790 /// cache.insert("key1".to_string(), 100);
791 /// cache.insert("key2".to_string(), 200);
792 /// cache.insert("key3".to_string(), 300);
793 ///
794 /// // Keep only entries with values greater than 150
795 /// cache.retain(|_, value| *value > 150);
796 ///
797 /// assert_eq!(cache.len(), 2);
798 /// assert!(!cache.contains_key("key1"));
799 /// assert!(cache.contains_key("key2"));
800 /// assert!(cache.contains_key("key3"));
801 /// # }
802 /// ```
803 pub fn retain<F>(&mut self, mut f: F)
804 where
805 F: FnMut(&K, &V) -> bool,
806 {
807 // Collect indices to remove
808 let mut to_remove = Vec::new();
809
810 for (i, node) in self.nodes.iter().enumerate() {
811 if !f(&node.key, &node.value) {
812 to_remove.push(i);
813 }
814 }
815
816 // Remove indices from highest to lowest to avoid invalidating other indices
817 to_remove.sort_unstable_by(|a, b| b.cmp(a));
818
819 for idx in to_remove {
820 // Remove from map
821 self.map.remove(&self.nodes[idx].key);
822
823 // If it's the last element, just pop it
824 if idx == self.nodes.len() - 1 {
825 self.nodes.pop();
826 } else {
827 // Replace with the last element
828 let last_idx = self.nodes.len() - 1;
829 // Use swap_remove which replaces the removed element with the last element
830 self.nodes.swap_remove(idx);
831 if idx < self.nodes.len() {
832 // Update map for the swapped node
833 self.map.insert(self.nodes[idx].key.clone(), idx);
834 }
835
836 // Update hand if needed
837 if let Some(hand_idx) = self.hand {
838 if hand_idx == idx {
839 // Hand was pointing to the removed node, move it to previous
840 self.hand = if idx > 0 {
841 Some(idx - 1)
842 } else if !self.nodes.is_empty() {
843 Some(self.nodes.len() - 1)
844 } else {
845 None
846 };
847 } else if hand_idx == last_idx {
848 // Hand was pointing to the last node that was moved
849 self.hand = Some(idx);
850 }
851 }
852 }
853 }
854 }
855}
856
857#[test]
858fn test() {
859 let mut cache = SieveCache::new(3).unwrap();
860 assert!(cache.insert("foo".to_string(), "foocontent".to_string()));
861 assert!(cache.insert("bar".to_string(), "barcontent".to_string()));
862 cache.remove("bar");
863 assert!(cache.insert("bar2".to_string(), "bar2content".to_string()));
864 assert!(cache.insert("bar3".to_string(), "bar3content".to_string()));
865 assert_eq!(cache.get("foo"), Some(&"foocontent".to_string()));
866 assert_eq!(cache.get("bar"), None);
867 assert_eq!(cache.get("bar2"), Some(&"bar2content".to_string()));
868 assert_eq!(cache.get("bar3"), Some(&"bar3content".to_string()));
869}
870
871#[test]
872fn test_visited_flag_update() {
873 let mut cache = SieveCache::new(2).unwrap();
874 cache.insert("key1".to_string(), "value1".to_string());
875 cache.insert("key2".to_string(), "value2".to_string());
876 // update `key1` entry.
877 cache.insert("key1".to_string(), "updated".to_string());
878 // new entry is added.
879 cache.insert("key3".to_string(), "value3".to_string());
880 assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
881}
882
883#[test]
884fn test_clear() {
885 let mut cache = SieveCache::new(10).unwrap();
886 cache.insert("key1".to_string(), "value1".to_string());
887 cache.insert("key2".to_string(), "value2".to_string());
888
889 assert_eq!(cache.len(), 2);
890 assert!(!cache.is_empty());
891
892 cache.clear();
893
894 assert_eq!(cache.len(), 0);
895 assert!(cache.is_empty());
896 assert_eq!(cache.get("key1"), None);
897 assert_eq!(cache.get("key2"), None);
898}
899
900#[test]
901fn test_iterators() {
902 let mut cache = SieveCache::new(10).unwrap();
903 cache.insert("key1".to_string(), "value1".to_string());
904 cache.insert("key2".to_string(), "value2".to_string());
905
906 // Test keys iterator
907 let keys: Vec<&String> = cache.keys().collect();
908 assert_eq!(keys.len(), 2);
909 assert!(keys.contains(&&"key1".to_string()));
910 assert!(keys.contains(&&"key2".to_string()));
911
912 // Test values iterator
913 let values: Vec<&String> = cache.values().collect();
914 assert_eq!(values.len(), 2);
915 assert!(values.contains(&&"value1".to_string()));
916 assert!(values.contains(&&"value2".to_string()));
917
918 // Test values_mut iterator
919 for value in cache.values_mut() {
920 *value = format!("{}_updated", value);
921 }
922
923 assert_eq!(cache.get("key1"), Some(&"value1_updated".to_string()));
924 assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
925
926 // Test key-value iterator
927 let entries: Vec<(&String, &String)> = cache.iter().collect();
928 assert_eq!(entries.len(), 2);
929
930 // Test key-value mutable iterator
931 for (key, value) in cache.iter_mut() {
932 if key == "key1" {
933 *value = format!("{}_special", value);
934 }
935 }
936
937 assert_eq!(
938 cache.get("key1"),
939 Some(&"value1_updated_special".to_string())
940 );
941 assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
942}
943
944#[test]
945fn test_retain() {
946 let mut cache = SieveCache::new(10).unwrap();
947
948 // Add some entries
949 cache.insert("even1".to_string(), 2);
950 cache.insert("even2".to_string(), 4);
951 cache.insert("odd1".to_string(), 1);
952 cache.insert("odd2".to_string(), 3);
953
954 assert_eq!(cache.len(), 4);
955
956 // Keep only entries with even values
957 cache.retain(|_, v| v % 2 == 0);
958
959 assert_eq!(cache.len(), 2);
960 assert!(cache.contains_key("even1"));
961 assert!(cache.contains_key("even2"));
962 assert!(!cache.contains_key("odd1"));
963 assert!(!cache.contains_key("odd2"));
964
965 // Keep only entries with keys containing '1'
966 cache.retain(|k, _| k.contains('1'));
967
968 assert_eq!(cache.len(), 1);
969 assert!(cache.contains_key("even1"));
970 assert!(!cache.contains_key("even2"));
971}