-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Stashing progress * Implemented LRUCache
- Loading branch information
Showing
4 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |