Merge branch '12-lru-cache' into 'master'
Resolve "lru cache" Closes #12 See merge request slon/shad-go-private!15
This commit is contained in:
commit
2dc5b49ade
4 changed files with 214 additions and 0 deletions
23
lrucache/README.md
Normal file
23
lrucache/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
## lrucache
|
||||
|
||||
В этой задаче нужно написать простой Least recently used cache.
|
||||
|
||||
LRU cache - это key-value storage фиксированного размера, реализующий операции:
|
||||
* `set(k, v)` - обновляет хранимое по ключу `k` значение.
|
||||
В случае, если операция приводит к превышению размера кэша,
|
||||
из того удаляется значение по самому "старому" ключу.
|
||||
* `get(k) -> v, ok` - возвращает значение, хранимое по ключу `k`.
|
||||
|
||||
Обе функции `set` и `get` обновляют access time ключа.
|
||||
|
||||
В файле [cache.go](./cache.go) задан интерфейс `Cache` с подробным описанием всех методов.
|
||||
|
||||
Нужно написать реализацию и конструктор, принимающий размер кэша:
|
||||
```
|
||||
func New(cap int) Cache
|
||||
```
|
||||
|
||||
## Ссылки
|
||||
|
||||
1. container/list: https://golang.org/pkg/container/list/
|
||||
2. wiki: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
|
22
lrucache/cache.go
Normal file
22
lrucache/cache.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
// +build !change
|
||||
|
||||
package lrucache
|
||||
|
||||
type Cache interface {
|
||||
// Get returns value associated with the key.
|
||||
//
|
||||
// The second value is a bool that is true if the key exists in the cache,
|
||||
// and false if not.
|
||||
Get(key int) (int, bool)
|
||||
// Set updates value associated with the key.
|
||||
//
|
||||
// If there is no key in the cache new (key, value) pair is created.
|
||||
Set(key, value int)
|
||||
// Range calls function f on all elements of the cache
|
||||
// in increasing access time order.
|
||||
//
|
||||
// Stops earlier if f returns false.
|
||||
Range(f func(key, value int) bool)
|
||||
// Clear removes all keys and values from the cache.
|
||||
Clear()
|
||||
}
|
162
lrucache/cache_test.go
Normal file
162
lrucache/cache_test.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
package lrucache
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCache_empty(t *testing.T) {
|
||||
c := New(0)
|
||||
|
||||
c.Set(1, 2)
|
||||
_, ok := c.Get(1)
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestCache_update(t *testing.T) {
|
||||
c := New(1)
|
||||
|
||||
_, ok := c.Get(1)
|
||||
require.False(t, ok)
|
||||
|
||||
c.Set(1, 2)
|
||||
v, ok := c.Get(1)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 2, v)
|
||||
}
|
||||
|
||||
func TestCache_Clear(t *testing.T) {
|
||||
c := New(5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Set(i, i)
|
||||
}
|
||||
|
||||
c.Clear()
|
||||
|
||||
for i := 9; i >= 0; i-- {
|
||||
c.Set(i, i)
|
||||
}
|
||||
|
||||
var keys, values []int
|
||||
c.Range(func(key, value int) bool {
|
||||
keys = append(keys, key)
|
||||
values = append(values, value)
|
||||
return true
|
||||
})
|
||||
|
||||
require.Equal(t, []int{4, 3, 2, 1, 0}, keys)
|
||||
require.Equal(t, []int{4, 3, 2, 1, 0}, values)
|
||||
}
|
||||
|
||||
func TestCache_Range(t *testing.T) {
|
||||
c := New(5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Set(i, i)
|
||||
}
|
||||
|
||||
var keys, values []int
|
||||
c.Range(func(key, value int) bool {
|
||||
keys = append(keys, key)
|
||||
values = append(values, value)
|
||||
return key < 8
|
||||
})
|
||||
|
||||
require.Equal(t, []int{5, 6, 7, 8}, keys)
|
||||
require.Equal(t, []int{5, 6, 7, 8}, values)
|
||||
}
|
||||
|
||||
func TestCache_eviction(t *testing.T) {
|
||||
r := rand.New(rand.NewSource(42))
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cap int
|
||||
numInserts int
|
||||
maxKey int32
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
cap: 0,
|
||||
numInserts: 10,
|
||||
maxKey: 10,
|
||||
},
|
||||
{
|
||||
name: "underused",
|
||||
cap: 100,
|
||||
numInserts: 90,
|
||||
maxKey: 1000,
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
cap: 100,
|
||||
numInserts: 10000,
|
||||
maxKey: 1000,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := New(tc.cap)
|
||||
|
||||
keyToValue := make(map[int]int)
|
||||
for i := 0; i < tc.numInserts; i++ {
|
||||
key := int(r.Int31n(tc.maxKey))
|
||||
c.Set(key, i)
|
||||
keyToValue[key] = i
|
||||
}
|
||||
|
||||
var keys, values []int
|
||||
c.Range(func(key, value int) bool {
|
||||
require.Equal(t, value, keyToValue[key])
|
||||
keys = append(keys, key)
|
||||
values = append(values, value)
|
||||
return true
|
||||
})
|
||||
|
||||
expectedLen := tc.cap
|
||||
if len(keyToValue) < tc.cap {
|
||||
expectedLen = len(keyToValue)
|
||||
}
|
||||
require.Len(t, values, expectedLen)
|
||||
require.True(t, sort.IntsAreSorted(values), "values: %+v", values)
|
||||
|
||||
for _, k := range keys {
|
||||
v, ok := c.Get(k)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, keyToValue[k], v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCache_Set(b *testing.B) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cap int
|
||||
}{
|
||||
{
|
||||
name: "small",
|
||||
cap: 1,
|
||||
},
|
||||
{
|
||||
name: "medium",
|
||||
cap: 1000,
|
||||
},
|
||||
{
|
||||
name: "large",
|
||||
cap: 1000000,
|
||||
},
|
||||
} {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
c := New(tc.cap)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.Set(i, i)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
7
lrucache/lru.go
Normal file
7
lrucache/lru.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !solution
|
||||
|
||||
package lrucache
|
||||
|
||||
func New(cap int) Cache {
|
||||
panic("implement me")
|
||||
}
|
Loading…
Reference in a new issue