ds-algo-study
Version:
Just experimenting with publishing a package
250 lines (224 loc) • 5.43 kB
JavaScript
// ============================================================================
// Interview Problem: Design and Implement an LRU Cache
// ============================================================================
//
// -------
// Prompt:
// -------
//
// Given the implementation of a Doubly Linked List, design and implement
// an LRU, or Least Recently Used, cache.
//
// ------------
// Explanation:
// ------------
//
// An LRU Cache is a data structure valued for its constant time read and write
// operations. Its methods, get(key) and set(key, val), are both O(1), just
// like a Hash Table.
//
// However, unlike a Hash Table, instead of re-sizing once it reaches its
// maximum capacity for stored items, it instead removes the least recently
// used item from the cache, also in O(1) time.
//
// NOTE: We determine the item that is "Least Recently Used" by its most-recent
// "get" time, not just by its creation time!
//
// --------
// Example:
// --------
//
// const lruCache = new LRUCache(4); => limit of 4 items
// lruCache.set('a', 'A');
// lruCache.set('b', 'B');
// lruCache.set('c', 'C');
// lruCache.set('d', 'D');
// lruCache.set('e', 'E');
//
// lruCache.get('c') => 'C'
// lruCache.get('b') => 'B'
//
// Item 'a' was removed because it was the oldest item by insertion/usage
//
// lruCache.get('a') => null
//
// Next, item 'e' is removed to make room, because it is the oldest by usage,
// which takes priority.
//
// lruCache.set('f', 'F');
//
// Item 'd' is also removed, because it was retrieved before item 'b' was
// last retrieved.
//
// lruCache.set('g', G);
//
// -----------
// Let's code!
// -----------
// TODO: Implement the LRUCacheItem class here
class LRUCacheItem {
constructor(val = null, key = null) {
this.value = val;
this.key = key;
this.node = null;
}
}
// TODO: Implement the LRUCacheItem class here
class LRUCache {
constructor(limit) {
this.cache = new List();
this.items = {};
this.length = 0;
this.limit = limit;
}
// TODO: Implement the size method here
size() {
return this.length;
}
// TODO: Implement the get method here
get(key) {
const item = this.items[key];
if (item) {
this.promote(item);
return item.value;
} else {
return null;
}
}
// TODO: Implement the set method here
set(key, val) {
const item = this.items[key];
if (item) {
item.value = val;
this.promote(item);
} else {
if (this.isFull()) this.prune();
const newItem = new LRUCacheItem(val, key);
newItem.node = this.cache.unshift(newItem);
this.items[key] = newItem;
this.length++;
}
}
isFull() {
return this.length === this.limit;
}
prune() {
const removed = this.cache.pop();
delete this.items[removed.key];
this.length--;
this.length = Math.max(this.length, 0);
}
promote(item) {
this.cache.moveToFront(item.node);
}
}
// ----------------------------------------
// Given: Doubly Linked List - Do Not Edit!
// ----------------------------------------
class ListNode {
constructor(val, prev = null, next = null) {
this.prev = prev;
this.val = val;
this.next = next;
}
delete() {
if (this.prev) this.prev.next = this.next;
if (this.next) this.next.prev = this.prev;
}
}
class List {
constructor() {
this.head = null;
this.tail = null;
}
// Insert at the head of the list.
unshift(val) {
if (this.head === null && this.tail === null) {
this.head = new ListNode(val);
this.tail = this.head;
} else {
this.head = new ListNode(val, null, this.head);
this.head.next.prev = this.head;
}
return this.head;
}
// Delete at the head of the list.
shift() {
if (this.head === null && this.tail === null) {
return null;
} else {
let head = this.head;
this.head = this.head.next;
head.delete();
return head.val;
}
}
// Insert at the end of the list.
push(val) {
if (this.head === null && this.tail === null) {
this.head = this.tail = new ListNode(val);
} else {
this.tail = new ListNode(val, this.tail, null);
this.tail.prev.next = this.tail;
}
return this.tail;
}
// Delete at the end of the list.
pop() {
if (this.head === null && this.tail === null) {
return null;
} else {
let tail = this.tail;
this.tail = this.tail.prev;
tail.delete();
return tail.val;
}
}
// Move a node to the front of the List
moveToFront(node) {
if (node === this.tail) {
this.pop();
} else if (node === this.head) {
return;
} else {
node.delete();
}
node.prev = node.next = null;
// Don't delegate to shift, since we want to keep the same
// object.
if (this.head === null && this.tail === null) {
this.head = node;
this.tail = node;
} else {
this.head.prev = node;
node.next = this.head;
this.head = node;
}
}
// Move a node to the end of the List
moveToEnd(node) {
if (node === this.head) {
this.shift();
} else if (node === this.tail) {
return;
} else {
node.delete();
}
// Don't delegate to push, since we want to keep the same
// object.
node.prev = null;
node.next = null;
if (this.head === null && this.tail === null) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
}
}
exports.List = List;
exports.ListNode = ListNode;
exports.LRUCache = LRUCache;
exports.LRUCacheItem = LRUCacheItem;