From 39c576248c7479e42499613723dc4fdc9d82bad4 Mon Sep 17 00:00:00 2001 From: dudaodong Date: Thu, 29 Dec 2022 11:43:14 +0800 Subject: [PATCH] test: add examples for lrucache --- algorithm/lrucache.go | 121 +++++++++++++++++++++++++++++ algorithm/lrucache_example_test.go | 79 +++++++++++++++++++ algorithm/lrucache_test.go | 33 ++++++++ 3 files changed, 233 insertions(+) create mode 100644 algorithm/lrucache.go create mode 100644 algorithm/lrucache_example_test.go create mode 100644 algorithm/lrucache_test.go diff --git a/algorithm/lrucache.go b/algorithm/lrucache.go new file mode 100644 index 0000000..c1c737d --- /dev/null +++ b/algorithm/lrucache.go @@ -0,0 +1,121 @@ +package algorithm + +type lruNode[K comparable, V any] struct { + key K + value V + pre *lruNode[K, V] + next *lruNode[K, V] +} + +// newLruNode return a lruNode pointer +func newLruNode[K comparable, V any](key K, value V) *lruNode[K, V] { + return &lruNode[K, V]{ + key: key, + value: value, + pre: nil, + next: nil, + } +} + +// LRUCache lru cache (thread unsafe) +type LRUCache[K comparable, V any] struct { + cache map[K]*lruNode[K, V] + head *lruNode[K, V] + tail *lruNode[K, V] + capacity int + length int +} + +// NewLRUCache creates a LRUCache pointer instance. +func NewLRUCache[K comparable, V any](capacity int) *LRUCache[K, V] { + return &LRUCache[K, V]{ + cache: make(map[K]*lruNode[K, V], capacity), + head: nil, + tail: nil, + capacity: capacity, + length: 0, + } +} + +// Get value of key from lru cache. +// Play: https://go.dev/play/p/iUynEfOP8G0 +func (l *LRUCache[K, V]) Get(key K) (V, bool) { + var value V + + node, ok := l.cache[key] + if ok { + l.moveToHead(node) + return node.value, true + } + + return value, false +} + +// Put value of key into lru cache. +// Play: https://go.dev/play/p/iUynEfOP8G0 +func (l *LRUCache[K, V]) Put(key K, value V) { + node, ok := l.cache[key] + if !ok { + newNode := newLruNode(key, value) + l.cache[key] = newNode + l.addNode(newNode) + + if len(l.cache) > l.capacity { + oldKey := l.deleteNode(l.head) + delete(l.cache, oldKey) + } + } else { + node.value = value + l.moveToHead(node) + } + l.length = len(l.cache) +} + +// Delete item from lru cache. +func (l *LRUCache[K, V]) Delete(key K) bool { + node, ok := l.cache[key] + if ok { + key := l.deleteNode(node) + delete(l.cache, key) + return true + } + + return false +} + +// Len returns the number of items in the cache. +func (l *LRUCache[K, V]) Len() int { + return l.length +} + +func (l *LRUCache[K, V]) addNode(node *lruNode[K, V]) { + if l.tail != nil { + l.tail.next = node + node.pre = l.tail + node.next = nil + } + l.tail = node + if l.head == nil { + l.head = node + } +} + +func (l *LRUCache[K, V]) deleteNode(node *lruNode[K, V]) K { + if node == l.tail { + l.tail = l.tail.pre + } else if node == l.head { + l.head = l.head.next + } else { + node.pre.next = node.next + node.next.pre = node.pre + } + return node.key +} + +func (l *LRUCache[K, V]) moveToHead(node *lruNode[K, V]) { + if l.tail == node { + return + } + l.deleteNode(node) + l.addNode(node) +} diff --git a/algorithm/lrucache_example_test.go b/algorithm/lrucache_example_test.go new file mode 100644 index 0000000..b792b74 --- /dev/null +++ b/algorithm/lrucache_example_test.go @@ -0,0 +1,79 @@ +package algorithm + +import "fmt" + +func ExampleLRUCache_Put() { + cache := NewLRUCache[int, int](2) + + cache.Put(1, 1) + cache.Put(2, 2) + + result1, ok1 := cache.Get(1) + result2, ok2 := cache.Get(2) + result3, ok3 := cache.Get(3) + + fmt.Println(result1, ok1) + fmt.Println(result2, ok2) + fmt.Println(result3, ok3) + + // Output: + // 1 true + // 2 true + // 0 false +} + +func ExampleLRUCache_Get() { + cache := NewLRUCache[int, int](2) + + cache.Put(1, 1) + cache.Put(2, 2) + + result1, ok1 := cache.Get(1) + result2, ok2 := cache.Get(2) + result3, ok3 := cache.Get(3) + + fmt.Println(result1, ok1) + fmt.Println(result2, ok2) + fmt.Println(result3, ok3) + + // Output: + // 1 true + // 2 true + // 0 false +} + +func ExampleLRUCache_Delete() { + cache := NewLRUCache[int, int](2) + + cache.Put(1, 1) + cache.Put(2, 2) + + result1, ok1 := cache.Get(1) + + ok2 := cache.Delete(2) + + _, ok3 := cache.Get(2) + + fmt.Println(result1, ok1) + fmt.Println(ok2) + fmt.Println(ok3) + + // Output: + // 1 true + // true + // false +} + +func ExampleLRUCache_Len() { + cache := NewLRUCache[int, int](2) + + cache.Put(1, 1) + cache.Put(2, 2) + + result := cache.Len() + + fmt.Println(result) + + // Output: + // 2 +} diff --git a/algorithm/lrucache_test.go b/algorithm/lrucache_test.go new file mode 100644 index 0000000..7d62b68 --- /dev/null +++ b/algorithm/lrucache_test.go @@ -0,0 +1,33 @@ +package algorithm + +import ( + "testing" + + "github.com/duke-git/lancet/v2/internal" +) + +func TestLRUCache(t *testing.T) { + asssert := internal.NewAssert(t, "TestLRUCache") + + cache := NewLRUCache[int, int](3) + + cache.Put(1, 1) + cache.Put(2, 2) + cache.Put(3, 3) + + asssert.Equal(3, cache.Len()) + + v, ok := cache.Get(1) + asssert.Equal(true, ok) + asssert.Equal(1, v) + + v, ok = cache.Get(2) + asssert.Equal(true, ok) + asssert.Equal(2, v) + + ok = cache.Delete(2) + asssert.Equal(true, ok) + + _, ok = cache.Get(2) + asssert.Equal(false, ok) +}