Skip to content

Commit

Permalink
hw04_lru_cache
Browse files Browse the repository at this point in the history
  • Loading branch information
timersha committed Jan 5, 2025
1 parent da3bb9d commit 5405173
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 15 deletions.
Empty file removed hw04_lru_cache/.sync
Empty file.
56 changes: 54 additions & 2 deletions hw04_lru_cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package hw04lrucache

import (
"sync"
)

type Key string

type Cache interface {
Expand All @@ -9,17 +13,65 @@ type Cache interface {
}

type lruCache struct {
Cache // Remove me after realization.

mx sync.RWMutex
capacity int
queue List
items map[Key]*ListItem
}

type CacheItem struct {
k Key
v interface{}
}

func NewCache(capacity int) Cache {
return &lruCache{
capacity: capacity,
queue: NewList(),
items: make(map[Key]*ListItem, capacity),
}
}

func (lruC *lruCache) cleanOutdatedElements() {
if lruC.capacity < lruC.queue.Len() {
delete(lruC.items, lruC.queue.Back().Value.(*CacheItem).k)
lruC.queue.Remove(lruC.queue.Back())
}
}

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

v, ok := lruC.items[key]
if ok {
v.Value = &CacheItem{key, value}
lruC.queue.MoveToFront(v)
} else {
v = lruC.queue.PushFront(value)
v.Value = &CacheItem{key, value}
lruC.items[key] = v
lruC.cleanOutdatedElements()
}
return ok
}

func (lruC *lruCache) Get(key Key) (interface{}, bool) {
lruC.mx.RLock()
defer lruC.mx.RUnlock()

v, ok := lruC.items[key]
if ok {
lruC.queue.MoveToFront(v)
return (v.Value).(*CacheItem).v, true
}
return nil, false
}

func (lruC *lruCache) Clear() {
l := lruC.queue.Len()
for range l {
delete(lruC.items, lruC.queue.Front().Value.(*CacheItem).k)
lruC.queue.Remove(lruC.queue.Front())
}
}
80 changes: 74 additions & 6 deletions hw04_lru_cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package hw04lrucache

import (
"math/rand"
"strconv"
"sync"
"testing"

"github.com/stretchr/testify/require"

Check failure on line 8 in hw04_lru_cache/cache_test.go

View workflow job for this annotation

GitHub Actions / lint

import 'github.com/stretchr/testify/require' is not allowed from list 'Main' (depguard)
"golang.org/x/exp/rand"
)

func TestCache(t *testing.T) {
t.Run("empty cache", func(t *testing.T) {
c := NewCache(10)

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

_, ok := c.Get("aaa")
require.False(t, ok)
require.True(t, ok)

_, ok = c.Get("bbb")
require.False(t, ok)
Expand Down Expand Up @@ -49,14 +52,79 @@ func TestCache(t *testing.T) {
require.Nil(t, val)
})

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

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("aaa")
require.False(t, ok)

_, ok = c.Get("bbb")
require.True(t, ok)

_, ok = c.Get("ccc")
require.True(t, ok)
})

t.Run("purge logic 2", func(t *testing.T) {
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)

wasInCache = c.Set("aaa", 200)
require.True(t, wasInCache)

wasInCache = c.Set("bbb", 300)
require.True(t, wasInCache)

wasInCache = c.Set("ccc", 400)
require.True(t, wasInCache)

wasInCache = c.Set("ddd", 500)
require.False(t, wasInCache)

_, ok := c.Get("aaa")
require.False(t, ok)
})

t.Run("clear cache logic", func(t *testing.T) {
// Arrange
c := NewCache(3)
_ = c.Set("aaa", 100)
_ = c.Set("bbb", 200)
_ = c.Set("ccc", 300)

// Act
c.Clear()

// Assert
_, ok := c.Get("aaa")
require.False(t, ok)

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

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

func TestCacheMultithreading(t *testing.T) {

Check warning on line 127 in hw04_lru_cache/cache_test.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)
t.Skip() // Remove me if task with asterisk completed.

c := NewCache(10)
wg := &sync.WaitGroup{}
wg.Add(2)
Expand Down
11 changes: 8 additions & 3 deletions hw04_lru_cache/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
module github.com/fixme_my_friend/hw04_lru_cache
module github.com/timersha/golang-tests/hw04_lru_cache

go 1.22
go 1.22.0

require github.com/stretchr/testify v1.7.0
toolchain go1.22.10

require (
github.com/stretchr/testify v1.7.0
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions hw04_lru_cache/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
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=
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588=
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
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=
Expand Down
106 changes: 102 additions & 4 deletions hw04_lru_cache/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,113 @@ type List interface {

type ListItem struct {
Value interface{}
Next *ListItem
Prev *ListItem
Next *ListItem // право
Prev *ListItem // лево
}

type list struct {
List // Remove me after realization.
// Place your code here.
count int
front *ListItem
back *ListItem
}

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

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

func (l *list) Front() *ListItem {
return l.front
}

func (l *list) Back() *ListItem {
return l.back
}

func (l *list) PushFront(v interface{}) *ListItem {
newFront := &ListItem{
Value: v,
Next: l.front,
Prev: nil,
}
if l.front == nil && l.back == nil {
l.front = newFront
l.back = newFront
} else {
l.front.Prev = newFront
l.front = newFront
}
l.count++
return l.front
}

func (l *list) PushBack(v interface{}) *ListItem {
newBack := &ListItem{
Value: v,
Next: nil,
Prev: l.back,
}
if l.front == nil && l.back == nil {
l.front = newBack
l.back = newBack
} else {
l.back.Next = newBack
l.back = newBack
}
l.count++
return l.back
}

func (l *list) Remove(i *ListItem) {
if l.front == i {
newFront := l.front.Next
l.front = newFront
l.count--
return
}

if l.back == i {
newBack := l.back.Prev
l.back = newBack
l.count--
return
}

i.Prev.Next = i.Next
i.Next.Prev = i.Prev
l.count--
}

func (l *list) MoveToFront(i *ListItem) {
if l.front == nil && l.back == nil {
l.front = i
l.back = i
l.count++
return
}

if l.front == i {
return
}

if l.back == i {
i.Prev.Next = nil
l.back = i.Prev
i.Prev = nil

l.front.Prev = i
i.Next = l.front
l.front = i
return
}

i.Prev.Next = i.Next
i.Next.Prev = i.Prev

l.front.Prev = i
i.Next = l.front
l.front = i
}
45 changes: 45 additions & 0 deletions hw04_lru_cache/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,51 @@ func TestList(t *testing.T) {
require.Nil(t, l.Back())
})

t.Run("front and back are same and move to front", func(t *testing.T) {
l := NewList()

item := l.PushBack(10)
l.MoveToFront(item)
require.Equal(t, 1, l.Len())
})

t.Run("remove the only one front", func(t *testing.T) {
l := NewList()

item := l.PushFront(10)
l.Remove(item)
require.Equal(t, 0, l.Len())
})

t.Run("remove the only one back", func(t *testing.T) {
l := NewList()

item := l.PushBack(10)
l.Remove(item)
require.Equal(t, 0, l.Len())
})

t.Run("front and back are nil and move to front", func(t *testing.T) {
l := NewList()

item := &ListItem{9, nil, nil}
l.MoveToFront(item)
require.Equal(t, 1, l.Len())
})

t.Run("move to front from middle", func(t *testing.T) {
l := NewList()

l.PushBack(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.MoveToFront(middle)
require.Equal(t, middle, l.Front())
})

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

Expand Down

0 comments on commit 5405173

Please sign in to comment.