From 89a2446ceff584f9f079a7f6c74e20a38a3c7109 Mon Sep 17 00:00:00 2001
From: Andrey Ivanov <tiburon-777@users.noreply.github.com>
Date: Mon, 22 Jun 2020 17:06:12 +0300
Subject: [PATCH 1/4] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83?=
 =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC?=
 =?UTF-8?q?=D0=B8=D1=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 hw04_lru_cache/cache.go      |  50 +++++++++++++++---
 hw04_lru_cache/cache_test.go |  26 ++++++++-
 hw04_lru_cache/go.mod        |   2 +-
 hw04_lru_cache/list.go       | 100 ++++++++++++++++++++++++++++++++---
 hw04_lru_cache/list_test.go  |  83 ++++++++++++++++++++++-------
 5 files changed, 224 insertions(+), 37 deletions(-)

diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go
index 738399e..dd93cfd 100644
--- a/hw04_lru_cache/cache.go
+++ b/hw04_lru_cache/cache.go
@@ -3,20 +3,54 @@ package hw04_lru_cache //nolint:golint,stylecheck
 type Key string
 
 type Cache interface {
-	// Place your code here
+	Set(key Key, value interface{}) bool // Добавить значение в кэш по ключу
+	Get(key Key) (interface{}, bool)     // Получить значение из кэша по ключу
+	Clear()                              // Очистить кэш
 }
 
 type lruCache struct {
-	// Place your code here:
-	// - capacity
-	// - queue
-	// - items
+	capacity int
+	queue    *List
+	items    map[Key]*ListItem
 }
 
-type cacheItem struct {
-	// Place your code here
+type Item struct {
+	Key   Key
+	Value interface{}
 }
 
 func NewCache(capacity int) Cache {
-	return &lruCache{}
+	return &lruCache{
+		capacity: capacity,
+		queue:    NewList(),
+		items:    map[Key]*ListItem{},
+	}
+}
+
+func (l *lruCache) Set(key Key, value interface{}) bool {
+	if _, exists := l.Get(key); exists {
+		l.items[key].Value = value
+		return exists
+	}
+	if l.queue.Len() >= l.capacity {
+		// TODO: нужно как-то удалить элемент из мапа
+		//delete(l.items,l.queue.Back().(*Item).Key)
+		l.queue.Remove(l.queue.Back())
+	}
+	l.items[key] = l.queue.PushFront(value)
+	return false
+}
+
+func (l *lruCache) Get(key Key) (interface{}, bool) {
+	if l.items[key] == nil {
+		return nil, false
+	}
+	l.queue.MoveToFront(l.items[key])
+	return l.items[key].Value, true
+}
+
+func (l *lruCache) Clear() {
+	l.items = nil
+	l.queue.len = 0
+	l.queue.Info = ListItem{}
 }
diff --git a/hw04_lru_cache/cache_test.go b/hw04_lru_cache/cache_test.go
index e99447d..fd3ea5c 100644
--- a/hw04_lru_cache/cache_test.go
+++ b/hw04_lru_cache/cache_test.go
@@ -50,7 +50,31 @@ func TestCache(t *testing.T) {
 	})
 
 	t.Run("purge logic", func(t *testing.T) {
-		// Write me
+		c := NewCache(3)
+
+		wasInCache := c.Set("aaa", 100)
+		require.False(t, wasInCache)
+
+		wasInCache = c.Set("bbb", 200)
+		require.False(t, wasInCache)
+
+		wasInCache = c.Set("ccc", 300)
+		require.False(t, wasInCache)
+
+		_, ok := c.Get("bbb")
+		require.True(t, ok)
+
+		_, ok = c.Get("aaa")
+		require.True(t, ok)
+
+		wasInCache = c.Set("ddd", 400)
+		require.False(t, wasInCache)
+
+		_, ok = c.Get("ddd")
+		require.True(t, ok)
+
+		_, ok = c.Get("ccc")
+		require.False(t, ok)
 	})
 }
 
diff --git a/hw04_lru_cache/go.mod b/hw04_lru_cache/go.mod
index b4abc63..ba531d5 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/tiburon-777/HW_OTUS/hw04_lru_cache
 
 go 1.14
 
diff --git a/hw04_lru_cache/list.go b/hw04_lru_cache/list.go
index eab309f..8e7db93 100644
--- a/hw04_lru_cache/list.go
+++ b/hw04_lru_cache/list.go
@@ -1,17 +1,101 @@
 package hw04_lru_cache //nolint:golint,stylecheck
 
-type List interface {
-	// Place your code here
+type ListInterface interface {
+	Len() int                          // длина списка
+	Front() *ListItem                  // первый Item
+	Back() *ListItem                   // последний Item
+	PushFront(v interface{}) *ListItem // добавить значение в начало
+	PushBack(v interface{}) *ListItem  // добавить значение в конец
+	Remove(i *ListItem)                // удалить элемент
+	MoveToFront(i *ListItem)           // переместить элемент в начало
 }
 
-type listItem struct {
-	// Place your code here
+type ListItem struct {
+	Value interface{} // значение
+	Next  *ListItem   // следующий элемент
+	Prev  *ListItem   // предыдущий элемент
 }
 
-type list struct {
-	// Place your code here
+type List struct {
+	Info ListItem
+	len  int
 }
 
-func NewList() List {
-	return &list{}
+func NewList() *List {
+	return &List{len: 0}
+}
+
+func (l *List) Len() int {
+	return l.len
+}
+
+func (l *List) Front() *ListItem {
+	return l.Info.Next
+}
+
+func (l *List) Back() *ListItem {
+	return l.Info.Prev
+}
+
+func (l *List) PushFront(v interface{}) *ListItem {
+	e := &ListItem{Value: v}
+	if l.len != 0 {
+		e.Prev = l.Info.Next
+		l.Info.Next.Next = e
+	} else {
+		l.Info.Prev = e
+	}
+	l.Info.Next = e
+	l.len++
+	return e
+}
+
+func (l *List) PushBack(v interface{}) *ListItem {
+	e := &ListItem{Value: v}
+	if l.len != 0 {
+		e.Next = l.Info.Prev
+		l.Info.Prev.Prev = e
+	} else {
+		l.Info.Next = e
+	}
+	l.Info.Prev = e
+	l.len++
+	return e
+}
+
+func (l *List) Remove(i *ListItem) {
+	if i.Prev == nil {
+		i.Prev = &ListItem{}
+		l.Info.Prev = i.Next
+	}
+	if i.Next == nil {
+		i.Next = &ListItem{}
+		l.Info.Next = i.Prev
+	}
+	i.Prev.Next = i.Next
+	i.Next.Prev = i.Prev
+	if l.Len() > 1 {
+		l.Info.Next.Next = nil
+		l.Info.Prev.Prev = nil
+	} else {
+		l.Info = ListItem{}
+	}
+	l.len--
+}
+
+func (l *List) MoveToFront(i *ListItem) {
+	if l.Info.Next == i {
+		return
+	}
+	if i.Prev != nil {
+		i.Prev.Next = i.Next
+		i.Next.Prev = i.Prev
+	} else {
+		i.Next.Prev = i.Prev
+		l.Info.Prev = i.Next
+	}
+	i.Prev = l.Front()
+	l.Front().Next = i
+	i.Next = nil
+	l.Info.Next = i
 }
diff --git a/hw04_lru_cache/list_test.go b/hw04_lru_cache/list_test.go
index 02109fd..a219aab 100644
--- a/hw04_lru_cache/list_test.go
+++ b/hw04_lru_cache/list_test.go
@@ -18,14 +18,50 @@ func TestList(t *testing.T) {
 	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, l.Len(), 3)
+		l.PushFront(10)
+		require.Equal(t, []int{10}, transpond(l))
+		require.Equal(t, 1, l.Len())
+
+		l.Remove(l.Front())
+		require.Equal(t, []int{}, transpond(l))
+		require.Equal(t, 0, l.Len())
+
+		l.PushFront(10)
+		require.Equal(t, []int{10}, transpond(l))
+		require.Equal(t, 1, l.Len())
+
+		l.PushBack(20)
+		require.Equal(t, []int{20, 10}, transpond(l))
+		require.Equal(t, 2, l.Len())
+
+		l.PushBack(30)
+		require.Equal(t, []int{30, 20, 10}, transpond(l))
+		require.Equal(t, 3, l.Len())
 
 		middle := l.Back().Next // 20
-		l.Remove(middle)        // [10, 30]
-		require.Equal(t, l.Len(), 2)
+		l.Remove(middle)
+		require.Equal(t, []int{30, 10}, transpond(l))
+		require.Equal(t, 2, l.Len())
+
+		l.PushFront(20)
+		require.Equal(t, []int{30, 10, 20}, transpond(l))
+		require.Equal(t, 3, l.Len())
+
+		l.Remove(l.Back())
+		require.Equal(t, []int{10, 20}, transpond(l))
+		require.Equal(t, 2, l.Len())
+
+		l.PushBack(30)
+		require.Equal(t, []int{30, 10, 20}, transpond(l))
+		require.Equal(t, 3, l.Len())
+
+		l.Remove(l.Front())
+		require.Equal(t, []int{30, 10}, transpond(l))
+		require.Equal(t, 2, l.Len())
+
+		l.PushFront(20)
+		require.Equal(t, []int{30, 10, 20}, transpond(l))
+		require.Equal(t, 3, l.Len())
 
 		for i, v := range [...]int{40, 50, 60, 70, 80} {
 			if i%2 == 0 {
@@ -33,19 +69,28 @@ func TestList(t *testing.T) {
 			} else {
 				l.PushBack(v)
 			}
-		} // [80, 60, 40, 10, 30, 50, 70]
-
-		require.Equal(t, l.Len(), 7)
-		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.Back(); i != nil; i = i.Next {
-			elems = append(elems, i.Value.(int))
 		}
-		require.Equal(t, []int{50, 30, 10, 40, 60, 80, 70}, elems)
+		require.Equal(t, []int{70, 50, 30, 10, 20, 40, 60, 80}, transpond(l))
+		require.Equal(t, 8, l.Len())
+
+		l.MoveToFront(l.Front())
+		require.Equal(t, []int{70, 50, 30, 10, 20, 40, 60, 80}, transpond(l))
+		require.Equal(t, 8, l.Len())
+
+		l.MoveToFront(l.Back().Next.Next) // 30
+		require.Equal(t, []int{70, 50, 10, 20, 40, 60, 80, 30}, transpond(l))
+		require.Equal(t, 8, l.Len())
+
+		l.MoveToFront(l.Back())
+		require.Equal(t, []int{50, 10, 20, 40, 60, 80, 30, 70}, transpond(l))
+		require.Equal(t, 8, l.Len())
 	})
 }
+
+func transpond(l *List) []int {
+	elems := make([]int, 0, l.Len())
+	for i := l.Back(); i != nil; i = i.Next {
+		elems = append(elems, i.Value.(int))
+	}
+	return elems
+}

From 11d4b357743479e0650a5efbaf5536c62f7b576d Mon Sep 17 00:00:00 2001
From: Andrey Ivanov <aivanov@cinimex.ru>
Date: Tue, 23 Jun 2020 14:45:11 +0300
Subject: [PATCH 2/4] HW4 is completed

---
 hw04_lru_cache/cache.go      | 26 +++++++++++++++++++-------
 hw04_lru_cache/cache_test.go |  7 +++----
 2 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go
index dd93cfd..1d8288e 100644
--- a/hw04_lru_cache/cache.go
+++ b/hw04_lru_cache/cache.go
@@ -1,4 +1,5 @@
 package hw04_lru_cache //nolint:golint,stylecheck
+import "sync"
 
 type Key string
 
@@ -12,6 +13,7 @@ type lruCache struct {
 	capacity int
 	queue    *List
 	items    map[Key]*ListItem
+	mx       sync.RWMutex
 }
 
 type Item struct {
@@ -23,25 +25,33 @@ func NewCache(capacity int) Cache {
 	return &lruCache{
 		capacity: capacity,
 		queue:    NewList(),
-		items:    map[Key]*ListItem{},
+		items:    make(map[Key]*ListItem),
 	}
 }
 
 func (l *lruCache) Set(key Key, value interface{}) bool {
-	if _, exists := l.Get(key); exists {
-		l.items[key].Value = value
+	if _, exists := l.items[key]; exists {
+		l.mx.RLock()
+		l.items[key].Value = Item{Value: value, Key: key}
+		l.queue.MoveToFront(l.items[key])
+		l.mx.RUnlock()
 		return exists
 	}
-	if l.queue.Len() >= l.capacity {
-		// TODO: нужно как-то удалить элемент из мапа
-		//delete(l.items,l.queue.Back().(*Item).Key)
+	if l.queue.Len() == l.capacity {
+		l.mx.RLock()
+		delete(l.items, l.queue.Back().Value.(Item).Key)
 		l.queue.Remove(l.queue.Back())
+		l.mx.RUnlock()
 	}
-	l.items[key] = l.queue.PushFront(value)
+	l.mx.RLock()
+	l.items[key] = l.queue.PushFront(Item{Value: value, Key: key})
+	l.mx.RUnlock()
 	return false
 }
 
 func (l *lruCache) Get(key Key) (interface{}, bool) {
+	l.mx.Lock()
+	defer l.mx.Unlock()
 	if l.items[key] == nil {
 		return nil, false
 	}
@@ -50,7 +60,9 @@ func (l *lruCache) Get(key Key) (interface{}, bool) {
 }
 
 func (l *lruCache) Clear() {
+	l.mx.Lock()
 	l.items = nil
 	l.queue.len = 0
 	l.queue.Info = ListItem{}
+	l.mx.Unlock()
 }
diff --git a/hw04_lru_cache/cache_test.go b/hw04_lru_cache/cache_test.go
index fd3ea5c..c16cf82 100644
--- a/hw04_lru_cache/cache_test.go
+++ b/hw04_lru_cache/cache_test.go
@@ -31,18 +31,18 @@ func TestCache(t *testing.T) {
 
 		val, ok := c.Get("aaa")
 		require.True(t, ok)
-		require.Equal(t, 100, val)
+		require.Equal(t, 100, val.(Item).Value)
 
 		val, ok = c.Get("bbb")
 		require.True(t, ok)
-		require.Equal(t, 200, val)
+		require.Equal(t, 200, val.(Item).Value)
 
 		wasInCache = c.Set("aaa", 300)
 		require.True(t, wasInCache)
 
 		val, ok = c.Get("aaa")
 		require.True(t, ok)
-		require.Equal(t, 300, val)
+		require.Equal(t, 300, val.(Item).Value)
 
 		val, ok = c.Get("ccc")
 		require.False(t, ok)
@@ -79,7 +79,6 @@ func TestCache(t *testing.T) {
 }
 
 func TestCacheMultithreading(t *testing.T) {
-	t.Skip() // Remove if task with asterisk completed
 
 	c := NewCache(10)
 	wg := &sync.WaitGroup{}

From 1db330e7123c08c9b7debcf05f4cc44cbb21db17 Mon Sep 17 00:00:00 2001
From: Andrey Ivanov <aivanov@cinimex.ru>
Date: Tue, 23 Jun 2020 15:02:08 +0300
Subject: [PATCH 3/4] HW4 is completed

---
 hw04_lru_cache/cache.go      | 2 +-
 hw04_lru_cache/cache_test.go | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go
index 1d8288e..e6929af 100644
--- a/hw04_lru_cache/cache.go
+++ b/hw04_lru_cache/cache.go
@@ -56,7 +56,7 @@ func (l *lruCache) Get(key Key) (interface{}, bool) {
 		return nil, false
 	}
 	l.queue.MoveToFront(l.items[key])
-	return l.items[key].Value, true
+	return l.items[key].Value.(Item).Value, true
 }
 
 func (l *lruCache) Clear() {
diff --git a/hw04_lru_cache/cache_test.go b/hw04_lru_cache/cache_test.go
index c16cf82..09ab0bd 100644
--- a/hw04_lru_cache/cache_test.go
+++ b/hw04_lru_cache/cache_test.go
@@ -31,18 +31,18 @@ func TestCache(t *testing.T) {
 
 		val, ok := c.Get("aaa")
 		require.True(t, ok)
-		require.Equal(t, 100, val.(Item).Value)
+		require.Equal(t, 100, val)
 
 		val, ok = c.Get("bbb")
 		require.True(t, ok)
-		require.Equal(t, 200, val.(Item).Value)
+		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.(Item).Value)
+		require.Equal(t, 300, val)
 
 		val, ok = c.Get("ccc")
 		require.False(t, ok)

From a161171e11b480bf7a1c7fa6261b23e25166c01f Mon Sep 17 00:00:00 2001
From: Andrey Ivanov <aivanov@cinimex.ru>
Date: Wed, 24 Jun 2020 08:29:44 +0300
Subject: [PATCH 4/4] HW4 is completed

---
 hw04_lru_cache/cache.go | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go
index e6929af..89ec238 100644
--- a/hw04_lru_cache/cache.go
+++ b/hw04_lru_cache/cache.go
@@ -1,5 +1,8 @@
 package hw04_lru_cache //nolint:golint,stylecheck
-import "sync"
+import (
+	"log"
+	"sync"
+)
 
 type Key string
 
@@ -39,7 +42,11 @@ func (l *lruCache) Set(key Key, value interface{}) bool {
 	}
 	if l.queue.Len() == l.capacity {
 		l.mx.RLock()
-		delete(l.items, l.queue.Back().Value.(Item).Key)
+		k, ok := l.queue.Back().Value.(Item)
+		if !ok {
+			log.Fatal("Ошибка приведения типов")
+		}
+		delete(l.items, k.Key)
 		l.queue.Remove(l.queue.Back())
 		l.mx.RUnlock()
 	}
@@ -56,7 +63,11 @@ func (l *lruCache) Get(key Key) (interface{}, bool) {
 		return nil, false
 	}
 	l.queue.MoveToFront(l.items[key])
-	return l.items[key].Value.(Item).Value, true
+	s, ok := l.items[key].Value.(Item)
+	if !ok {
+		log.Fatal("Ошибка приведения типов")
+	}
+	return s.Value, true
 }
 
 func (l *lruCache) Clear() {