Spaces:
Running
Running
| /** | |
| * Node in the doubly-linked list used for LRU tracking. | |
| * Each node represents a cache entry with bidirectional pointers. | |
| */ "use strict"; | |
| Object.defineProperty(exports, "__esModule", { | |
| value: true | |
| }); | |
| Object.defineProperty(exports, "LRUCache", { | |
| enumerable: true, | |
| get: function() { | |
| return LRUCache; | |
| } | |
| }); | |
| class LRUNode { | |
| constructor(key, data, size){ | |
| this.prev = null; | |
| this.next = null; | |
| this.key = key; | |
| this.data = data; | |
| this.size = size; | |
| } | |
| } | |
| /** | |
| * Sentinel node used for head/tail boundaries. | |
| * These nodes don't contain actual cache data but simplify list operations. | |
| */ class SentinelNode { | |
| constructor(){ | |
| this.prev = null; | |
| this.next = null; | |
| } | |
| } | |
| class LRUCache { | |
| constructor(maxSize, calculateSize){ | |
| this.cache = new Map(); | |
| this.totalSize = 0; | |
| this.maxSize = maxSize; | |
| this.calculateSize = calculateSize; | |
| // Create sentinel nodes to simplify doubly-linked list operations | |
| // HEAD <-> TAIL (empty list) | |
| this.head = new SentinelNode(); | |
| this.tail = new SentinelNode(); | |
| this.head.next = this.tail; | |
| this.tail.prev = this.head; | |
| } | |
| /** | |
| * Adds a node immediately after the head (marks as most recently used). | |
| * Used when inserting new items or when an item is accessed. | |
| * PRECONDITION: node must be disconnected (prev/next should be null) | |
| */ addToHead(node) { | |
| node.prev = this.head; | |
| node.next = this.head.next; | |
| // head.next is always non-null (points to tail or another node) | |
| this.head.next.prev = node; | |
| this.head.next = node; | |
| } | |
| /** | |
| * Removes a node from its current position in the doubly-linked list. | |
| * Updates the prev/next pointers of adjacent nodes to maintain list integrity. | |
| * PRECONDITION: node must be connected (prev/next are non-null) | |
| */ removeNode(node) { | |
| // Connected nodes always have non-null prev/next | |
| node.prev.next = node.next; | |
| node.next.prev = node.prev; | |
| } | |
| /** | |
| * Moves an existing node to the head position (marks as most recently used). | |
| * This is the core LRU operation - accessed items become most recent. | |
| */ moveToHead(node) { | |
| this.removeNode(node); | |
| this.addToHead(node); | |
| } | |
| /** | |
| * Removes and returns the least recently used node (the one before tail). | |
| * This is called during eviction when the cache exceeds capacity. | |
| * PRECONDITION: cache is not empty (ensured by caller) | |
| */ removeTail() { | |
| const lastNode = this.tail.prev; | |
| // tail.prev is always non-null and always LRUNode when cache is not empty | |
| this.removeNode(lastNode); | |
| return lastNode; | |
| } | |
| /** | |
| * Sets a key-value pair in the cache. | |
| * If the key exists, updates the value and moves to head. | |
| * If new, adds at head and evicts from tail if necessary. | |
| * | |
| * Time Complexity: | |
| * - O(1) for uniform item sizes | |
| * - O(k) where k is the number of items evicted (can be O(N) for variable sizes) | |
| */ set(key, value) { | |
| const size = (this.calculateSize == null ? void 0 : this.calculateSize.call(this, value)) ?? 1; | |
| if (size > this.maxSize) { | |
| console.warn('Single item size exceeds maxSize'); | |
| return; | |
| } | |
| const existing = this.cache.get(key); | |
| if (existing) { | |
| // Update existing node: adjust size and move to head (most recent) | |
| existing.data = value; | |
| this.totalSize = this.totalSize - existing.size + size; | |
| existing.size = size; | |
| this.moveToHead(existing); | |
| } else { | |
| // Add new node at head (most recent position) | |
| const newNode = new LRUNode(key, value, size); | |
| this.cache.set(key, newNode); | |
| this.addToHead(newNode); | |
| this.totalSize += size; | |
| } | |
| // Evict least recently used items until under capacity | |
| while(this.totalSize > this.maxSize && this.cache.size > 0){ | |
| const tail = this.removeTail(); | |
| this.cache.delete(tail.key); | |
| this.totalSize -= tail.size; | |
| } | |
| } | |
| /** | |
| * Checks if a key exists in the cache. | |
| * This is a pure query operation - does NOT update LRU order. | |
| * | |
| * Time Complexity: O(1) | |
| */ has(key) { | |
| return this.cache.has(key); | |
| } | |
| /** | |
| * Retrieves a value by key and marks it as most recently used. | |
| * Moving to head maintains the LRU property for future evictions. | |
| * | |
| * Time Complexity: O(1) | |
| */ get(key) { | |
| const node = this.cache.get(key); | |
| if (!node) return undefined; | |
| // Mark as most recently used by moving to head | |
| this.moveToHead(node); | |
| return node.data; | |
| } | |
| /** | |
| * Returns an iterator over the cache entries. The order is outputted in the | |
| * order of most recently used to least recently used. | |
| */ *[Symbol.iterator]() { | |
| let current = this.head.next; | |
| while(current && current !== this.tail){ | |
| // Between head and tail, current is always LRUNode | |
| const node = current; | |
| yield [ | |
| node.key, | |
| node.data | |
| ]; | |
| current = current.next; | |
| } | |
| } | |
| /** | |
| * Removes a specific key from the cache. | |
| * Updates both the hash map and doubly-linked list. | |
| * | |
| * Time Complexity: O(1) | |
| */ remove(key) { | |
| const node = this.cache.get(key); | |
| if (!node) return; | |
| this.removeNode(node); | |
| this.cache.delete(key); | |
| this.totalSize -= node.size; | |
| } | |
| /** | |
| * Returns the number of items in the cache. | |
| */ get size() { | |
| return this.cache.size; | |
| } | |
| /** | |
| * Returns the current total size of all cached items. | |
| * This uses the custom size calculation if provided. | |
| */ get currentSize() { | |
| return this.totalSize; | |
| } | |
| } | |
| //# sourceMappingURL=lru-cache.js.map |