Skip to content

Commit 823ecf5

Browse files
authored
Merge pull request #13 from Code-Hex/add/mru
added MRU
2 parents 51a6259 + 0ed47d0 commit 823ecf5

File tree

7 files changed

+267
-6
lines changed

7 files changed

+267
-6
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ go-generics-cache is an in-memory key:value store/cache that is suitable for app
1010
- Simple cache is like `map[string]interface{}`
1111
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/simple/example_test.go)
1212
- Cache replacement policies
13-
- Least recently used (LRU)
13+
- **Least recently used (LRU)**
1414
- Discards the least recently used items first.
1515
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lru/example_test.go)
16-
- Least-frequently used (LFU)
16+
- **Least-frequently used (LFU)**
1717
- Counts how often an item is needed. Those that are used least often are discarded first.
1818
- [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf)
1919
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lfu/example_test.go)
20-
- First in first out (FIFO)
20+
- **First in first out (FIFO)**
2121
- Using this algorithm the cache behaves in the same way as a [FIFO queue](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)).
2222
- The cache evicts the blocks in the order they were added, without any regard to how often or how many times they were accessed before.
2323
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/fifo/example_test.go)
24+
- **Most recently used (MRU)**
25+
- In contrast to Least Recently Used (LRU), MRU discards the most recently used items first.
26+
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/mru/example_test.go)
2427

2528
## Requirements
2629

@@ -41,7 +44,7 @@ go version devel go1.18-c2397905e0 Sat Nov 13 03:33:55 2021 +0000 darwin/arm64
4144

4245
## Usage
4346

44-
See also [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/example_test.go)
47+
See also [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/example_test.go) or [gotipplay playground](https://gotipplay.golang.org/p/MRF_I4oUS_W)
4548

4649
```go
4750
package main

cache.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/Code-Hex/go-generics-cache/fifo"
88
"github.com/Code-Hex/go-generics-cache/lfu"
99
"github.com/Code-Hex/go-generics-cache/lru"
10+
"github.com/Code-Hex/go-generics-cache/mru"
1011
"github.com/Code-Hex/go-generics-cache/simple"
1112
)
1213

@@ -23,6 +24,7 @@ var (
2324
_ Interface[any, any] = (*lru.Cache[any, any])(nil)
2425
_ Interface[any, any] = (*lfu.Cache[any, any])(nil)
2526
_ Interface[any, any] = (*fifo.Cache[any, any])(nil)
27+
_ Interface[any, any] = (*mru.Cache[any, any])(nil)
2628
)
2729

2830
// Item is an item
@@ -104,6 +106,13 @@ func AsFIFO[K comparable, V any](opts ...fifo.Option) Option[K, V] {
104106
}
105107
}
106108

109+
// AsMRU is an option to make a new Cache as MRU algorithm.
110+
func AsMRU[K comparable, V any](opts ...mru.Option) Option[K, V] {
111+
return func(o *options[K, V]) {
112+
o.cache = mru.NewCache[K, *Item[K, V]](opts...)
113+
}
114+
}
115+
107116
// New creates a new thread safe Cache.
108117
//
109118
// There are several Cache replacement policies available with you specified any options.

example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func ExampleCacheWithExpiration() {
4646
}
4747

4848
func ExampleDelete() {
49-
c := cache.New(cache.AsLRU[string, int]())
49+
c := cache.New(cache.AsMRU[string, int]())
5050
c.Set("a", 1)
5151
c.Delete("a")
5252
gota, aok := c.Get("a")

lru/lru.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type entry[K comparable, V any] struct {
2020
val V
2121
}
2222

23-
// Option is an option for LFU cache.
23+
// Option is an option for LRU cache.
2424
type Option func(*options)
2525

2626
type options struct {

mru/example_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package mru_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Code-Hex/go-generics-cache/mru"
7+
)
8+
9+
func ExampleMRUCache() {
10+
c := mru.NewCache[string, int]()
11+
c.Set("a", 1)
12+
c.Set("b", 2)
13+
av, aok := c.Get("a")
14+
bv, bok := c.Get("b")
15+
cv, cok := c.Get("c")
16+
fmt.Println(av, aok)
17+
fmt.Println(bv, bok)
18+
fmt.Println(cv, cok)
19+
// Output:
20+
// 1 true
21+
// 2 true
22+
// 0 false
23+
}
24+
25+
func ExampleCacheKeys() {
26+
c := mru.NewCache[string, int]()
27+
c.Set("a", 1)
28+
c.Set("b", 2)
29+
c.Set("c", 3)
30+
keys := c.Keys()
31+
for _, key := range keys {
32+
fmt.Println(key)
33+
}
34+
// Output:
35+
// c
36+
// b
37+
// a
38+
}

mru/mru.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package mru
2+
3+
import (
4+
"container/list"
5+
)
6+
7+
// Cache is used a MRU (Most recently used) cache replacement policy.
8+
//
9+
// In contrast to Least Recently Used (LRU), MRU discards the most recently used items first.
10+
type Cache[K comparable, V any] struct {
11+
cap int
12+
list *list.List
13+
items map[K]*list.Element
14+
}
15+
16+
type entry[K comparable, V any] struct {
17+
key K
18+
val V
19+
}
20+
21+
// Option is an option for MRU cache.
22+
type Option func(*options)
23+
24+
type options struct {
25+
capacity int
26+
}
27+
28+
func newOptions() *options {
29+
return &options{
30+
capacity: 128,
31+
}
32+
}
33+
34+
// WithCapacity is an option to set cache capacity.
35+
func WithCapacity(cap int) Option {
36+
return func(o *options) {
37+
o.capacity = cap
38+
}
39+
}
40+
41+
// NewCache creates a new non-thread safe MRU cache whose capacity is the default size (128).
42+
func NewCache[K comparable, V any](opts ...Option) *Cache[K, V] {
43+
o := newOptions()
44+
for _, optFunc := range opts {
45+
optFunc(o)
46+
}
47+
return &Cache[K, V]{
48+
cap: o.capacity,
49+
list: list.New(),
50+
items: make(map[K]*list.Element, o.capacity),
51+
}
52+
}
53+
54+
// Get looks up a key's value from the cache.
55+
func (c *Cache[K, V]) Get(key K) (zero V, _ bool) {
56+
e, ok := c.items[key]
57+
if !ok {
58+
return
59+
}
60+
// updates cache order
61+
c.list.MoveToBack(e)
62+
return e.Value.(*entry[K, V]).val, true
63+
}
64+
65+
// Set sets a value to the cache with key. replacing any existing value.
66+
func (c *Cache[K, V]) Set(key K, val V) {
67+
if e, ok := c.items[key]; ok {
68+
// updates cache order
69+
c.list.MoveToBack(e)
70+
entry := e.Value.(*entry[K, V])
71+
entry.val = val
72+
return
73+
}
74+
75+
if c.list.Len() == c.cap {
76+
c.deleteNewest()
77+
}
78+
79+
newEntry := &entry[K, V]{
80+
key: key,
81+
val: val,
82+
}
83+
e := c.list.PushBack(newEntry)
84+
c.items[key] = e
85+
}
86+
87+
// Keys returns the keys of the cache. the order is from recently used.
88+
func (c *Cache[K, V]) Keys() []K {
89+
keys := make([]K, 0, len(c.items))
90+
for ent := c.list.Back(); ent != nil; ent = ent.Prev() {
91+
entry := ent.Value.(*entry[K, V])
92+
keys = append(keys, entry.key)
93+
}
94+
return keys
95+
}
96+
97+
// Len returns the number of items in the cache.
98+
func (c *Cache[K, V]) Len() int {
99+
return c.list.Len()
100+
}
101+
102+
// Delete deletes the item with provided key from the cache.
103+
func (c *Cache[K, V]) Delete(key K) {
104+
if e, ok := c.items[key]; ok {
105+
c.delete(e)
106+
}
107+
}
108+
109+
func (c *Cache[K, V]) deleteNewest() {
110+
e := c.list.Front()
111+
c.delete(e)
112+
}
113+
114+
func (c *Cache[K, V]) delete(e *list.Element) {
115+
c.list.Remove(e)
116+
entry := e.Value.(*entry[K, V])
117+
delete(c.items, entry.key)
118+
}

mru/mru_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package mru_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/Code-Hex/go-generics-cache/mru"
8+
)
9+
10+
func TestSet(t *testing.T) {
11+
cache := mru.NewCache[string, int](mru.WithCapacity(2))
12+
cache.Set("foo", 1)
13+
cache.Set("bar", 2)
14+
if got := cache.Len(); got != 2 {
15+
t.Fatalf("invalid length: %d", got)
16+
}
17+
if got, ok := cache.Get("foo"); got != 1 || !ok {
18+
t.Fatalf("invalid value got %d, cachehit %v", got, ok)
19+
}
20+
21+
// if over the cap
22+
cache.Set("baz", 3)
23+
if got := cache.Len(); got != 2 {
24+
t.Fatalf("invalid length: %d", got)
25+
}
26+
baz, ok := cache.Get("baz")
27+
if baz != 3 || !ok {
28+
t.Fatalf("invalid value baz %d, cachehit %v", baz, ok)
29+
}
30+
31+
// check eviction most recently used
32+
if _, ok := cache.Get("bar"); ok {
33+
t.Log(cache.Keys())
34+
t.Fatalf("invalid eviction the newest value for bar %v", ok)
35+
}
36+
37+
// current state
38+
// - baz <- recently used
39+
// - foo
40+
41+
// valid: if over the cap but specify the oldest key
42+
cache.Set("foo", 100)
43+
if got := cache.Len(); got != 2 {
44+
t.Fatalf("invalid length: %d", got)
45+
}
46+
foo, ok := cache.Get("foo")
47+
if foo != 100 || !ok {
48+
t.Fatalf("invalid replacing value foo %d, cachehit %v", foo, ok)
49+
}
50+
}
51+
52+
func TestDelete(t *testing.T) {
53+
cache := mru.NewCache[string, int](mru.WithCapacity(1))
54+
cache.Set("foo", 1)
55+
if got := cache.Len(); got != 1 {
56+
t.Fatalf("invalid length: %d", got)
57+
}
58+
59+
cache.Delete("foo2")
60+
if got := cache.Len(); got != 1 {
61+
t.Fatalf("invalid length after deleted does not exist key: %d", got)
62+
}
63+
64+
cache.Delete("foo")
65+
if got := cache.Len(); got != 0 {
66+
t.Fatalf("invalid length after deleted: %d", got)
67+
}
68+
if _, ok := cache.Get("foo"); ok {
69+
t.Fatalf("invalid get after deleted %v", ok)
70+
}
71+
}
72+
73+
func TestKeys(t *testing.T) {
74+
cache := mru.NewCache[string, int]()
75+
cache.Set("foo", 1)
76+
cache.Set("bar", 2)
77+
cache.Set("baz", 3)
78+
cache.Set("bar", 4) // again
79+
cache.Set("foo", 5) // again
80+
81+
got := strings.Join(cache.Keys(), ",")
82+
want := strings.Join([]string{
83+
"foo",
84+
"bar",
85+
"baz",
86+
}, ",")
87+
if got != want {
88+
t.Errorf("want %q, but got %q", want, got)
89+
}
90+
if len(cache.Keys()) != cache.Len() {
91+
t.Errorf("want number of keys %d, but got %d", len(cache.Keys()), cache.Len())
92+
}
93+
}

0 commit comments

Comments
 (0)