Skip to content

Commit

Permalink
Adding LRUCache type (#12)
Browse files Browse the repository at this point in the history
* Stashing progress

* Implemented LRUCache
  • Loading branch information
marstr authored Sep 16, 2020
1 parent 45c3223 commit 22cb427
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 0 deletions.
4 changes: 4 additions & 0 deletions linkedlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ func (list *LinkedList) Swap(x, y uint) error {
return nil
}

func (list *LinkedList) moveToFront(node *llNode) {

}

// ToSlice converts the contents of the LinkedList into a slice.
func (list *LinkedList) ToSlice() []interface{} {
return list.Enumerate(nil).ToSlice()
Expand Down
133 changes: 133 additions & 0 deletions lru_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package collection

import "sync"

// LRUCache hosts up to a given number of items. When more are presented, the least recently used item
// is evicted from the cache.
type LRUCache struct {
capacity uint
entries map[interface{}]lruEntry
touched *LinkedList
key sync.RWMutex
}

type lruEntry struct {
Node *llNode
Key interface{}
Value interface{}
}

// NewLRUCache creates an empty cache, which will accommodate the given number of items.
func NewLRUCache(capacity uint) *LRUCache {
return &LRUCache{
capacity: capacity,
entries: make(map[interface{}]lruEntry, capacity + 1),
touched: NewLinkedList(),
}
}

// Put adds a value to the cache. The added value may be expelled without warning.
func (lru *LRUCache) Put(key interface{}, value interface{}) {
lru.key.Lock()
defer lru.key.Unlock()

entry, ok := lru.entries[key]
if ok {
lru.touched.removeNode(entry.Node)
} else {
entry = lruEntry{
Node: &llNode{},
Key: key,
Value: value,
}
}

entry.Node.payload = entry
lru.touched.addNodeFront(entry.Node)
lru.entries[key] = entry

if lru.touched.Length() > lru.capacity {
removed, ok := lru.touched.RemoveBack()
if ok {
delete(lru.entries, removed.(lruEntry).Key)
}
}
}

// Get retrieves a cached value, if it is still present.
func (lru *LRUCache) Get(key interface{}) (interface{}, bool) {
lru.key.RLock()
defer lru.key.RUnlock()

entry, ok := lru.entries[key]
if !ok {
return nil, false
}

lru.touched.removeNode(entry.Node)
lru.touched.addNodeFront(entry.Node)
return entry.Node.payload.(lruEntry).Value, true
}

// Remove explicitly takes an item out of the cache.
func (lru *LRUCache) Remove(key interface{}) bool {
lru.key.RLock()
defer lru.key.RUnlock()

entry, ok := lru.entries[key]
if !ok {
return false
}

lru.touched.removeNode(entry.Node)
delete(lru.entries, key)
return true
}

// Enumerate lists each value in the cache.
func (lru *LRUCache) Enumerate(cancel <-chan struct{}) Enumerator {
retval := make(chan interface{})

nested := lru.touched.Enumerate(cancel)

go func() {
lru.key.RLock()
defer lru.key.RUnlock()
defer close(retval)

for entry := range nested {
select {
case retval <- entry.(lruEntry).Value:
break
case <-cancel:
return
}
}
}()

return retval
}

// EnumerateKeys lists each key in the cache.
func (lru *LRUCache) EnumerateKeys(cancel <-chan struct{}) Enumerator {
retval := make(chan interface{})

nested := lru.touched.Enumerate(cancel)

go func() {
lru.key.RLock()
defer lru.key.RUnlock()
defer close(retval)

for entry := range nested {
select {
case retval <- entry.(lruEntry).Key:
break
case <-cancel:
return
}
}
}()

return retval
}
42 changes: 42 additions & 0 deletions lru_cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package collection

import "testing"

func TestLRUCache_Remove_empty(t *testing.T) {
subject := NewLRUCache(10)
got := subject.Remove(7)
if got != false {
t.Fail()
}
}

func TestLRUCache_Remove_present(t *testing.T) {
const key = 10
subject := NewLRUCache(6)
subject.Put(key, "ten")
ok := subject.Remove(key)
if !ok {
t.Fail()
}

_, ok = subject.Get(key)
if ok {
t.Fail()
}
}

func TestLRUCache_Remove_notPresent(t *testing.T) {
const key1 = 10
const key2 = key1 + 1
subject := NewLRUCache(6)
subject.Put(key2, "eleven")
ok := subject.Remove(key1)
if ok {
t.Fail()
}

_, ok = subject.Get(key2)
if !ok {
t.Fail()
}
}
56 changes: 56 additions & 0 deletions lru_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package collection_test

import (
"context"
"fmt"
"github.com/marstr/collection"
)

func ExampleLRUCache() {
subject := collection.NewLRUCache(3)
subject.Put(1, "one")
subject.Put(2, "two")
subject.Put(3, "three")
subject.Put(4, "four")
fmt.Println(subject.Get(1))
fmt.Println(subject.Get(4))
// Output:
// <nil> false
// four true
}

func ExampleLRUCache_Enumerate() {
ctx := context.Background()
subject := collection.NewLRUCache(3)
subject.Put(1, "one")
subject.Put(2, "two")
subject.Put(3, "three")
subject.Put(4, "four")

for key := range subject.Enumerate(ctx.Done()) {
fmt.Println(key)
}

// Output:
// four
// three
// two
}

func ExampleLRUCache_EnumerateKeys() {
ctx := context.Background()
subject := collection.NewLRUCache(3)
subject.Put(1, "one")
subject.Put(2, "two")
subject.Put(3, "three")
subject.Put(4, "four")

for key := range subject.EnumerateKeys(ctx.Done()) {
fmt.Println(key)
}

// Output:
// 4
// 3
// 2
}

0 comments on commit 22cb427

Please sign in to comment.