Initial commit
This commit is contained in:
0
hw04_lru_cache/.sync
Normal file
0
hw04_lru_cache/.sync
Normal file
84
hw04_lru_cache/README.md
Normal file
84
hw04_lru_cache/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
## Домашнее задание №4 «LRU-кэш»
|
||||
Необходимо реализовать LRU-кэш на основе двусвязного списка.
|
||||
|
||||
Задание состоит из двух частей, которые необходимо выполнять последовательно.
|
||||
|
||||
### 1) Реализация двусвязного списка
|
||||
Список имеет структуру вида
|
||||
```text
|
||||
nil <- (prev) front <-> ... <-> elem <-> ... <-> back (next) -> nil
|
||||
```
|
||||
|
||||
Необходимо реализовать следующий интерфейс List:
|
||||
- Len() int // длина списка
|
||||
- Front() *ListItem // первый элемент списка
|
||||
- Back() *ListItem // последний элемент списка
|
||||
- PushFront(v interface{}) *ListItem // добавить значение в начало
|
||||
- PushBack(v interface{}) *ListItem // добавить значение в конец
|
||||
- Remove(i *ListItem) // удалить элемент
|
||||
- MoveToFront(i *ListItem) // переместить элемент в начало
|
||||
|
||||
**Считаем, что методы Remove и MoveToFront вызываются только от существующих в списке элементов.**
|
||||
|
||||
Элемент списка ListItem:
|
||||
- Value interface{} // значение
|
||||
- Next *ListItem // следующий элемент
|
||||
- Prev *ListItem // предыдущий элемент
|
||||
|
||||
Сложность всех операций должна быть O(1),
|
||||
т.е. не должно быть мест, где осуществляется полный обход списка.
|
||||
|
||||
### 2) Реализация кэша на основе ранее написанного списка
|
||||
Необходимо реализовать следующий интерфейс Cache:
|
||||
- Set(key Key, value interface{}) bool // Добавить значение в кэш по ключу.
|
||||
- Get(key Key) (interface{}, bool) // Получить значение из кэша по ключу.
|
||||
- Clear() // Очистить кэш.
|
||||
|
||||
Структура кэша:
|
||||
- ёмкость (количество сохраняемых в кэше элементов)
|
||||
- очередь \[последних используемых элементов\] на основе двусвязного списка
|
||||
- словарь, отображающий ключ (строка) на элемент очереди
|
||||
|
||||
Элемент кэша хранит в себе ключ, по которому он лежит в словаре, и само значение.
|
||||
Для чего это нужно понятно из алгоритма работы кэша (см. ниже).
|
||||
|
||||
Сложность операций `Set`/`Get` должна быть O(1), при желании `Clear` тоже можно сделать О(1).
|
||||
|
||||
Алгоритм работы кэша:
|
||||
- при добавлении элемента:
|
||||
- если элемент присутствует в словаре, то обновить его значение и переместить элемент в начало очереди;
|
||||
- если элемента нет в словаре, то добавить в словарь и в начало очереди
|
||||
(при этом, если размер очереди больше ёмкости кэша,
|
||||
то необходимо удалить последний элемент из очереди и его значение из словаря);
|
||||
- возвращаемое значение - флаг, присутствовал ли элемент в кэше.
|
||||
- при получении элемента:
|
||||
- если элемент присутствует в словаре, то переместить элемент в начало очереди и вернуть его значение и true;
|
||||
- если элемента нет в словаре, то вернуть nil и false
|
||||
(работа с кешом похожа на работу с `map`)
|
||||
|
||||
Ожидаются следующие тесты:
|
||||
- на логику выталкивания элементов из-за размера очереди
|
||||
(например: n = 3, добавили 4 элемента - 1й из кэша вытолкнулся);
|
||||
- на логику выталкивания давно используемых элементов
|
||||
(например: n = 3, добавили 3 элемента, обратились несколько раз к разным элементам:
|
||||
изменили значение, получили значение и пр. - добавили 4й элемент,
|
||||
из первой тройки вытолкнется тот элемент, что был затронут наиболее давно).
|
||||
|
||||
**(*) Дополнительное задание: сделать кэш горутино-безопасным.**
|
||||
|
||||
### Критерии оценки
|
||||
- Пайплайн зелёный - 4 балла
|
||||
- Добавлены новые юнит-тесты для списка - 1 балл
|
||||
- Добавлены новые юнит-тесты для кэша (см. выше) - до 3 баллов
|
||||
- Понятность и чистота кода - до 2 баллов
|
||||
- Дополнительное задание на баллы не влияет
|
||||
|
||||
#### Зачёт от 7 баллов
|
||||
|
||||
### Подсказки
|
||||
- https://en.wikipedia.org/wiki/Doubly_linked_list
|
||||
- https://ru.bmstu.wiki/LRU_(Least_Recently_Used)
|
||||
- `sync.Mutex`
|
||||
|
||||
### Частые ошибки
|
||||
1) В задании со звёздочкой забывают про синхронизацию в методе Clear кэша.
|
||||
25
hw04_lru_cache/cache.go
Normal file
25
hw04_lru_cache/cache.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package hw04lrucache
|
||||
|
||||
type Key string
|
||||
|
||||
type Cache interface {
|
||||
Set(key Key, value interface{}) bool
|
||||
Get(key Key) (interface{}, bool)
|
||||
Clear()
|
||||
}
|
||||
|
||||
type lruCache struct {
|
||||
Cache // Remove me after realization.
|
||||
|
||||
capacity int
|
||||
queue List
|
||||
items map[Key]*ListItem
|
||||
}
|
||||
|
||||
func NewCache(capacity int) Cache {
|
||||
return &lruCache{
|
||||
capacity: capacity,
|
||||
queue: NewList(),
|
||||
items: make(map[Key]*ListItem, capacity),
|
||||
}
|
||||
}
|
||||
79
hw04_lru_cache/cache_test.go
Normal file
79
hw04_lru_cache/cache_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package hw04lrucache
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
t.Run("empty cache", func(t *testing.T) {
|
||||
c := NewCache(10)
|
||||
|
||||
_, ok := c.Get("aaa")
|
||||
require.False(t, ok)
|
||||
|
||||
_, ok = c.Get("bbb")
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
c := NewCache(5)
|
||||
|
||||
wasInCache := c.Set("aaa", 100)
|
||||
require.False(t, wasInCache)
|
||||
|
||||
wasInCache = c.Set("bbb", 200)
|
||||
require.False(t, wasInCache)
|
||||
|
||||
val, ok := c.Get("aaa")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 100, val)
|
||||
|
||||
val, ok = c.Get("bbb")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 200, val)
|
||||
|
||||
wasInCache = c.Set("aaa", 300)
|
||||
require.True(t, wasInCache)
|
||||
|
||||
val, ok = c.Get("aaa")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 300, val)
|
||||
|
||||
val, ok = c.Get("ccc")
|
||||
require.False(t, ok)
|
||||
require.Nil(t, val)
|
||||
})
|
||||
|
||||
t.Run("purge logic", func(t *testing.T) {
|
||||
// Write me
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheMultithreading(t *testing.T) {
|
||||
t.Skip() // Remove me if task with asterisk completed.
|
||||
|
||||
c := NewCache(10)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
c.Set(Key(strconv.Itoa(i)), i)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
c.Get(Key(strconv.Itoa(rand.Intn(1_000_000))))
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
11
hw04_lru_cache/go.mod
Normal file
11
hw04_lru_cache/go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/fixme_my_friend/hw04_lru_cache
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/stretchr/testify v1.7.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
13
hw04_lru_cache/go.sum
Normal file
13
hw04_lru_cache/go.sum
Normal file
@@ -0,0 +1,13 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
26
hw04_lru_cache/list.go
Normal file
26
hw04_lru_cache/list.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package hw04lrucache
|
||||
|
||||
type List interface {
|
||||
Len() int
|
||||
Front() *ListItem
|
||||
Back() *ListItem
|
||||
PushFront(v interface{}) *ListItem
|
||||
PushBack(v interface{}) *ListItem
|
||||
Remove(i *ListItem)
|
||||
MoveToFront(i *ListItem)
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
Value interface{}
|
||||
Next *ListItem
|
||||
Prev *ListItem
|
||||
}
|
||||
|
||||
type list struct {
|
||||
List // Remove me after realization.
|
||||
// Place your code here.
|
||||
}
|
||||
|
||||
func NewList() List {
|
||||
return new(list)
|
||||
}
|
||||
51
hw04_lru_cache/list_test.go
Normal file
51
hw04_lru_cache/list_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package hw04lrucache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
t.Run("empty list", func(t *testing.T) {
|
||||
l := NewList()
|
||||
|
||||
require.Equal(t, 0, l.Len())
|
||||
require.Nil(t, l.Front())
|
||||
require.Nil(t, l.Back())
|
||||
})
|
||||
|
||||
t.Run("complex", func(t *testing.T) {
|
||||
l := NewList()
|
||||
|
||||
l.PushFront(10) // [10]
|
||||
l.PushBack(20) // [10, 20]
|
||||
l.PushBack(30) // [10, 20, 30]
|
||||
require.Equal(t, 3, l.Len())
|
||||
|
||||
middle := l.Front().Next // 20
|
||||
l.Remove(middle) // [10, 30]
|
||||
require.Equal(t, 2, l.Len())
|
||||
|
||||
for i, v := range [...]int{40, 50, 60, 70, 80} {
|
||||
if i%2 == 0 {
|
||||
l.PushFront(v)
|
||||
} else {
|
||||
l.PushBack(v)
|
||||
}
|
||||
} // [80, 60, 40, 10, 30, 50, 70]
|
||||
|
||||
require.Equal(t, 7, l.Len())
|
||||
require.Equal(t, 80, l.Front().Value)
|
||||
require.Equal(t, 70, l.Back().Value)
|
||||
|
||||
l.MoveToFront(l.Front()) // [80, 60, 40, 10, 30, 50, 70]
|
||||
l.MoveToFront(l.Back()) // [70, 80, 60, 40, 10, 30, 50]
|
||||
|
||||
elems := make([]int, 0, l.Len())
|
||||
for i := l.Front(); i != nil; i = i.Next {
|
||||
elems = append(elems, i.Value.(int))
|
||||
}
|
||||
require.Equal(t, []int{70, 80, 60, 40, 10, 30, 50}, elems)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user