@ -4,10 +4,6 @@
// Package lru contains a typed Least-Recently-Used cache.
// Package lru contains a typed Least-Recently-Used cache.
package lru
package lru
import (
"container/list"
)
// Cache is container type keyed by K, storing V, optionally evicting the least
// Cache is container type keyed by K, storing V, optionally evicting the least
// recently used items if a maximum size is exceeded.
// recently used items if a maximum size is exceeded.
//
//
@ -22,14 +18,27 @@ type Cache[K comparable, V any] struct {
// an item is evicted. Zero means no limit.
// an item is evicted. Zero means no limit.
MaxEntries int
MaxEntries int
ll * list . List
// head is a ring of LRU values. head points to the most recently
m map [ K ] * list . Element // of *entry[K,V]
// used element, head.prev is the least recently used.
//
// An LRU is technically a simple list rather than a ring, but
// implementing it as a ring makes the list manipulation
// operations more regular, because the first/last positions in
// the list stop being special.
//
// head is nil when the LRU is empty.
head * entry [ K , V ]
// lookup is a map of all the LRU entries contained in
// head. lookup and head always contain exactly the same elements;
// lookup is just there to allow O(1) lookups of keys.
lookup map [ K ] * entry [ K , V ]
}
}
// entry is the element type for the container/list.Element.
// entry is an entry of Cache .
type entry [ K comparable , V any ] struct {
type entry [ K comparable , V any ] struct {
key K
prev , next * entry [ K , V ]
value V
key K
value V
}
}
// Set adds or replaces a value to the cache, set or updating its associated
// Set adds or replaces a value to the cache, set or updating its associated
@ -38,19 +47,18 @@ type entry[K comparable, V any] struct {
// If MaxEntries is non-zero and the length of the cache is greater
// If MaxEntries is non-zero and the length of the cache is greater
// after any addition, the least recently used value is evicted.
// after any addition, the least recently used value is evicted.
func ( c * Cache [ K , V ] ) Set ( key K , value V ) {
func ( c * Cache [ K , V ] ) Set ( key K , value V ) {
if c . m == nil {
if c . lookup == nil {
c . m = make ( map [ K ] * list . Element )
c . lookup = make ( map [ K ] * entry [ K , V ] )
c . ll = list . New ( )
}
}
if e e, ok := c . m [ key ] ; ok {
if e nt, ok := c . lookup [ key ] ; ok {
c . ll . MoveToFront ( ee )
c . moveToFront ( ent )
e e. Value . ( * e ntry[ K , V ] ) . value = value
e nt. value = value
return
return
}
}
e le := c . ll . PushFront ( & entry [ K , V ] { key , value } )
e nt := c . newAtFront ( key , value )
c . m[ key ] = ele
c . lookup[ key ] = ent
if c . MaxEntries != 0 && c . Len ( ) > c . MaxEntries {
if c . MaxEntries != 0 && c . Len ( ) > c . MaxEntries {
c . D eleteOldest( )
c . d eleteOldest( )
}
}
}
}
@ -71,14 +79,14 @@ func (c *Cache[K, V]) Contains(key K) bool {
return ok
return ok
}
}
// GetOk looks up a key's value from the cache, also reporting
// GetOk looks up a key's value from the cache, also reporting whether
// whether it was present.
// it was present.
//
//
// If found, key is moved to the front of the LRU.
// If found, key is moved to the front of the LRU.
func ( c * Cache [ K , V ] ) GetOk ( key K ) ( value V , ok bool ) {
func ( c * Cache [ K , V ] ) GetOk ( key K ) ( value V , ok bool ) {
if e le, hit := c . m [ key ] ; hit {
if e nt, hit := c . lookup [ key ] ; hit {
c . ll . MoveToFront ( ele )
c . moveToFront ( ent )
return e le. Value . ( * e ntry[ K , V ] ) . value , true
return e nt. value , true
}
}
var zero V
var zero V
return zero , false
return zero , false
@ -91,8 +99,8 @@ func (c *Cache[K, V]) GetOk(key K) (value V, ok bool) {
// LRU. This should mostly be used for non-intrusive debug inspection
// LRU. This should mostly be used for non-intrusive debug inspection
// of the cache.
// of the cache.
func ( c * Cache [ K , V ] ) PeekOk ( key K ) ( value V , ok bool ) {
func ( c * Cache [ K , V ] ) PeekOk ( key K ) ( value V , ok bool ) {
if e le, hit := c . m [ key ] ; hit {
if e nt, hit := c . lookup [ key ] ; hit {
return e le. Value . ( * e ntry[ K , V ] ) . value , true
return e nt. value , true
}
}
var zero V
var zero V
return zero , false
return zero , false
@ -100,25 +108,66 @@ func (c *Cache[K, V]) PeekOk(key K) (value V, ok bool) {
// Delete removes the provided key from the cache if it was present.
// Delete removes the provided key from the cache if it was present.
func ( c * Cache [ K , V ] ) Delete ( key K ) {
func ( c * Cache [ K , V ] ) Delete ( key K ) {
if e , ok := c . m [ key ] ; ok {
if e nt , ok := c . lookup [ key ] ; ok {
c . deleteElement ( e )
c . deleteElement ( e nt )
}
}
}
}
// DeleteOldest removes the item from the cache that was least recently
// DeleteOldest removes the item from the cache that was least
// accessed. It is a no-op if the cache is empty.
// recently accessed. It is a no-op if the cache is empty.
func ( c * Cache [ K , V ] ) DeleteOldest ( ) {
func ( c * Cache [ K , V ] ) DeleteOldest ( ) {
if c . ll != nil {
if c . head != nil {
if e := c . ll . Back ( ) ; e != nil {
c . deleteOldest ( )
c . deleteElement ( e )
}
}
}
}
}
func ( c * Cache [ K , V ] ) deleteElement ( e * list . Element ) {
// Len returns the number of items in the cache.
c . ll . Remove ( e )
func ( c * Cache [ K , V ] ) Len ( ) int { return len ( c . lookup ) }
delete ( c . m , e . Value . ( * entry [ K , V ] ) . key )
// newAtFront creates a new LRU entry using key and value, and inserts
// it at the front of c.head.
func ( c * Cache [ K , V ] ) newAtFront ( key K , value V ) * entry [ K , V ] {
ret := & entry [ K , V ] { key : key , value : value }
if c . head == nil {
ret . prev = ret
ret . next = ret
} else {
ret . next = c . head
ret . prev = c . head . prev
c . head . prev . next = ret
c . head . prev = ret
}
c . head = ret
return ret
}
}
// Len returns the number of items in the cache.
// moveToFront moves ent, which must be an existing element of the
func ( c * Cache [ K , V ] ) Len ( ) int { return len ( c . m ) }
// cache, to the front of c.head.
func ( c * Cache [ K , V ] ) moveToFront ( ent * entry [ K , V ] ) {
if c . head == ent {
return
}
ent . prev . next = ent . next
ent . next . prev = ent . prev
ent . prev = c . head . prev
ent . next = c . head
c . head . prev . next = ent
c . head . prev = ent
c . head = ent
}
// deleteOldest removes the oldest entry in the cache. It panics if
// there are no entries in the cache.
func ( c * Cache [ K , V ] ) deleteOldest ( ) { c . deleteElement ( c . head . prev ) }
// deleteElement removes ent from the cache. ent must be an existing
// current element of the cache.
func ( c * Cache [ K , V ] ) deleteElement ( ent * entry [ K , V ] ) {
if ent . next == ent {
c . head = nil
} else {
ent . next . prev = ent . prev
ent . prev . next = ent . next
}
delete ( c . lookup , ent . key )
}