Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/shard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,19 @@ impl<
self.remove_internal(hash, idx)
}

pub fn remove_if<Q, F>(&mut self, hash: u64, key: &Q, f: F) -> Option<(Key, Val)>
where
Q: Hash + Equivalent<Key> + ?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;
Expand Down
37 changes: 37 additions & 0 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Q, F>(&self, key: &Q, f: F) -> Option<(Key, Val)>
where
Q: Hash + Equivalent<Key> + ?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.
Expand Down Expand Up @@ -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);
}
}
38 changes: 38 additions & 0 deletions src/unsync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ impl<Key: Eq + Hash, Val, We: Weighter<Key, Val>, B: BuildHasher, L: Lifecycle<K
self.shard.remove(self.shard.hash(key), 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 is more efficient as it requires only 1 lookup.
///
/// Returns the removed entry, if any.
pub fn remove_if<Q, F>(&mut self, key: &Q, f: F) -> Option<(Key, Val)>
where
Q: Hash + Equivalent<Key> + ?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.
Expand Down Expand Up @@ -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);
}
}
Loading