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:
verytable 2020-03-12 19:18:49 +00:00
commit 2dc5b49ade
4 changed files with 214 additions and 0 deletions

23
lrucache/README.md Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,7 @@
// +build !solution
package lrucache
func New(cap int) Cache {
panic("implement me")
}