Skip to content

Commit 79beaff

Browse files
authored
feat: add retain api (#64)
* feat: add retain api * Fix compile error * Add test cases * Add more tests * Reverse the predicate logic * Rename test methods to retain * Apply suggestions from code review
1 parent d83e8be commit 79beaff

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

src/lib.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,78 @@ mod tests {
371371
assert_eq!(result, Ok(4));
372372
}
373373

374+
#[test]
375+
fn test_retain_unsync() {
376+
let mut cache = unsync::Cache::<u64, u64>::new(100);
377+
let ranges = 0..10;
378+
for i in ranges.clone() {
379+
let guard = cache.get_ref_or_guard(&i).unwrap_err();
380+
guard.insert(i);
381+
assert_eq!(cache.get_ref_or_guard(&i).ok().copied(), Some(i));
382+
}
383+
let small = 3;
384+
cache.retain(|&key, &val| val > small && key > small);
385+
for i in ranges.clone() {
386+
let actual = cache.get(&i);
387+
if i > small {
388+
assert!(actual.is_some());
389+
assert_eq!(*actual.unwrap(), i);
390+
} else {
391+
assert!(actual.is_none());
392+
}
393+
}
394+
let big = 7;
395+
cache.retain(|&key, &val| val < big && key < big);
396+
for i in ranges {
397+
let actual = cache.get(&i);
398+
if i > small && i < big {
399+
assert!(actual.is_some());
400+
assert_eq!(*actual.unwrap(), i);
401+
} else {
402+
assert!(actual.is_none());
403+
}
404+
}
405+
}
406+
407+
#[tokio::test]
408+
async fn test_retain_sync() {
409+
use crate::sync::*;
410+
let cache = Cache::<u64, u64>::new(100);
411+
let ranges = 0..10;
412+
for i in ranges.clone() {
413+
let GuardResult::Guard(guard) = cache.get_value_or_guard(&i, None) else {
414+
panic!();
415+
};
416+
guard.insert(i).unwrap();
417+
let GuardResult::Value(v) = cache.get_value_or_guard(&i, None) else {
418+
panic!();
419+
};
420+
assert_eq!(v, i);
421+
}
422+
let small = 4;
423+
cache.retain(|&key, &val| val > small && key > small);
424+
for i in ranges.clone() {
425+
let actual = cache.get(&i);
426+
if i > small {
427+
assert!(actual.is_some());
428+
assert_eq!(actual.unwrap(), i);
429+
} else {
430+
assert!(actual.is_none());
431+
}
432+
}
433+
let big = 8;
434+
cache.retain(|&key, &val| val < big && key < big);
435+
for i in ranges {
436+
let actual = cache.get(&i);
437+
if i > small && i < big {
438+
assert!(actual.is_some());
439+
assert_eq!(actual.unwrap(), i);
440+
} else {
441+
assert!(actual.is_none());
442+
}
443+
}
444+
}
445+
374446
#[test]
375447
#[cfg_attr(miri, ignore)]
376448
fn test_value_or_guard() {

src/shard.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,33 @@ impl<
332332
})
333333
}
334334

335+
pub fn retain<F>(&mut self, f: F)
336+
where
337+
F: Fn(&Key, &Val) -> bool,
338+
{
339+
let retained_tokens = self
340+
.map
341+
.iter()
342+
.filter_map(|&idx| match self.entries.get(idx) {
343+
Some((entry, _idx)) => match entry {
344+
Entry::Resident(r) => {
345+
if !f(&r.key, &r.value) {
346+
let hash = self.hash(&r.key);
347+
Some((idx, hash))
348+
} else {
349+
None
350+
}
351+
}
352+
Entry::Placeholder(_) | Entry::Ghost(_) => None,
353+
},
354+
None => None,
355+
})
356+
.collect::<Vec<_>>();
357+
for (idx, hash) in retained_tokens {
358+
self.remove_internal(hash, idx);
359+
}
360+
}
361+
335362
pub fn weight(&self) -> u64 {
336363
self.weight_hot + self.weight_cold
337364
}

src/sync.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,18 @@ impl<
287287
Ok(lcs)
288288
}
289289

290+
/// Retains only the items specified by the predicate.
291+
/// In other words, remove all items for which `f(&key, &value)` returns `false`. The
292+
/// elements are visited in arbitrary order.
293+
pub fn retain<F>(&self, f: F)
294+
where
295+
F: Fn(&Key, &Val) -> bool,
296+
{
297+
for s in self.shards.iter() {
298+
s.write().retain(&f);
299+
}
300+
}
301+
290302
/// Inserts an item in the cache with key `key`.
291303
pub fn insert(&self, key: Key, value: Val) {
292304
let lcs = self.insert_with_lifecycle(key, value);

src/unsync.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ impl<Key: Eq + Hash, Val, We: Weighter<Key, Val>, B: BuildHasher, L: Lifecycle<K
227227
Ok(lcs)
228228
}
229229

230+
/// Retains only the items specified by the predicate.
231+
/// In other words, remove all items for which `f(&key, &value)` returns `false`. The
232+
/// elements are visited in arbitrary order.
233+
pub fn retain<F>(&mut self, f: F)
234+
where
235+
F: Fn(&Key, &Val) -> bool,
236+
{
237+
self.shard.retain(f);
238+
}
239+
230240
/// Gets or inserts an item in the cache with key `key`.
231241
/// Returns a reference to the inserted `value` if it was admitted to the cache.
232242
///

0 commit comments

Comments
 (0)