Repository: maypok86/otter (go)

Issue:
Add delete by func
I really like otter, but what do miss (in comparison to ristretto for example) is something similar to a tag option. I need it sometimes to invalidate a part of the cache, and an easy option (in my opinion at least) to implement this would be to add a function to delete a part of the the cache depending on a function.

This would go along something like this:

```go
// DeleteByFunc removes the association for this key from the cache if the selectFunc returns true.
func (bs baseCache[K, V]) DeleteByFunc(selectFunc func(key K, value V) bool) {
	// aggregate all keys to delete
	keysToDelete := make([]K, 0)
	bs.Range(func(key K, value V) bool {
		if selectFunc(key, value) {
	            keysToDelete = append(keysToDelete, key)
        	}
	        return true
	})
	
	for _, key := range keysToDelete {
        	bs.Delete(key)
	}
}
```

You probably need a mutex, but the general idea should be clear.

Any additional ideas are appreciated.

Pull request description:
[#44] Add DeleteByFunc function
## Description

Added DeleteByFunc function.

## Related issue(s)

- Resolves #44

## :white_check_mark: Checklist for your Pull Request

<!--
Ideally a PR has all of the checkmarks set.

If something in this list is irrelevant to your PR, you should still set this
checkmark indicating that you are sure it is dealt with (be that by irrelevance).

If you don't set a checkmark (e. g. don't add a test for new functionality),
you must be able to justify that.
-->

#### Related changes (conditional)

- Tests
    - [x] If I added new functionality, I added tests covering it.
    - [x] If I fixed a bug, I added a regression test to prevent the bug from
      silently reappearing again.

- Documentation
    - [x] I checked whether I should update the docs and did so if necessary:
        - [README](../README.md)

- Public contracts
    - [x] My changes doesn't break project license.

#### Stylistic guide (mandatory)

- [x] My code complies with the [styles guide](https://github.com/uber-go/guide/blob/master/style.md).
- [x] My commit history is clean (only contains changes relating to my
  issue/pull request and no reverted-my-earlier-commit changes) and commit
  messages start with identifiers of related issues in square brackets.

  **Example:** `[#42] Short commit description`

  If necessary both of these can be achieved even after the commits have been
  made/pushed using [rebase and squash](https://git-scm.com/docs/git-rebase).

#### Before merging (mandatory)
- [x] Check __target__  branch of PR is set correctly


Patch:
diff --git a/cache.go b/cache.go
index 812a4d3..23d66af 100644
--- a/cache.go
+++ b/cache.go
@@ -70,6 +70,11 @@ func (bs baseCache[K, V]) Delete(key K) {
 	bs.cache.Delete(key)
 }
 
+// DeleteByFunc removes the association for this key from the cache when the given function returns true.
+func (bs baseCache[K, V]) DeleteByFunc(f func(key K, value V) bool) {
+	bs.cache.DeleteByFunc(f)
+}
+
 // Range iterates over all items in the cache.
 //
 // Iteration stops early when the given function returns false.
diff --git a/internal/core/cache.go b/internal/core/cache.go
index d5f7c98..85c4e73 100644
--- a/internal/core/cache.go
+++ b/internal/core/cache.go
@@ -232,6 +232,22 @@ func (c *Cache[K, V]) Delete(key K) {
 	}
 }
 
+// DeleteByFunc removes the association for this key from the cache when the given function returns true.
+func (c *Cache[K, V]) DeleteByFunc(f func(key K, value V) bool) {
+	// TODO(maypok86): This function can be implemented more efficiently, if the performance of this implementation is not enough for you, then come with an issue :)
+	var keysToDelete []K
+	c.Range(func(key K, value V) bool {
+		if f(key, value) {
+			keysToDelete = append(keysToDelete, key)
+		}
+		return true
+	})
+
+	for _, key := range keysToDelete {
+		c.Delete(key)
+	}
+}
+
 func (c *Cache[K, V]) cleanup() {
 	expired := make([]*node.Node[K, V], 0, 128)
 	for {


Test patch:
diff --git a/cache_test.go b/cache_test.go
index ba8961d..7a280bf 100644
--- a/cache_test.go
+++ b/cache_test.go
@@ -182,6 +182,29 @@ func TestCache_SetWithTTL(t *testing.T) {
 	}
 }
 
+func TestBaseCache_DeleteByFunc(t *testing.T) {
+	size := 256
+	c, err := MustBuilder[int, int](size).WithTTL(time.Hour).Build()
+	if err != nil {
+		t.Fatalf("can not create builder: %v", err)
+	}
+
+	for i := 0; i < size; i++ {
+		c.Set(i, i)
+	}
+
+	c.DeleteByFunc(func(key int, value int) bool {
+		return key%2 == 1
+	})
+
+	c.Range(func(key int, value int) bool {
+		if key%2 == 1 {
+			t.Fatalf("key should be odd, but got: %d", key)
+		}
+		return true
+	})
+}
+
 func TestCache_Ratio(t *testing.T) {
 	c, err := MustBuilder[uint64, uint64](100).CollectStats().Build()
 	if err != nil {