package lru import ( "container/list" "testing" ) // Cache is a fixed-capacity least-recently-used cache. type Cache[K comparable, V any] struct { capacity int items map[K]*list.Element order *list.List } type entry[K comparable, V any] struct { key K value V } // New creates an LRU cache that can hold up to capacity items. func New[K comparable, V any](capacity int) *Cache[K, V] { if capacity < 0 { capacity = 0 } return &Cache[K, V]{ capacity: capacity, items: make(map[K]*list.Element), order: list.New(), } } // Len returns the current number of cached items. func (c *Cache[K, V]) Len() int { return len(c.items) } // Get returns a cached value and marks it as recently used. func (c *Cache[K, V]) Get(key K) (V, bool) { if elem, ok := c.items[key]; ok { c.order.MoveToFront(elem) return elem.Value.(entry[K, V]).value, true } var zero V return zero, false } // Put inserts or updates a value and evicts the least-recently-used item if full. func (c *Cache[K, V]) Put(key K, value V) { if c.capacity == 0 { return } if elem, ok := c.items[key]; ok { elem.Value = entry[K, V]{key: key, value: value} c.order.MoveToFront(elem) return } elem := c.order.PushFront(entry[K, V]{key: key, value: value}) c.items[key] = elem if len(c.items) > c.capacity { c.removeOldest() } } // Delete removes a key if present. func (c *Cache[K, V]) Delete(key K) bool { elem, ok := c.items[key] if !ok { return false } c.order.Remove(elem) delete(c.items, key) return true } func (c *Cache[K, V]) removeOldest() { elem := c.order.Back() if elem == nil { return } c.order.Remove(elem) delete(c.items, elem.Value.(entry[K, V]).key) } func TestCacheEvictsLeastRecentlyUsed(t *testing.T) { cache := New[string, int](2) cache.Put("a", 1) cache.Put("b", 2) cache.Put("c", 3) if _, ok := cache.Get("a"); ok { t.Fatal("expected a to be evicted") } if got, ok := cache.Get("b"); !ok || got != 2 { t.Fatalf("expected b=2, got %v, ok=%v", got, ok) } if got, ok := cache.Get("c"); !ok || got != 3 { t.Fatalf("expected c=3, got %v, ok=%v", got, ok) } } func TestGetRefreshesRecency(t *testing.T) { cache := New[string, int](2) cache.Put("a", 1) cache.Put("b", 2) if _, ok := cache.Get("a"); !ok { t.Fatal("expected a to exist") } cache.Put("c", 3) if _, ok := cache.Get("b"); ok { t.Fatal("expected b to be evicted") } if got, ok := cache.Get("a"); !ok || got != 1 { t.Fatalf("expected a=1, got %v, ok=%v", got, ok) } }