go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/collections/lru.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package collections 9 10 // LRU is a structure that allows you to evict items based on 11 // which were used last. 12 // 13 // It combines a light linked list structure with a map for 14 // fast lookups and fast evictions based on which item 15 // is at the "tail" of the linked list. 16 type LRU[K comparable, V any] struct { 17 Capacity int 18 OnEvict func(K, V) 19 20 head *lruItem[K, V] 21 tail *lruItem[K, V] 22 lookup map[K]*lruItem[K, V] 23 } 24 25 // Get returns an item with a given key. 26 func (lru *LRU[K, V]) Get(k K) (v V, ok bool) { 27 if lru.lookup == nil { 28 return 29 } 30 var i *lruItem[K, V] 31 if i, ok = lru.lookup[k]; !ok { 32 return 33 } 34 lru.moveToTail(i) 35 v = i.value 36 return 37 } 38 39 // Touch moves a given key to the end of the lru queue. 40 func (lru *LRU[K, V]) Touch(k K) (ok bool) { 41 if lru.lookup == nil { 42 return 43 } 44 45 item, ok := lru.lookup[k] 46 if !ok { 47 return 48 } 49 lru.moveToTail(item) 50 return 51 } 52 53 // Get returns an item with a given key. 54 func (lru *LRU[K, V]) Set(k K, v V) { 55 if lru.lookup == nil { 56 lru.lookup = make(map[K]*lruItem[K, V]) 57 } 58 59 if item, ok := lru.lookup[k]; ok { 60 item.value = v 61 lru.moveToTail(item) 62 return 63 } 64 65 newItem := &lruItem[K, V]{ 66 key: k, 67 value: v, 68 } 69 lru.lookup[k] = newItem 70 if lru.head == nil { 71 lru.head = newItem 72 lru.tail = newItem 73 return 74 } 75 lru.moveToTail(newItem) 76 77 if lru.Capacity > 0 && len(lru.lookup) > lru.Capacity { 78 delete(lru.lookup, lru.head.key) 79 if lru.OnEvict != nil { 80 lru.OnEvict(lru.head.key, lru.head.value) 81 } 82 lru.removeHead() 83 } 84 } 85 86 // Remove removes an element. 87 func (lru *LRU[K, V]) Remove(k K) (ok bool) { 88 if lru.lookup == nil { 89 return 90 } 91 92 var i *lruItem[K, V] 93 if i, ok = lru.lookup[k]; !ok { 94 return 95 } 96 delete(lru.lookup, k) 97 98 if lru.head == i { 99 lru.removeHead() 100 return 101 } 102 lru.removeItem(i) 103 return 104 } 105 106 // Head returns the head, or oldest, key and value. 107 func (lru *LRU[K, V]) Head() (k K, v V, ok bool) { 108 if lru.head == nil { 109 return 110 } 111 k = lru.head.key 112 v = lru.head.value 113 ok = true 114 return 115 } 116 117 // Tail returns the tail, or most recently used, key and value. 118 func (lru *LRU[K, V]) Tail() (k K, v V, ok bool) { 119 if lru.tail == nil { 120 return 121 } 122 k = lru.tail.key 123 v = lru.tail.value 124 ok = true 125 return 126 } 127 128 // Len returns the number of items in the lru cache. 129 func (lru *LRU[K, V]) Len() int { return len(lru.lookup) } 130 131 // Each calls a given function for each element in the lru cache. 132 func (lru *LRU[K, V]) Each(fn func(K, V)) { 133 current := lru.head 134 for current != nil { 135 fn(current.key, current.value) 136 current = current.previous 137 } 138 } 139 140 // 141 // internal helpers 142 // 143 144 func (lru *LRU[K, V]) moveToTail(i *lruItem[K, V]) { 145 if lru.tail == i { 146 return 147 } 148 149 // remove item from existing place in list 150 if lru.head == i { 151 lru.head = i.previous 152 lru.head.next = nil 153 } else { 154 after := i.previous 155 before := i.next 156 if after != nil { 157 after.next = before 158 } 159 if before != nil { 160 before.previous = after 161 } 162 } 163 164 // append to tail 165 i.next = lru.tail 166 i.previous = nil 167 lru.tail.previous = i 168 lru.tail = i 169 } 170 171 func (lru *LRU[K, V]) removeHead() { 172 if lru.head == nil { 173 return 174 } 175 176 // if we have a single element, 177 // we will need to change the tail 178 // pointer as well 179 if lru.head == lru.tail { 180 lru.head = nil 181 lru.tail = nil 182 return 183 } 184 185 // remove from head 186 after := lru.head.previous 187 if after != nil { 188 after.next = nil 189 } 190 lru.head = after 191 } 192 193 func (lru *LRU[K, V]) removeItem(i *lruItem[K, V]) { 194 after := i.previous 195 before := i.next 196 if after != nil { 197 after.next = before 198 } 199 if before != nil { 200 before.previous = after 201 } 202 if lru.tail == i { 203 lru.tail = i.next 204 if lru.tail != nil { 205 lru.tail.previous = nil 206 } 207 } 208 } 209 210 type lruItem[K comparable, V any] struct { 211 key K 212 value V 213 214 // next points towards the head 215 next *lruItem[K, V] 216 // previous points towards the tail 217 previous *lruItem[K, V] 218 }