Skip to content

Commit 8ade14b

Browse files
authored
Merge pull request #3 from Code-Hex/add/lru
Add lru cache
2 parents 2db8578 + 308ca3b commit 8ade14b

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ go-generics-cache is an in-memory key:value store/cache that is suitable for app
66

77
- implemented with [Go Generics](https://go.dev/blog/generics-proposal)
88
- a thread-safe `map[string]interface{}` with expiration times
9+
- LRU cache
10+
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lru/example_test.go)
911

1012
## Requirements
1113

lru/example_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package lru_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Code-Hex/go-generics-cache/lru"
7+
)
8+
9+
func ExampleLRUCache() {
10+
c := lru.New[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 := lru.New[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+
// a
36+
// b
37+
// c
38+
}

lru/lru.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package lru
2+
3+
import (
4+
"container/list"
5+
"sync"
6+
)
7+
8+
// Cache is a thread safe LRU cache
9+
type Cache[K comparable, V any] struct {
10+
cap int
11+
list *list.List
12+
items map[K]*list.Element
13+
mu sync.RWMutex
14+
}
15+
16+
type item[K comparable, V any] struct {
17+
Key K
18+
Value V
19+
}
20+
21+
// New creates a new LRU cache whose capacity is the default size (128).
22+
func New[K comparable, V any]() *Cache[K, V] {
23+
return NewCap[K, V](128)
24+
}
25+
26+
// NewCap creates a new LRU cache whose capacity is the specified size.
27+
func NewCap[K comparable, V any](cap int) *Cache[K, V] {
28+
return &Cache[K, V]{
29+
cap: cap,
30+
list: list.New(),
31+
items: make(map[K]*list.Element),
32+
}
33+
}
34+
35+
// Get looks up a key's value from the cache.
36+
func (c *Cache[K, V]) Get(key K) (zero V, _ bool) {
37+
c.mu.RLock()
38+
defer c.mu.RUnlock()
39+
if e, ok := c.items[key]; ok {
40+
// updates cache order
41+
c.list.MoveToFront(e)
42+
return e.Value.(*item[K, V]).Value, true
43+
}
44+
return
45+
}
46+
47+
// Set sets a value to the cache with key. replacing any existing value.
48+
func (c *Cache[K, V]) Set(key K, val V) {
49+
c.mu.Lock()
50+
defer c.mu.Unlock()
51+
52+
if e, ok := c.items[key]; ok {
53+
// updates cache order
54+
c.list.MoveToFront(e)
55+
e.Value.(*item[K, V]).Value = val
56+
return
57+
}
58+
59+
e := c.list.PushFront(&item[K, V]{
60+
Key: key,
61+
Value: val,
62+
})
63+
c.items[key] = e
64+
65+
if c.list.Len() > c.cap {
66+
c.deleteOldest()
67+
}
68+
}
69+
70+
// Keys returns the keys of the cache. the order is from oldest to newest.
71+
func (c *Cache[K, V]) Keys() []K {
72+
c.mu.RLock()
73+
defer c.mu.RUnlock()
74+
keys := make([]K, 0, len(c.items))
75+
for ent := c.list.Back(); ent != nil; ent = ent.Prev() {
76+
keys = append(keys, ent.Value.(*item[K, V]).Key)
77+
}
78+
return keys
79+
}
80+
81+
// Len returns the number of items in the cache.
82+
func (c *Cache[K, V]) Len() int {
83+
c.mu.RLock()
84+
defer c.mu.RUnlock()
85+
return c.list.Len()
86+
}
87+
88+
// Delete deletes the item with provided key from the cache.
89+
func (c *Cache[K, V]) Delete(key K) {
90+
c.mu.Lock()
91+
defer c.mu.Unlock()
92+
if e, ok := c.items[key]; ok {
93+
c.delete(e)
94+
}
95+
}
96+
97+
// Contains reports whether key is within cache.
98+
func (c *Cache[K, V]) Contains(key K) bool {
99+
c.mu.RLock()
100+
defer c.mu.RUnlock()
101+
_, ok := c.items[key]
102+
return ok
103+
}
104+
105+
func (c *Cache[K, V]) deleteOldest() {
106+
e := c.list.Back()
107+
c.delete(e)
108+
}
109+
110+
func (c *Cache[K, V]) delete(e *list.Element) {
111+
c.list.Remove(e)
112+
item := e.Value.(*item[K, V])
113+
delete(c.items, item.Key)
114+
}

lru/lru_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package lru_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/Code-Hex/go-generics-cache/lru"
7+
)
8+
9+
func TestSet(t *testing.T) {
10+
// set capacity is 1
11+
cache := lru.NewCap[string, int](1)
12+
cache.Set("foo", 1)
13+
if got := cache.Len(); got != 1 {
14+
t.Fatalf("invalid length: %d", got)
15+
}
16+
if got, ok := cache.Get("foo"); got != 1 || !ok {
17+
t.Fatalf("invalid value got %d, cachehit %v", got, ok)
18+
}
19+
20+
// if over the cap
21+
cache.Set("bar", 2)
22+
if got := cache.Len(); got != 1 {
23+
t.Fatalf("invalid length: %d", got)
24+
}
25+
bar, ok := cache.Get("bar")
26+
if bar != 2 || !ok {
27+
t.Fatalf("invalid value bar %d, cachehit %v", bar, ok)
28+
}
29+
30+
// checks deleted oldest
31+
if _, ok := cache.Get("foo"); ok {
32+
t.Fatalf("invalid delete oldest value foo %v", ok)
33+
}
34+
35+
// valid: if over the cap but same key
36+
cache.Set("bar", 100)
37+
if got := cache.Len(); got != 1 {
38+
t.Fatalf("invalid length: %d", got)
39+
}
40+
bar, ok = cache.Get("bar")
41+
if bar != 100 || !ok {
42+
t.Fatalf("invalid replacing value bar %d, cachehit %v", bar, ok)
43+
}
44+
}
45+
46+
func TestContains(t *testing.T) {
47+
cache := lru.New[string, int]()
48+
cache.Set("foo", 1)
49+
cache.Set("bar", 2)
50+
cache.Set("baz", 3)
51+
for _, key := range []string{
52+
"foo",
53+
"bar",
54+
"baz",
55+
} {
56+
if !cache.Contains(key) {
57+
t.Errorf("not found: %s", key)
58+
}
59+
}
60+
}
61+
62+
func TestDelete(t *testing.T) {
63+
cache := lru.NewCap[string, int](1)
64+
cache.Set("foo", 1)
65+
if got := cache.Len(); got != 1 {
66+
t.Fatalf("invalid length: %d", got)
67+
}
68+
69+
cache.Delete("foo2")
70+
if got := cache.Len(); got != 1 {
71+
t.Fatalf("invalid length after deleted does not exist key: %d", got)
72+
}
73+
74+
cache.Delete("foo")
75+
if got := cache.Len(); got != 0 {
76+
t.Fatalf("invalid length after deleted: %d", got)
77+
}
78+
if _, ok := cache.Get("foo"); ok {
79+
t.Fatalf("invalid get after deleted %v", ok)
80+
}
81+
82+
}

0 commit comments

Comments
 (0)