Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions hw04_lru_cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,82 @@
package hw04_lru_cache //nolint:golint,stylecheck

import (
"sync"
)

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
sync.Mutex
capacity int
Queue List
Items map[Key]*listItem
}

type cacheItem struct {
// Place your code here
cKey Key
cValue interface{}
}

func NewCache(capacity int) Cache {
return &lruCache{}
return &lruCache{
capacity: capacity,
Queue: NewList(),
Items: make(map[Key]*listItem),
}
}

func newItem(key Key, value interface{}) *cacheItem {
return &cacheItem{
cKey: key,
cValue: value,
}
}

func (c *lruCache) Set(key Key, value interface{}) bool {
c.Lock()
defer c.Unlock()

if item, ok := c.Items[key]; ok {
item.Value.(*cacheItem).cValue = value
c.Queue.MoveToFront(item)
return true
}

for c.Queue.Len() >= c.capacity {
latestItem := c.Queue.Back()

c.Queue.Remove(latestItem)
delete(c.Items, latestItem.Value.(*cacheItem).cKey)
}

item := newItem(key, value)
c.Items[key] = c.Queue.PushFront(item)
return false
}

func (c *lruCache) Get(key Key) (interface{}, bool) {
c.Lock()
defer c.Unlock()

if item, ok := c.Items[key]; ok {
c.Queue.MoveToFront(item)
return item.Value.(*cacheItem).cValue, true
}

return nil, false
}

func (c *lruCache) Clear() {
c.Lock()
defer c.Unlock()

c.Queue = NewList()
c.Items = make(map[Key]*listItem)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему для map тут создается новый экзепляр, а для списка удаляются элементы? Чем обусловлен разный подход к чистке?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, неоднозначно получилось, поправил во втором коммите.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GC корректно отловит существующий список ни к чему не привязанный?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, раз ссылок на объект не осталось, то GC его приберет.

}
72 changes: 68 additions & 4 deletions hw04_lru_cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,73 @@ func TestCache(t *testing.T) {
})

t.Run("purge logic", func(t *testing.T) {
// Write me
c := NewCache(1)

wasInCache := c.Set("aaa", 100)
require.False(t, wasInCache)

wasInCache = c.Set("bbb", 200)
require.False(t, wasInCache)

val, ok := c.Get("aaa")
require.False(t, ok)
require.Equal(t, nil, val)

val, ok = c.Get("bbb")
require.True(t, ok)
require.Equal(t, 200, val)
})

t.Run("push logic", func(t *testing.T) {
c := NewCache(3)

c.Set("aaa", 100)
c.Set("bbb", 200)
c.Set("ccc", 300)

// make aaa and bbb more frequent
c.Get("aaa")
c.Get("bbb")

c.Set("ddd", 400)

// check purged value
val, ok := c.Get("ccc")
require.False(t, ok)
require.Equal(t, nil, val)

// check exist values
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)

val, ok = c.Get("ddd")
require.True(t, ok)
require.Equal(t, 400, val)
})

t.Run("clear", func(t *testing.T) {
c := NewCache(1)

c.Set("aaa", 100)
c.Set("bbb", 200)
_, ok := c.Get("aaa")
require.False(t, ok)

c.Clear()
_, ok = c.Get("bbb")
require.False(t, ok)
})
}

func TestCacheMultithreading(t *testing.T) {
t.Skip() // Remove if task with asterisk completed

c := NewCache(10)
wg := &sync.WaitGroup{}
wg.Add(2)
wg.Add(3)

go func() {
defer wg.Done()
Expand All @@ -75,5 +132,12 @@ func TestCacheMultithreading(t *testing.T) {
}
}()

go func() {
defer wg.Done()
for i := 0; i < 1_000_000; i++ {
c.Clear()
}
}()

wg.Wait()
}
2 changes: 1 addition & 1 deletion hw04_lru_cache/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/fixme_my_friend/hw04_lru_cache
module github.com/ezhk/golang-learning/hw04_lru_cache

go 1.14

Expand Down
94 changes: 91 additions & 3 deletions hw04_lru_cache/list.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,105 @@
package hw04_lru_cache //nolint:golint,stylecheck

type List interface {
// Place your code here
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
Value interface{} // значение
Next *listItem // следующий элемент
Prev *listItem // предыдущий элемент
}

type list struct {
// Place your code here
// Length stay for back compatibility when we cannot use map
Length int

First *listItem
Last *listItem
}

func NewList() List {
return &list{}
}

func (l *list) Len() int {
return l.Length
}

func (l *list) Front() *listItem {
return l.First
}

func (l *list) Back() *listItem {
return l.Last
}

func (l *list) PushFront(v interface{}) *listItem {
switch l.Len() {
case 0:
l.First = &listItem{Value: v}
l.Last = l.First
default:
value := &listItem{
Value: v,
Next: l.First,
}
l.First.Prev = value
l.First = value
}

l.Length++
return l.First
}

func (l *list) PushBack(v interface{}) *listItem {
switch l.Len() {
case 0:
l.Last = &listItem{Value: v}
l.First = l.Last
default:
value := &listItem{
Value: v,
Prev: l.Last,
}
l.Last.Next = value
l.Last = value
}

l.Length++
return l.Last
}

func (l *list) Remove(i *listItem) {
switch {
// element in the middle
case i.Prev != nil && i.Next != nil:
i.Prev.Next = i.Next
i.Next.Prev = i.Prev
// element in the right/end
case i.Prev != nil:
l.Last = i.Prev
i.Prev.Next = nil
// element in the left/begin
case i.Next != nil:
l.First = i.Next
i.Next.Prev = nil
// stay only one element
default:
l.First = nil
l.Last = nil
}

l.Length--
}

func (l *list) MoveToFront(i *listItem) {
l.Remove(i)
l.PushFront(i.Value)
}
39 changes: 39 additions & 0 deletions hw04_lru_cache/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,45 @@ func TestList(t *testing.T) {
require.Nil(t, l.Back())
})

t.Run("zero or one element", func(t *testing.T) {
l := NewList()

l.PushFront(10)
require.Equal(t, l.Len(), 1)
// validate internal struct: when last and first are equals
require.Equal(t, l.Front(), l.Back())

elems := make([]int, 0, l.Len())
for i := l.Front(); i != nil; i = i.Next {
elems = append(elems, i.Value.(int))
}
require.Equal(t, elems, []int{10})

l.Remove(l.Front())
require.Equal(t, l.Len(), 0)
require.Equal(t, l.Front(), (*listItem)(nil))
require.Equal(t, l.Back(), (*listItem)(nil))
})

t.Run("two elements", func(t *testing.T) {
l := NewList()
l.PushFront("begin")
l.PushBack("end")
require.Equal(t, l.Len(), 2)

elems := make([]string, 0, l.Len())
for i := l.Front(); i != nil; i = i.Next {
elems = append(elems, i.Value.(string))
}
require.Equal(t, elems, []string{"begin", "end"})

l.Remove(l.Front())
require.Equal(t, l.Len(), 1)

// validate last value
require.Equal(t, l.Back().Value.(string), "end")
})

t.Run("complex", func(t *testing.T) {
l := NewList()

Expand Down