mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2026-03-13 08:51:02 +08:00
154 lines
3.7 KiB
JavaScript
154 lines
3.7 KiB
JavaScript
/* eslint-disable no-param-reassign, max-classes-per-file */
|
|
|
|
/**
|
|
* Simple implementation of the Doubly-Linked List Node
|
|
* that is used in LRUCache class below.
|
|
*/
|
|
class LinkedListNode {
|
|
/**
|
|
* Creates a doubly-linked list node.
|
|
* @param {string} key
|
|
* @param {any} val
|
|
* @param {LinkedListNode} prev
|
|
* @param {LinkedListNode} next
|
|
*/
|
|
constructor(key, val, prev = null, next = null) {
|
|
this.key = key;
|
|
this.val = val;
|
|
this.prev = prev;
|
|
this.next = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation of the LRU (Least Recently Used) Cache
|
|
* based on the HashMap and Doubly Linked List data-structures.
|
|
*
|
|
* Current implementation allows to have fast (O(1)) read and write operations.
|
|
*
|
|
* At any moment in time the LRU Cache holds not more that "capacity" number of items in it.
|
|
*/
|
|
class LRUCache {
|
|
/**
|
|
* Creates a cache instance of a specific capacity.
|
|
* @param {number} capacity
|
|
*/
|
|
constructor(capacity) {
|
|
this.capacity = capacity; // How many items to store in cache at max.
|
|
this.nodesMap = {}; // The quick links to each linked list node in cache.
|
|
this.size = 0; // The number of items that is currently stored in the cache.
|
|
this.head = new LinkedListNode(); // The Head (first) linked list node.
|
|
this.tail = new LinkedListNode(); // The Tail (last) linked list node.
|
|
}
|
|
|
|
/**
|
|
* Returns the cached value by its key.
|
|
* Time complexity: O(1).
|
|
* @param {string} key
|
|
* @returns {any}
|
|
*/
|
|
get(key) {
|
|
if (this.nodesMap[key] === undefined) return undefined;
|
|
const node = this.nodesMap[key];
|
|
this.promote(node);
|
|
return node.val;
|
|
}
|
|
|
|
/**
|
|
* Sets the value to cache by its key.
|
|
* Time complexity: O(1).
|
|
* @param {string} key
|
|
* @param {any} val
|
|
*/
|
|
set(key, val) {
|
|
if (this.nodesMap[key]) {
|
|
const node = this.nodesMap[key];
|
|
node.val = val;
|
|
this.promote(node);
|
|
} else {
|
|
const node = new LinkedListNode(key, val);
|
|
this.append(node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Promotes the node to the end of the linked list.
|
|
* It means that the node is most frequently used.
|
|
* It also reduces the chance for such node to get evicted from cache.
|
|
* @param {LinkedListNode} node
|
|
*/
|
|
promote(node) {
|
|
this.evict(node);
|
|
this.append(node);
|
|
}
|
|
|
|
/**
|
|
* Appends a new node to the end of the cache linked list.
|
|
* @param {LinkedListNode} node
|
|
*/
|
|
append(node) {
|
|
this.nodesMap[node.key] = node;
|
|
|
|
if (!this.head.next) {
|
|
// First node to append.
|
|
this.head.next = node;
|
|
this.tail.prev = node;
|
|
node.prev = this.head;
|
|
node.next = this.tail;
|
|
} else {
|
|
// Append to an existing tail.
|
|
const oldTail = this.tail.prev;
|
|
oldTail.next = node;
|
|
node.prev = oldTail;
|
|
node.next = this.tail;
|
|
this.tail.prev = node;
|
|
}
|
|
|
|
this.size += 1;
|
|
|
|
if (this.size > this.capacity) {
|
|
this.evict(this.head.next);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evicts (removes) the node from cache linked list.
|
|
* @param {LinkedListNode} node
|
|
*/
|
|
evict(node) {
|
|
delete this.nodesMap[node.key];
|
|
this.size -= 1;
|
|
|
|
const prevNode = node.prev;
|
|
const nextNode = node.next;
|
|
|
|
// If one and only node.
|
|
if (prevNode === this.head && nextNode === this.tail) {
|
|
this.head.next = null;
|
|
this.tail.prev = null;
|
|
this.size = 0;
|
|
return;
|
|
}
|
|
|
|
// If this is a Head node.
|
|
if (prevNode === this.head) {
|
|
nextNode.prev = this.head;
|
|
this.head.next = nextNode;
|
|
return;
|
|
}
|
|
|
|
// If this is a Tail node.
|
|
if (nextNode === this.tail) {
|
|
prevNode.next = this.tail;
|
|
this.tail.prev = prevNode;
|
|
return;
|
|
}
|
|
|
|
// If the node is in the middle.
|
|
prevNode.next = nextNode;
|
|
nextNode.prev = prevNode;
|
|
}
|
|
}
|
|
|
|
export default LRUCache;
|