Initial commit

This commit is contained in:
NortPerm
2023-10-30 15:21:12 +03:00
committed by GitHub
commit 13e2575b4e
149 changed files with 3961 additions and 0 deletions

0
hw04_lru_cache/.sync Normal file
View File

84
hw04_lru_cache/README.md Normal file
View 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
View 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),
}
}

View 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
View 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
View 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
View 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)
}

View 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)
})
}