Files
SpeedScope/lru-cache.ts
T

152 lines
3.2 KiB
TypeScript

class ListNode<V> {
prev: ListNode<V> | null = null
next: ListNode<V> | null = null
constructor(readonly data: V) { }
}
export class List<V> {
private head: ListNode<V> | null = null
private tail: ListNode<V> | null = null
private size: number = 0
constructor() { }
getHead(): ListNode<V> | null { return this.head }
getTail(): ListNode<V> | null { return this.tail }
getSize(): number { return this.size }
append(node: ListNode<V>): void {
if (!this.tail) {
this.head = this.tail = node
} else {
this.tail.next = node
node.prev = this.tail
this.tail = node
}
this.size++
}
prepend(node: ListNode<V>): ListNode<V> {
if (!this.head) {
this.head = this.tail = node
} else {
this.head.prev = node
node.next = this.head
this.head = node
}
this.size++
return node
}
pop(): ListNode<V> | null {
if (!this.tail) {
return null
} else {
const ret = this.tail
if (ret.prev) {
this.tail = ret.prev
this.tail.next = null
} else {
this.head = this.tail = null
}
this.size--
ret.prev = null
return ret
}
}
dequeue(): ListNode<V> | null {
if (!this.head) {
return null
} else {
const ret = this.head
if (ret.next) {
this.head = ret.next
this.head.prev = null
} else {
this.head = this.tail = null
}
this.size--
ret.next = null
return ret
}
}
remove(node: ListNode<V>): void {
if (node.prev == null) {
this.dequeue()
} else if (node.next == null) {
this.pop()
} else {
// Neither first nor last, should be safe to just link
// neighbours.
node.next.prev = node.prev
node.prev.next = node.next
node.next = null
node.prev = null
this.size--
}
}
}
interface LRUCacheNode<K, V> {
value: V
listNode: ListNode<K>
}
export class LRUCache<K, V> {
private list = new List<K>()
private map = new Map<K, LRUCacheNode<K, V>>()
constructor(private capacity: number) { }
has(key: K): boolean {
return this.map.has(key)
}
get(key: K): V | null {
const node = this.map.get(key)
if (!node) {
return null
}
// Bring node to the front of the list
this.list.remove(node.listNode)
this.list.prepend(node.listNode)
return node ? node.value : null
}
getSize() { return this.list.getSize() }
getCapacity() { return this.capacity }
insert(key: K, value: V) {
const node = this.map.get(key)
if (node) {
this.list.remove(node.listNode)
}
// Evict old entries when out of capacity
while (this.list.getSize() >= this.capacity) {
this.map.delete(this.list.pop()!.data)
}
const listNode = this.list.prepend(new ListNode(key))
this.map.set(key, { value, listNode })
}
getOrInsert(key: K, f: (key: K) => V): V {
let value = this.get(key)
if (value == null) {
value = f(key)
this.insert(key, value)
}
return value
}
removeLRU(): [K, V] | null {
const oldest = this.list.pop()
if (!oldest) return null
const key = oldest.data
const value = this.map.get(key)!.value
this.map.delete(key)
return [key, value]
}
}