diff --git a/src/shard.rs b/src/shard.rs index 52a2546..7982da1 100644 --- a/src/shard.rs +++ b/src/shard.rs @@ -604,6 +604,19 @@ impl< self.remove_internal(hash, idx) } + pub fn remove_if(&mut self, hash: u64, key: &Q, f: F) -> Option<(Key, Val)> + where + Q: Hash + Equivalent + ?Sized, + F: FnOnce(&Val) -> bool, + { + let (idx, resident) = self.search_resident(hash, key)?; + if f(&resident.value) { + self.remove_internal(hash, idx) + } else { + None + } + } + pub fn remove_token(&mut self, token: Token) -> Option<(Key, Val)> { let Some((Entry::Resident(resident), _)) = self.entries.get(token) else { return None; diff --git a/src/sync.rs b/src/sync.rs index c42d0ad..6a3366e 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -274,6 +274,19 @@ impl< shard.write().remove(hash, key) } + /// Remove an item from the cache whose key is `key` if `f(&value)` returns `true` for that entry. + /// Compared to peek and remove, this method guarantees that no new value was inserted in-between. + /// + /// Returns the removed entry, if any. + pub fn remove_if(&self, key: &Q, f: F) -> Option<(Key, Val)> + where + Q: Hash + Equivalent + ?Sized, + F: FnOnce(&Val) -> bool, + { + let (shard, hash) = self.shard_for(key).unwrap(); + shard.write().remove_if(hash, key, f) + } + /// Inserts an item in the cache, but _only_ if an entry with key `key` already exists. /// If `soft` is set, the replace operation won't affect the "hotness" of the entry, /// even if the value is replaced. @@ -756,4 +769,28 @@ mod tests { assert!(cache.len() <= 180); assert!(cache.weight() <= 200); } + + #[test] + fn test_remove_if() { + let cache = Cache::new(100); + + // Insert test data + cache.insert(1, 10); + cache.insert(2, 20); + cache.insert(3, 30); + + // Test removing with predicate that returns true + let removed = cache.remove_if(&2, |v| *v == 20); + assert_eq!(removed, Some((2, 20))); + assert_eq!(cache.get(&2), None); + + // Test removing with predicate that returns false + let not_removed = cache.remove_if(&3, |v| *v == 999); + assert_eq!(not_removed, None); + assert_eq!(cache.get(&3), Some(30)); + + // Test removing non-existent key + let not_found = cache.remove_if(&999, |_| true); + assert_eq!(not_found, None); + } } diff --git a/src/unsync.rs b/src/unsync.rs index bec78f1..8d4e00a 100644 --- a/src/unsync.rs +++ b/src/unsync.rs @@ -194,6 +194,18 @@ impl, B: BuildHasher, L: Lifecycle(&mut self, key: &Q, f: F) -> Option<(Key, Val)> + where + Q: Hash + Equivalent + ?Sized, + F: FnOnce(&Val) -> bool, + { + self.shard.remove_if(self.shard.hash(key), key, f) + } + /// Replaces an item in the cache, but only if it already exists. /// If `soft` is set, the replace operation won't affect the "hotness" of the key, /// even if the value is replaced. @@ -635,4 +647,30 @@ mod tests { } cache.validate(false); } + + #[test] + fn test_remove_if() { + let mut cache = Cache::new(100); + + // Insert test data + cache.insert(1, 10); + cache.insert(2, 20); + cache.insert(3, 30); + + // Test removing with predicate that returns true + let removed = cache.remove_if(&2, |v| *v == 20); + assert_eq!(removed, Some((2, 20))); + assert_eq!(cache.get(&2), None); + + // Test removing with predicate that returns false + let not_removed = cache.remove_if(&3, |v| *v == 999); + assert_eq!(not_removed, None); + assert_eq!(cache.get(&3), Some(&30)); + + // Test removing non-existent key + let not_found = cache.remove_if(&999, |_| true); + assert_eq!(not_found, None); + + cache.validate(false); + } }