Repository: mrLexx/cool-golang
Pull request #4: HW4 is completed
Author: login: mrLexx
Created: 2025-03-04T20:57:13Z | Merged: 2025-03-11T20:42:04Z

## Домашнее задание №4 «LRU-кэш»

### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0))
- [x] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5)
- [x] Я запустил `go mod tidy`.
- [x] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2)
- [x] Я проверил, что все операции выполняются за О(1).

### Критерии оценки
- [ ] Пайплайн зелёный - 4 балла
- [ ] Добавлены новые юнит-тесты для списка - 1 балл
- [ ] Добавлены новые юнит-тесты для кэша (включая тест на логику
выталкивания из кэша редко используемых элементов) - до 3 баллов
- [ ] Понятность и чистота кода - до 2 баллов

#### Зачёт от 7 баллов



Diff:
diff --git a/hw04_lru_cache/.sync b/hw04_lru_cache/.sync
deleted file mode 100644
index e69de29..0000000
diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go
index a43d727..471c9cf 100644
--- a/hw04_lru_cache/cache.go
+++ b/hw04_lru_cache/cache.go
@@ -1,5 +1,10 @@
 package hw04lrucache
 
+import (
+	"errors"
+	"sync"
+)
+
 type Key string
 
 type Cache interface {
@@ -9,17 +14,77 @@ type Cache interface {
 }
 
 type lruCache struct {
-	Cache // Remove me after realization.
-
-	capacity int
+	sync.Mutex
 	queue    List
 	items    map[Key]*ListItem
+	capacity int
+}
+
+type lruCacheItem struct {
+	key   Key
+	value interface{}
 }
 
 func NewCache(capacity int) Cache {
+	if capacity < 0 {
+		err := errors.New("capacity must >= 0")
+		panic(err)
+	}
+
 	return &lruCache{
 		capacity: capacity,
 		queue:    NewList(),
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	if c.capacity == 0 {
+		return false
+	}
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
+		last := c.queue.Back()
+		c.queue.Remove(last)
+		delete(c.items, last.Value.(lruCacheItem).key)
+	}
+
+	v := lruCacheItem{key: key, value: value}
+	switch {
+	case !ok:
+		item = c.queue.PushFront(v)
+		c.items[key] = item
+	default:
+		item.Value = v
+		c.queue.MoveToFront(item)
+	}
+
+	return ok
+}
+
+func (c *lruCache) Get(key Key) (interface{}, bool) {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok {
+		return nil, false
+	}
+
+	c.queue.MoveToFront(item)
+
+	return item.Value.(lruCacheItem).value, true
+}
+
+func (c *lruCache) Clear() {
+	c.Lock()
+	defer c.Unlock()
+	c.items = make(map[Key]*ListItem, c.capacity)
+	c.queue = NewList()
+}
diff --git a/hw04_lru_cache/cache_test.go b/hw04_lru_cache/cache_test.go
index 2b43d4c..83ea427 100644
--- a/hw04_lru_cache/cache_test.go
+++ b/hw04_lru_cache/cache_test.go
@@ -49,13 +49,113 @@ func TestCache(t *testing.T) {
 		require.Nil(t, val)
 	})
 
-	t.Run("purge logic", func(t *testing.T) {
-		// Write me
+	t.Run("purge logic, overflow and clean", func(t *testing.T) {
+		c := NewCache(5)
+		c.Set("a01", 100) // a01
+		c.Set("a02", 200) // a02, a01
+		c.Set("a03", 300) // a03, a02, a01
+		c.Set("a04", 400) // a04, a03, a02, a01
+		c.Set("a05", 500) // a05, a04, a03, a02, a01
+		c.Set("a06", 500) // a06, a05, a04, a03, a02
+
+		val, ok := c.Get("a01")
+		require.False(t, ok)
+		require.Nil(t, val)
+
+		c.Clear()
+
+		check := []Key{"a02", "a03", "a04", "a05", "a06"}
+		for _, k := range check {
+			val, ok = c.Get(k)
+			require.False(t, ok)
+			require.Nil(t, val)
+		}
+	})
+
+	t.Run("purge logic, move to front by Set", func(t *testing.T) {
+		c := NewCache(5)
+
+		c.Set("a01", 100) // a01
+		c.Set("a02", 200) // a02, a01
+		c.Set("a03", 300) // a03, a02, a01
+		c.Set("a04", 400) // a04, a03, a02, a01
+		c.Set("a05", 500) // a05, a04, a03, a02, a01
+		c.Set("a01", 100) // a01, a05, a04, a03, a02
+		c.Set("a02", 200) // a02, a01, a05, a04, a03
+		c.Set("a06", 600) // a06, a02, a01, a05, a04
+
+		val, ok := c.Get("a03")
+		require.False(t, ok)
+		require.Nil(t, val)
+	})
+
+	t.Run("purge logic, move to front by Get", func(t *testing.T) {
+		c := NewCache(5)
+
+		c.Clear()
+		c.Set("a01", 100) // a01
+		c.Set("a02", 200) // a02, a01
+		c.Set("a03", 300) // a03, a02, a01
+		c.Set("a04", 400) // a04, a03, a02, a01
+		c.Set("a05", 500) // a05, a04, a03, a02, a01
+		c.Get("a03")      // a03, a05, a04, a02, a01
+		c.Get("a04")      // a04, a03, a05, a02, a01
+		c.Get("a03")      // a03, a04, a05, a02, a01
+		c.Set("a06", 600) // a06, a03, a04, a05, a02
+		c.Set("a07", 700) // a07, a06, a03, a04, a05
+		c.Set("a08", 800) // a08, a07, a06, a03, a04
+		c.Set("a09", 900) // a09, a08, a07, a06, a03
+
+		check := []Key{"a01", "a02", "a04", "a05"}
+		for _, k := range check {
+			val, ok := c.Get(k)
+			require.False(t, ok)
+			require.Nil(t, val)
+		}
+
+		check = []Key{"a03", "a06", "a07", "a08", "a09"}
+		for _, k := range check {
+			_, ok := c.Get(k)
+			require.True(t, ok)
+		}
+
+		c.Set("a10", 1000)
+		val, ok := c.Get("a01")
+		require.False(t, ok)
+		require.Nil(t, val)
+	})
+
+	t.Run("capacity, less than zero", func(t *testing.T) {
+		defer func() {
+			r := recover()
+			require.NotNil(t, r)
+		}()
+
+		c := NewCache(-1)
+		c.Clear()
+		_, ok := c.Get("a01")
+		require.False(t, ok)
+
+		c.Set("a01", 100) // panic
+	})
+
+	t.Run("capacity, equals zero", func(t *testing.T) {
+		defer func() {
+			r := recover()
+			require.Nil(t, r)
+		}()
+
+		c := NewCache(0)
+		c.Clear()
+		c.Set("a01", 100) // []
+		val, ok := c.Get("a01")
+		require.Nil(t, val)
+		require.False(t, ok)
 	})
 }
 
 func TestCacheMultithreading(t *testing.T) {
-	t.Skip() // Remove me if task with asterisk completed.
+	_ = t
 
 	c := NewCache(10)
 	wg := &sync.WaitGroup{}
diff --git a/hw04_lru_cache/go.mod b/hw04_lru_cache/go.mod
index 108b8cb..3b85f97 100644
--- a/hw04_lru_cache/go.mod
+++ b/hw04_lru_cache/go.mod
@@ -1,4 +1,4 @@
-module github.com/fixme_my_friend/hw04_lru_cache
+module github.com/mrLexx/cool-golang/hw04_lru_cache
 
 go 1.23
 
diff --git a/hw04_lru_cache/go.sum b/hw04_lru_cache/go.sum
index e3bec2c..713a0b4 100644
--- a/hw04_lru_cache/go.sum
+++ b/hw04_lru_cache/go.sum
@@ -1,17 +1,10 @@
-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=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 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=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/hw04_lru_cache/list.go b/hw04_lru_cache/list.go
index 928ebb5..5e1bb59 100644
--- a/hw04_lru_cache/list.go
+++ b/hw04_lru_cache/list.go
@@ -17,10 +17,98 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+
+	default:
+		list.back.Next = item
+		item.Prev = list.back
+		list.back = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) Remove(i *ListItem) {
+	if list.len == 0 {
+		return
+	}
+
+	switch {
+	case i.Prev == nil && i.Next == nil:
+		list.back, list.front = nil, nil
+
+	case i.Prev != nil && i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	case i.Prev != nil:
+		i.Prev.Next = nil
+		list.back = i.Prev
+
+	case i.Next != nil:
+		i.Next.Prev = nil
+		list.front = i.Next
+	}
+
+	list.len--
+}
+
+func (list *list) MoveToFront(i *ListItem) {
+	if list.front == i {
+		return
+	}
+
+	switch {
+	case i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	default:
+		i.Prev.Next = nil
+		list.back = i.Prev
+	}
+	i.Prev, i.Next = nil, list.front
+	list.front.Prev, list.front = i, i
+}
diff --git a/hw04_lru_cache/list_test.go b/hw04_lru_cache/list_test.go
index e8c93b2..0ea328e 100644
--- a/hw04_lru_cache/list_test.go
+++ b/hw04_lru_cache/list_test.go
@@ -48,4 +48,105 @@ func TestList(t *testing.T) {
 		}
 		require.Equal(t, []int{70, 80, 60, 40, 10, 30, 50}, elems)
 	})
+
+	t.Run("front and back", func(t *testing.T) {
+		l := NewList()
+
+		l.PushFront(10) // 10
+		require.Equal(t, true, l.Front() == l.Back())
+		require.Equal(t, 10, l.Front().Value)
+		require.Equal(t, 10, l.Back().Value)
+
+		l = NewList()
+		l.PushBack(20) // 10
+		require.Equal(t, true, l.Front() == l.Back())
+		require.Equal(t, 20, l.Front().Value)
+		require.Equal(t, 20, l.Back().Value)
+	})
+
+	t.Run("move to front", func(t *testing.T) {
+		l := NewList()
+
+		l.PushFront(10) // [10]
+		require.Equal(t, 10, l.Front().Value)
+		require.Equal(t, 10, l.Back().Value)
+
+		front := l.Front()
+		l.MoveToFront(front)
+		require.Equal(t, 10, l.Front().Value)
+		require.Equal(t, 10, l.Back().Value)
+
+		l.PushBack(20)
+		l.PushBack(30)
+		l.PushBack(40)
+		l.PushBack(50) // [10, 20, 30, 40, 50]
+
+		front = l.Front()
+		l.MoveToFront(front) // [10, 20, 30, 40, 50]
+		require.Equal(t, 10, l.Front().Value)
+
+		back := l.Back()
+		l.MoveToFront(back) // [50, 10, 20, 30, 40]
+		require.Equal(t, 50, l.Front().Value)
+
+		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{50, 10, 20, 30, 40}, elems)
+	})
+
+	t.Run("move to front, part 2", func(t *testing.T) {
+		_ = t
+		l := NewList()
+		var a [5]*ListItem
+		a[0] = l.PushFront(100)
+		a[1] = l.PushFront(110)
+		a[2] = l.PushFront(120)
+		a[3] = l.PushFront(130)
+		a[4] = l.PushFront(140) // [140, 130, 120, 110, 100]
+
+		l.MoveToFront(a[2]) // [120, 140, 130, 110, 100]
+		l.MoveToFront(a[3]) // [130, 120, 140, 110, 100]
+		l.MoveToFront(a[2]) // [120, 130, 140, 110, 100]
+		l.MoveToFront(a[3]) // [130, 120, 140, 110, 100]
+
+		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{130, 120, 140, 110, 100}, elems)
+	})
+
+	t.Run("remove", func(t *testing.T) {
+		l := NewList()
+
+		l.PushFront(10)
+		l.PushBack(20)
+		l.PushBack(30)
+		l.PushBack(40)
+		l.PushBack(50) // [10, 20, 30, 40, 50]
+
+		back := l.Back()
+		l.Remove(back) // [10, 20, 30, 40]
+		require.Equal(t, 40, l.Back().Value)
+		require.Equal(t, 4, l.Len())
+
+		front := l.Front()
+		l.Remove(front) // [20, 30, 40]
+		require.Equal(t, 20, l.Front().Value)
+		require.Equal(t, 3, l.Len())
+
+		l.Remove(l.Front()) // [30, 40]
+		l.Remove(l.Front()) // [40]
+		l.Remove(l.Front()) // []
+		l.Remove(l.Front()) // []
+		require.Equal(t, 0, l.Len())
+
+		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{}, elems)
+	})
 }


Reviews:
user: login: hungry-honeybadger
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T16:50:32Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T18:44:18Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T18:44:31Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T18:44:43Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T18:45:30Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T18:45:39Z
user: login: hungry-honeybadger
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T19:15:59Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T20:28:06Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T20:29:21Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T20:52:45Z
user: login: hungry-honeybadger
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T05:49:01Z
user: login: hungry-honeybadger
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-11T14:02:44Z
user: login: mrLexx
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-11T19:14:45Z
user: login: hungry-honeybadger
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-11T19:59:50Z

Review comments:
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
path: hw04_lru_cache/cache.go
user: login: hungry-honeybadger
body: Это так работать не будет. Если элемент уже есть то такой подход приведет к тому, что в листе может лежать несколько экземпляров одинаковых элементов в разных позиция.
created_at: 2025-03-05T16:42:45Z
author_association: COLLABORATOR
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
+	c.items[key] = q
+
+	if c.queue.Len() > c.capacity {
+		last := c.queue.Back()
+		delete(c.items, last.Value.(lruCacheItem).key)
+		c.queue.Remove(last)
+	}
+	return ok
+}
+
+func (c *lruCache) get(key Key) (interface{}, bool) {
+	item, ok := c.items[key]
+
+	if !ok {
+		return nil, false
+	}
+
+	c.queue.Remove(item)
path: hw04_lru_cache/cache.go
user: login: hungry-honeybadger
body: Зачем делать 2 вызовы есть у нас есть MoveToFront
created_at: 2025-03-05T16:43:13Z
author_association: COLLABORATOR
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
+	c.items[key] = q
+
+	if c.queue.Len() > c.capacity {
+		last := c.queue.Back()
+		delete(c.items, last.Value.(lruCacheItem).key)
+		c.queue.Remove(last)
+	}
+	return ok
+}
+
+func (c *lruCache) get(key Key) (interface{}, bool) {
+	item, ok := c.items[key]
+
+	if !ok {
+		return nil, false
+	}
+
+	c.queue.Remove(item)
+	c.queue.PushFront(item.Value)
+	return item.Value.(lruCacheItem).value, true
+}
+
+func (c *lruCache) clear() {
+	c.items = make(map[Key]*ListItem, c.capacity)
+	c.queue = NewList()
+}
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
path: hw04_lru_cache/cache.go
user: login: hungry-honeybadger
body: Идея разибвать метод на Set и set выглядит очень странной и не несет в себе никаких плюсов
created_at: 2025-03-05T16:44:36Z
author_association: COLLABORATOR
diff_hunk: @@ -17,10 +17,89 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front = item
+		list.back = item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	if list.len == 0 {
+		return list.PushFront(v)
path: hw04_lru_cache/list.go
user: login: hungry-honeybadger
body: Если уж есть желание не писать один и тот же код в 2 методах, то лучше создать 3 метод например pushFirst и дергать его в 2 методах.
created_at: 2025-03-05T16:46:58Z
author_association: COLLABORATOR
diff_hunk: @@ -17,10 +17,89 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front = item
+		list.back = item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	if list.len == 0 {
+		return list.PushFront(v)
+	}
+
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	item.Prev = list.back
+	list.back.Next = item
+	list.back = item
+
+	list.len++
+	return list.front
+}
+
+func (list *list) Remove(i *ListItem) {
+	if list.len == 0 {
+		return
+	}
+
+	switch {
+	case i.Prev == nil && i.Next == nil:
+		list.back, list.front = nil, nil
+
+	case i.Prev != nil && i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	case i.Prev != nil:
+		i.Prev.Next = nil
+		list.back = i.Prev
+
+	case i.Next != nil:
+		i.Next.Prev = nil
+		list.front = i.Next
+	}
+
+	list.len--
+}
+
+func (list *list) MoveToFront(i *ListItem) {
+	if list.len > 1 {
path: hw04_lru_cache/list.go
user: login: hungry-honeybadger
body: С учетом того. что по условию задания гарантируется что элемент точно есть в листе, то эта проверка выглядит излишний. На ее место лучше было бы поставить проверку что i не является первым элементов. И для улучшения читаемости лучше не запихивать весь блок кода в if а сделать что то типа
```
if list.front==I{
    return
}
```
created_at: 2025-03-05T16:50:16Z
author_association: COLLABORATOR
diff_hunk: @@ -17,10 +17,89 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front = item
+		list.back = item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	if list.len == 0 {
+		return list.PushFront(v)
+	}
+
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	item.Prev = list.back
+	list.back.Next = item
+	list.back = item
+
+	list.len++
+	return list.front
+}
+
+func (list *list) Remove(i *ListItem) {
+	if list.len == 0 {
+		return
+	}
+
+	switch {
+	case i.Prev == nil && i.Next == nil:
+		list.back, list.front = nil, nil
+
+	case i.Prev != nil && i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	case i.Prev != nil:
+		i.Prev.Next = nil
+		list.back = i.Prev
+
+	case i.Next != nil:
+		i.Next.Prev = nil
+		list.front = i.Next
+	}
+
+	list.len--
+}
+
+func (list *list) MoveToFront(i *ListItem) {
+	if list.len > 1 {
path: hw04_lru_cache/list.go
user: login: mrLexx
body: исправлено.
+ найдена и исправлена ошибка

created_at: 2025-03-10T18:44:18Z
author_association: OWNER
diff_hunk: @@ -17,10 +17,89 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front = item
+		list.back = item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	if list.len == 0 {
+		return list.PushFront(v)
path: hw04_lru_cache/list.go
user: login: mrLexx
body: Исправлено
created_at: 2025-03-10T18:44:31Z
author_association: OWNER
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
+	c.items[key] = q
+
+	if c.queue.Len() > c.capacity {
+		last := c.queue.Back()
+		delete(c.items, last.Value.(lruCacheItem).key)
+		c.queue.Remove(last)
+	}
+	return ok
+}
+
+func (c *lruCache) get(key Key) (interface{}, bool) {
+	item, ok := c.items[key]
+
+	if !ok {
+		return nil, false
+	}
+
+	c.queue.Remove(item)
+	c.queue.PushFront(item.Value)
+	return item.Value.(lruCacheItem).value, true
+}
+
+func (c *lruCache) clear() {
+	c.items = make(map[Key]*ListItem, c.capacity)
+	c.queue = NewList()
+}
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Исправлено
created_at: 2025-03-10T18:44:43Z
author_association: OWNER
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
+	c.items[key] = q
+
+	if c.queue.Len() > c.capacity {
+		last := c.queue.Back()
+		delete(c.items, last.Value.(lruCacheItem).key)
+		c.queue.Remove(last)
+	}
+	return ok
+}
+
+func (c *lruCache) get(key Key) (interface{}, bool) {
+	item, ok := c.items[key]
+
+	if !ok {
+		return nil, false
+	}
+
+	c.queue.Remove(item)
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Исправлено
created_at: 2025-03-10T18:45:30Z
author_association: OWNER
diff_hunk: @@ -23,3 +34,53 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) set(key Key, value interface{}) bool {
+	_, ok := c.items[key]
+
+	v := lruCacheItem{key: key, value: value}
+	q := c.queue.PushFront(v)
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Исправлено
created_at: 2025-03-10T18:45:38Z
author_association: OWNER
diff_hunk: @@ -23,3 +26,54 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
+		last := c.queue.Back()
+		c.queue.Remove(last)
+		for k, v := range c.items {
path: hw04_lru_cache/cache.go
user: login: hungry-honeybadger
body: Тут все было правильно. Теперь тут ошибка так как итерация по мапе имеет линейную сложность, а не константную 
created_at: 2025-03-10T19:15:55Z
author_association: COLLABORATOR
diff_hunk: @@ -23,3 +26,54 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
+		last := c.queue.Back()
+		c.queue.Remove(last)
+		for k, v := range c.items {
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Замечание испарвлено. Вернул обратно lruCacheItem
created_at: 2025-03-10T20:28:06Z
author_association: OWNER
diff_hunk: @@ -23,3 +26,54 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
+		last := c.queue.Back()
+		c.queue.Remove(last)
+		for k, v := range c.items {
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Также в функции Set исправлено ошибка, из-за которой - мапа сначала увеличивалась сверх ограничения  и только потом "урезалась"
created_at: 2025-03-10T20:29:21Z
author_association: OWNER
diff_hunk: @@ -23,3 +26,54 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
path: hw04_lru_cache/cache.go
user: login: mrLexx
body: Владислав, подскажите пожалуйста. В данном месте условия на сравнения будет достаточно? По идее, даже если в очереди будет несколько гуратин на "установку" значение, они не "пропустят" это условие в любом случае.

P.S. 
```go
if !ok && c.queue.Len() >= c.capacity {
}
```
было бы гарантировано. но как то избыточно
created_at: 2025-03-10T20:52:44Z
author_association: OWNER
diff_hunk: @@ -23,3 +26,54 @@ func NewCache(capacity int) Cache {
 		items:    make(map[Key]*ListItem, capacity),
 	}
 }
+
+func (c *lruCache) Set(key Key, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+
+	item, ok := c.items[key]
+
+	if !ok && c.queue.Len() == c.capacity {
path: hw04_lru_cache/cache.go
user: login: hungry-honeybadger
body: На это надо смотреть немного шире. По сути `>=`  и `==` создают абсолютно разное поведение кэша. Вот смотрите. 
Если используется 1 вариант то если мы задали размер кэша -1 то в данном случае когда мы пойдем добавлять 1 элемент, то мы получим панику и она будет вполне корректной. 
Если использовать 2 вариант, то если мы задали размер кэша -1 то мы получим бесконечный кэш. Что тоже является вполне валидным решением. 

Но в этой строчке кода кроется и проблема. 0 это валидный размер кэша, который значит что мы кэш отключаем, но в любом из случаев мы тут получим панику. А этого быть не должно и это надо поправить
created_at: 2025-03-11T05:49:01Z
author_association: COLLABORATOR
diff_hunk: @@ -17,10 +17,98 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+
+	default:
+		list.back.Next = item
+		item.Prev = list.back
+		list.back = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) Remove(i *ListItem) {
+	if list.len == 0 {
+		return
+	}
+
+	switch {
+	case i.Prev == nil && i.Next == nil:
+		list.back, list.front = nil, nil
+
+	case i.Prev != nil && i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	case i.Prev != nil:
+		i.Prev.Next = nil
+		list.back = i.Prev
+
+	case i.Next != nil:
+		i.Next.Prev = nil
+		list.front = i.Next
+	}
+
+	list.len--
+}
+
+func (list *list) MoveToFront(i *ListItem) {
+	if list.front == i {
+		return
+	}
+
+	switch {
path: hw04_lru_cache/list.go
user: login: hungry-honeybadger
body: У нас в этом блоке switch постоянно идет проверка i.Prev != nil, но это условие будет постоянно истино, так как единственный вариант когда это условие могло быть ложным мы отсекли в 100 строке
created_at: 2025-03-11T14:01:45Z
author_association: COLLABORATOR
diff_hunk: @@ -17,10 +17,98 @@ type ListItem struct {
 }
 
 type list struct {
-	List // Remove me after realization.
-	// Place your code here.
+	len int
+
+	front *ListItem
+	back  *ListItem
 }
 
 func NewList() List {
 	return new(list)
 }
+
+func (list list) Len() int {
+	return list.len
+}
+
+func (list list) Front() *ListItem {
+	return list.front
+}
+
+func (list list) Back() *ListItem {
+	return list.back
+}
+
+func (list *list) PushFront(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+	default:
+		list.front.Prev = item
+		item.Next = list.front
+		list.front = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) PushBack(v interface{}) *ListItem {
+	item := &ListItem{Value: v, Next: nil, Prev: nil}
+
+	switch list.len {
+	case 0:
+		list.front, list.back = item, item
+
+	default:
+		list.back.Next = item
+		item.Prev = list.back
+		list.back = item
+	}
+
+	list.len++
+	return list.front
+}
+
+func (list *list) Remove(i *ListItem) {
+	if list.len == 0 {
+		return
+	}
+
+	switch {
+	case i.Prev == nil && i.Next == nil:
+		list.back, list.front = nil, nil
+
+	case i.Prev != nil && i.Next != nil:
+		i.Prev.Next, i.Next.Prev = i.Next, i.Prev
+
+	case i.Prev != nil:
+		i.Prev.Next = nil
+		list.back = i.Prev
+
+	case i.Next != nil:
+		i.Next.Prev = nil
+		list.front = i.Next
+	}
+
+	list.len--
+}
+
+func (list *list) MoveToFront(i *ListItem) {
+	if list.front == i {
+		return
+	}
+
+	switch {
path: hw04_lru_cache/list.go
user: login: mrLexx
body: Исправлено
created_at: 2025-03-11T19:14:45Z
author_association: OWNER

PR comments:
user: login: mrLexx
created_at: 2025-03-04T21:16:36Z
body: @vladislavmass 
Метод Clear для кеша реализовал через переинецилизацию переменных, исходя из того, что после перинецилизации "старые" переменные становятся недостежимыми, и почистятся сборщиком мусора. Теоретически это гораздо выгодней чем поштучно удалять как их очереди, так и из самого кеша.

P.S. Теоретически можно вручную пробовать запускать сборщик мусора runtime.GC(), но только зачем? :)
author_association: OWNER
user: login: mrLexx
created_at: 2025-03-11T11:08:39Z
body: Добавлены ограничения на емкость:
-1 - вызывает панику в "контсрукторе" (лучше вызывать панику при иницилизации массива, чем в дальнейшем, эта ошибка "уйдет" в код)
0 - отключает кеш
author_association: OWNER