first commit
This commit is contained in:
commit
57d1dee00b
7 files changed
+465
No files matched your search
@@ -0,0 +1,4 @@
|
||||
## Question
|
||||
|
||||
Implement a *Least Recently Used* (lru) cache
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package lrucache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
type Creator func(key string) interface{}
|
||||
|
||||
type cacheKey struct {
|
||||
Value interface{}
|
||||
DoubleLinkedListNode *list.Element
|
||||
}
|
||||
|
||||
type LRUCache struct {
|
||||
cache map[string]*cacheKey
|
||||
ddl *list.List
|
||||
creator Creator
|
||||
length int
|
||||
}
|
||||
|
||||
func New(creator Creator, length int) *LRUCache {
|
||||
return &LRUCache{
|
||||
cache: make(map[string]*cacheKey),
|
||||
creator: creator,
|
||||
ddl: new(list.List).Init(),
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
func (lru *LRUCache) add(key string, val interface{}) interface{} {
|
||||
lru.ddl.PushFront(key)
|
||||
lru.cache[key] = &cacheKey{val, lru.ddl.Front()}
|
||||
return val
|
||||
}
|
||||
|
||||
func (lru *LRUCache) Size() int {
|
||||
return len(lru.cache)
|
||||
}
|
||||
|
||||
func (lru *LRUCache) removeExtraKeys() {
|
||||
if lru.ddl.Len() < lru.length {
|
||||
return
|
||||
}
|
||||
for e := lru.ddl.Back(); lru.ddl.Len() > lru.length; {
|
||||
delete(lru.cache, e.Value.(string))
|
||||
be := e.Prev()
|
||||
lru.ddl.Remove(e)
|
||||
e = be
|
||||
}
|
||||
}
|
||||
|
||||
func (lru *LRUCache) Get(key string) interface{} {
|
||||
if val, ok := lru.cache[key]; ok {
|
||||
lru.ddl.MoveToFront(val.DoubleLinkedListNode)
|
||||
return val.Value
|
||||
}
|
||||
ret := lru.add(key, lru.creator(key))
|
||||
lru.removeExtraKeys()
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Example of an lru implementation using double linked lists (not thread safe)
|
||||
package lrucache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mockCreator(key string) interface{} {
|
||||
return key
|
||||
}
|
||||
|
||||
func doubleLinkedListContains(l *list.List, elm string) bool {
|
||||
for node := l.Front(); node != nil; node = node.Next() {
|
||||
if node.Value.(string) == elm {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func toString(a interface{}) string {
|
||||
if i, ok := a.(string); ok {
|
||||
return i
|
||||
}
|
||||
panic("cannot convert in string")
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
lru := New(mockCreator, 10)
|
||||
if lru.add("key", "val") != "val" {
|
||||
t.Error("must returns the value passed")
|
||||
}
|
||||
|
||||
if toString(lru.ddl.Front().Value) != "key" {
|
||||
t.Error("key must be ther first item after adding it")
|
||||
}
|
||||
if val, ok := lru.cache["key"]; !ok {
|
||||
t.Error("key not in cache")
|
||||
} else if toString(val.Value) != "val" {
|
||||
t.Error("bad value associated to key")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRemoveExtraKeys(t *testing.T) {
|
||||
lru := New(mockCreator, 4)
|
||||
for i := 0; i < 10; i++ {
|
||||
lru.add(strconv.Itoa(i), strconv.Itoa(i))
|
||||
}
|
||||
if lru.Size() != 10 {
|
||||
t.Error("bad initial length")
|
||||
}
|
||||
lru.removeExtraKeys()
|
||||
if lru.Size() != 4 {
|
||||
t.Error("bad final length. should be 4 but have ", lru.Size())
|
||||
}
|
||||
if lru.ddl.Len() != 4 {
|
||||
t.Error("double linked list has incorrect length")
|
||||
}
|
||||
}
|
||||
|
||||
func checkLRUCacheState(lru *LRUCache, expected ...string) error {
|
||||
expectedSize := len(expected)
|
||||
if len(lru.cache) != expectedSize {
|
||||
return errors.New("cache of wrong size")
|
||||
}
|
||||
|
||||
if lru.ddl.Len() != expectedSize {
|
||||
return errors.New("double linked list of wrong size")
|
||||
}
|
||||
for _, e := range expected {
|
||||
if _, ok := lru.cache[e]; !ok {
|
||||
return errors.New("key " + e + " not in cache")
|
||||
}
|
||||
if !doubleLinkedListContains(lru.ddl, e) {
|
||||
return errors.New("key " + e + " not in double linked list")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
lru := New(mockCreator, 2)
|
||||
if v := lru.Get("0"); v != "0" {
|
||||
t.Error()
|
||||
}
|
||||
if err := checkLRUCacheState(lru, "0"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
lru.Get("1")
|
||||
if err := checkLRUCacheState(lru, "1", "0"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
lru.Get("2")
|
||||
if err := checkLRUCacheState(lru, "2", "1"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
lru.Get("1")
|
||||
if err := checkLRUCacheState(lru, "1", "2"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
lru.Get("3")
|
||||
if err := checkLRUCacheState(lru, "3", "1"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user