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