Skip to content

Commit 7de70b9

Browse files
authored
Merge pull request #11 from Code-Hex/add/fifo
added FIFO
2 parents 8144927 + 7f4450d commit 7de70b9

File tree

6 files changed

+266
-7
lines changed

6 files changed

+266
-7
lines changed

README.md

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

77
- a thread-safe
88
- implemented with [Go Generics](https://go.dev/blog/generics-proposal)
9-
- Simple cache is like `map[string]interface{}` with expiration times
9+
- TTL supported (with expiration times)
10+
- Simple cache is like `map[string]interface{}`
1011
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/simple/example_test.go)
11-
- LRU cache
12-
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lru/example_test.go)
13-
- LFU cache
14-
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lfu/example_test.go)
15-
- [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf)
12+
- Cache replacement policies
13+
- Least recently used (LRU)
14+
- Discards the least recently used items first.
15+
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lru/example_test.go)
16+
- Least-frequently used (LFU)
17+
- Counts how often an item is needed. Those that are used least often are discarded first.
18+
- [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf)
19+
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/lfu/example_test.go)
20+
- First in first out (FIFO)
21+
- Using this algorithm the cache behaves in the same way as a [FIFO queue](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)).
22+
- 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.
23+
- See [examples](https://github.com/Code-Hex/go-generics-cache/blob/main/fifo/example_test.go)
1624

1725
## Requirements
1826

cache.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"sync"
55
"time"
66

7+
"github.com/Code-Hex/go-generics-cache/fifo"
78
"github.com/Code-Hex/go-generics-cache/lfu"
89
"github.com/Code-Hex/go-generics-cache/lru"
910
"github.com/Code-Hex/go-generics-cache/simple"
@@ -21,6 +22,7 @@ var (
2122
_ Interface[any, any] = (*simple.Cache[any, any])(nil)
2223
_ Interface[any, any] = (*lru.Cache[any, any])(nil)
2324
_ Interface[any, any] = (*lfu.Cache[any, any])(nil)
25+
_ Interface[any, any] = (*fifo.Cache[any, any])(nil)
2426
)
2527

2628
// Item is an item
@@ -95,6 +97,13 @@ func AsLFU[K comparable, V any](opts ...lfu.Option) Option[K, V] {
9597
}
9698
}
9799

100+
// AsFIFO is an option to make a new Cache as FIFO algorithm.
101+
func AsFIFO[K comparable, V any](opts ...fifo.Option) Option[K, V] {
102+
return func(o *options[K, V]) {
103+
o.cache = fifo.NewCache[K, *Item[K, V]](opts...)
104+
}
105+
}
106+
98107
// New creates a new Cache.
99108
func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] {
100109
o := newOptions[K, V]()

example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func ExampleCache() {
2121
}
2222

2323
func ExampleCacheWithExpiration() {
24-
c := cache.New(cache.AsLFU[string, int]())
24+
c := cache.New(cache.AsFIFO[string, int]())
2525
exp := 250 * time.Millisecond
2626
c.Set("a", 1, cache.WithExpiration(exp))
2727

fifo/example_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package fifo_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/Code-Hex/go-generics-cache/fifo"
7+
)
8+
9+
func ExampleCache() {
10+
c := fifo.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+
c.Delete("a")
20+
_, aok2 := c.Get("a")
21+
if !aok2 {
22+
fmt.Println("key 'a' has been deleted")
23+
}
24+
// update
25+
c.Set("b", 3)
26+
newbv, _ := c.Get("b")
27+
fmt.Println(newbv)
28+
// Output:
29+
// 1 true
30+
// 2 true
31+
// 0 false
32+
// key 'a' has been deleted
33+
// 3
34+
}
35+
36+
func ExampleCacheKeys() {
37+
c := fifo.NewCache[string, int]()
38+
c.Set("foo", 1)
39+
c.Set("bar", 2)
40+
c.Set("baz", 3)
41+
keys := c.Keys()
42+
for _, key := range keys {
43+
fmt.Println(key)
44+
}
45+
// Output:
46+
// foo
47+
// bar
48+
// baz
49+
}

fifo/fifo.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package fifo
2+
3+
import (
4+
"container/list"
5+
)
6+
7+
// Cache is used a FIFO (First in first out) cache replacement policy.
8+
// In FIFO the item that enter the cache first is evicted first
9+
// w/o any regard of how often or how many times it was accessed before.
10+
type Cache[K comparable, V any] struct {
11+
items map[K]*list.Element
12+
queue *list.List // keys
13+
capacity int
14+
}
15+
16+
type entry[K comparable, V any] struct {
17+
key K
18+
val V
19+
}
20+
21+
// Option is an option for FIFO 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 cache.
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+
items: make(map[K]*list.Element, o.capacity),
49+
queue: list.New(),
50+
capacity: o.capacity,
51+
}
52+
}
53+
54+
// Set sets any item to the cache. replacing any existing item.
55+
func (c *Cache[K, V]) Set(key K, val V) {
56+
if c.queue.Len() == c.capacity {
57+
e := c.dequeue()
58+
delete(c.items, e.Value.(*entry[K, V]).key)
59+
}
60+
c.Delete(key) // delete old key if already exists specified key.
61+
entry := &entry[K, V]{
62+
key: key,
63+
val: val,
64+
}
65+
e := c.queue.PushBack(entry)
66+
c.items[key] = e
67+
}
68+
69+
// Get gets an item from the cache.
70+
// Returns the item or zero value, and a bool indicating whether the key was found.
71+
func (c *Cache[K, V]) Get(k K) (val V, ok bool) {
72+
got, found := c.items[k]
73+
if !found {
74+
return
75+
}
76+
return got.Value.(*entry[K, V]).val, true
77+
}
78+
79+
// Keys returns cache keys.
80+
func (c *Cache[K, V]) Keys() []K {
81+
keys := make([]K, 0, len(c.items))
82+
for e := c.queue.Front(); e != nil; e = e.Next() {
83+
keys = append(keys, e.Value.(*entry[K, V]).key)
84+
}
85+
return keys
86+
}
87+
88+
// Delete deletes the item with provided key from the cache.
89+
func (c *Cache[K, V]) Delete(key K) {
90+
if e, ok := c.items[key]; ok {
91+
c.queue.Remove(e)
92+
delete(c.items, key)
93+
}
94+
}
95+
96+
// Len returns the number of items in the cache.
97+
func (c *Cache[K, V]) Len() int {
98+
return c.queue.Len()
99+
}
100+
101+
func (c *Cache[K, V]) dequeue() *list.Element {
102+
e := c.queue.Front()
103+
c.queue.Remove(e)
104+
return e
105+
}

fifo/fifo_test.go

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

0 commit comments

Comments
 (0)