Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions data_structures/cache/BaseCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Represents the foundational architecture for caching mechanisms.
*
* This abstract class defines the standard contract and shared state
* required by specialized cache implementations, ensuring a consistent
* interface for data storage and retrieval.
*
* @template K - The type of keys maintained by the cache.
* @template V - The type of mapped values.
*/
export abstract class BaseCache<K, V> {
/**
* The maximum number of items the cache is permitted to hold
* before eviction policies are triggered.
*/
protected capacity: number;

/**
* Initializes a new instance of the BaseCache.
*
* @param capacity - The maximum allowed capacity of the cache.
* @throws {Error} If the provided capacity is less than or equal to zero.
*/
constructor(capacity: number) {
if (capacity <= 0) throw new Error("Capacity must be positive");
this.capacity = capacity;
}

/**
* Retrieves the value associated with the specified key.
*
* @param key - The key whose associated value is to be returned.
* @returns The value associated with the key, or undefined if the key does not exist.
*/
abstract get(key: K): V | undefined;

/**
* Associates the specified value with the specified key in the cache.
* If the cache previously contained a mapping for the key, the old value is replaced.
*
* @param key - The key with which the specified value is to be associated.
* @param value - The value to be associated with the specified key.
*/
abstract put(key: K, value: V): void;
}
164 changes: 164 additions & 0 deletions data_structures/cache/LRUCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { BaseCache } from './BaseCache';

/**
* Represents a single node within the doubly linked list utilized by the LRU Cache.
*
* @template K - The type of the key.
* @template V - The type of the value.
*/
class Node<K, V> {
/**
* Initializes a new instance of the Node.
*
* @param key - The identifying key for the node.
* @param value - The data value stored within the node.
* @param prev - Reference to the preceding node in the sequence.
* @param next - Reference to the succeeding node in the sequence.
*/
constructor(
public key: K,
public value: V,
public prev: Node<K, V> | null = null,
public next: Node<K, V> | null = null
) { }
}

/**
* A Least Recently Used (LRU) Cache implementation.
*
* This advanced cache automatically evicts the least recently accessed items
* when the strictly defined capacity limit is reached. It systematically combines
* a native Hash Map for constant-time lookups with a Doubly Linked List to
* guarantee that both `get` and `put` operations execute in rigorous O(1) time complexity.
*
* @template K - The type of keys maintained by the cache.
* @template V - The type of mapped values.
*/
export class LRUCache<K, V> extends BaseCache<K, V> {
/**
* An internal hash map establishing an O(1) lookup index for cache nodes.
*/
private map: Map<K, Node<K, V>> = new Map();

/**
* The vanguard of the doubly linked list, exclusively holding the most recently utilized item.
*/
private head: Node<K, V> | null = null;

/**
* The rearguard of the doubly linked list, designating the least recently utilized item
* (the prime candidate for the next eviction).
*/
private tail: Node<K, V> | null = null;

/**
* Instantiates an LRUCache with a precise maximum capacity.
*
* @param capacity - The absolute maximum number of key-value pairs the cache can concurrently retain.
*/
constructor(capacity: number) {
super(capacity);
}

/**
* Retrieves the precise value associated with the specified key, synchronously
* elevating the corresponding item to "most recently used" status.
*
* @param key - The key whose associated value is to be targeted.
* @returns The value associated with the key, or `undefined` if the cache miss occurs.
*/
get(key: K): V | undefined {
const node = this.map.get(key);
if (!node) return undefined;

this.moveToFront(node);
return node.value;
}

/**
* Inserts or seamlessly updates a key-value mapping within the cache structure.
* If the addition violates the cache's capacity constraint, the least recently
* utilized item is systematically purged.
*
* @param key - The key to insert or update.
* @param value - The updated value to bind to the given key.
*/
put(key: K, value: V): void {
const existingNode = this.map.get(key);

if (existingNode) {
existingNode.value = value;
this.moveToFront(existingNode);
} else {
if (this.map.size >= this.capacity) {
this.evictLeastRecentlyUsed();
}

const newNode = new Node(key, value);
this.map.set(key, newNode);
this.addToFront(newNode);
}
}

/**
* Re-links a specific, active node to the pinnacle (head) of the linked list.
*
* @param node - The node requiring promotion.
*/
private moveToFront(node: Node<K, V>): void {
if (node === this.head) return;

this.removeNode(node);
this.addToFront(node);
}

/**
* Injects a novel node directly at the head of the linked list.
*
* @param node - The freshly instantiated node to insert.
*/
private addToFront(node: Node<K, V>): void {
node.next = this.head;
node.prev = null;

if (this.head) {
this.head.prev = node;
}

this.head = node;

if (!this.tail) {
this.tail = node;
}
}

/**
* Surgically detaches a specific node from its surrounding linked list neighbors.
*
* @param node - The target node to isolate and remove.
*/
private removeNode(node: Node<K, V>): void {
if (node.prev) {
node.prev.next = node.next;
} else {
this.head = node.next;
}

if (node.next) {
node.next.prev = node.prev;
} else {
this.tail = node.prev;
}
}

/**
* Executes the eviction protocol, purging the least recently utilized node
* from both the linked list hierarchy and the primary map index.
*/
private evictLeastRecentlyUsed(): void {
if (!this.tail) return;

this.map.delete(this.tail.key);
this.removeNode(this.tail);
}
}
79 changes: 79 additions & 0 deletions data_structures/cache/test/LRUCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { LRUCache } from '../LRUCache';
import { BaseCache } from '../BaseCache';

/**
* Test Suite: Least Recently Used (LRU) Cache Implementation
*
* This suite rigorously validates the functional requirements of the LRUCache,
* ensuring robust key-value storage, correct eviction policies upon reaching
* capacity, and O(1) update characteristics when items are accessed.
*/
describe('LRUCache', () => {
let lru: LRUCache<string, number>;

beforeEach(() => {
// Initialize the cache with a constrained capacity to systematically trigger and verify eviction logic.
lru = new LRUCache<string, number>(3);
});

it('should be an instance of BaseCache', () => {
expect(lru).toBeInstanceOf(BaseCache);
});

it('should successfully store and retrieve standard key-value pairs', () => {
lru.put('a', 1);
lru.put('b', 2);
expect(lru.get('a')).toBe(1);
expect(lru.get('b')).toBe(2);
});

it('should gracefully return undefined for queries on non-existent keys', () => {
expect(lru.get('non-existent')).toBeUndefined();
});

it('should accurately update the value corresponding to an existing key', () => {
lru.put('a', 1);
lru.put('a', 10);
expect(lru.get('a')).toBe(10);
});

it('should strictly evict the least recently used item when the maximum capacity is exceeded', () => {
lru.put('a', 1); // State: [a]
lru.put('b', 2); // State: [b, a]
lru.put('c', 3); // State: [c, b, a]

// Inserting a 4th element must trigger the eviction of 'a' (the current tail)
lru.put('d', 4); // State: [d, c, b]

expect(lru.get('a')).toBeUndefined();
expect(lru.get('b')).toBe(2);
expect(lru.get('c')).toBe(3);
expect(lru.get('d')).toBe(4);
});

it('should successfully re-prioritize an accessed item to the front of the cache (Most Recently Used)', () => {
lru.put('a', 1);
lru.put('b', 2);
lru.put('c', 3); // State: [c, b, a]

// Accessing 'a' promotes it to the head, making it the most recently used
lru.get('a'); // State: [a, c, b]

// Consequently, 'b' falls to the tail position and becomes the next candidate for eviction
lru.put('d', 4); // State: [d, a, c]

expect(lru.get('b')).toBeUndefined();
expect(lru.get('a')).toBe(1);
expect(lru.get('c')).toBe(3);
expect(lru.get('d')).toBe(4);
});

it('should maintain strict operational integrity even with a minimal capacity of 1', () => {
const smallLru = new LRUCache<string, number>(1);
smallLru.put('a', 1);
smallLru.put('b', 2);

expect(smallLru.get('a')).toBeUndefined();
expect(smallLru.get('b')).toBe(2);
});
});