From 488d9191ef9f071e511d7f6341d8c8edf312ac7d Mon Sep 17 00:00:00 2001 From: Maxim Alyoshin Date: Sun, 3 May 2026 21:58:27 +0300 Subject: [PATCH 1/2] refactor: remove redundant code --- src/data-structures/cache/lfu/index.spec.ts | 42 ---- src/data-structures/cache/lfu/index.ts | 82 +++--- .../cache/lru/lru-cache-on-map/index.spec.ts | 99 ++------ .../cache/lru/lru-cache-on-map/index.ts | 30 +-- .../cache/lru/lru-cache/index.spec.ts | 45 +--- .../cache/lru/lru-cache/index.ts | 42 ++-- src/data-structures/cache/mfu/index.spec.ts | 233 ++---------------- src/data-structures/cache/mfu/index.ts | 216 ++++------------ src/data-structures/cache/types.ts | 2 - src/data-structures/deque/index.spec.ts | 9 - src/data-structures/deque/index.ts | 5 - src/data-structures/disjoint-set/index.ts | 7 +- src/data-structures/hash-table/index.spec.ts | 9 - src/data-structures/hash-table/index.ts | 5 - src/data-structures/heap/heap.ts | 5 - .../heap/max-heap/index.spec.ts | 91 ++----- src/data-structures/heap/max-heap/index.ts | 5 - .../heap/min-heap/index.spec.ts | 71 +----- src/data-structures/heap/min-heap/index.ts | 5 - .../doubly-linked-list/index.spec.ts | 9 - .../linked-lists/doubly-linked-list/index.ts | 4 - .../linked-lists/linked-list/index.spec.ts | 9 - .../linked-lists/linked-list/index.ts | 4 - .../priority-queue/index.spec.ts | 31 +-- src/data-structures/promise/index.ts | 21 +- src/data-structures/queue/index.spec.ts | 9 - src/data-structures/queue/index.ts | 5 - src/data-structures/stack/index.spec.ts | 9 - src/data-structures/stack/index.ts | 5 - .../abstract-linked-list.ts | 5 +- 30 files changed, 207 insertions(+), 907 deletions(-) diff --git a/src/data-structures/cache/lfu/index.spec.ts b/src/data-structures/cache/lfu/index.spec.ts index a1e62f1..c22ad57 100644 --- a/src/data-structures/cache/lfu/index.spec.ts +++ b/src/data-structures/cache/lfu/index.spec.ts @@ -10,28 +10,6 @@ describe('LFUCache', () => { }); it('returns initial state correctly', () => { expect(cache.size).toBe(0); - expect(cache.toArray()).toEqual([]); - expect(cache.isEmpty).toBeTruthy(); - }); - - describe('toArray', () => { - it('returns values of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray()).toEqual([1, 2]); - }); - - it('returns keys of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray(({ key }) => key)).toEqual(['a', 'b']); - }); }); describe('put', () => { @@ -40,7 +18,6 @@ describe('LFUCache', () => { cache.put('a', 1); // Assert - expect(cache.toArray()).toEqual([1]); expect(cache.size).toBe(1); }); @@ -49,7 +26,6 @@ describe('LFUCache', () => { cache.put('a', 1); // Assert - expect(cache.toArray()).toEqual([1]); expect(cache.size).toBe(1); }); @@ -61,7 +37,6 @@ describe('LFUCache', () => { cache.put('a', 2); // Assert - expect(cache.toArray()).toEqual([2]); expect(cache.size).toBe(1); }); @@ -85,7 +60,6 @@ describe('LFUCache', () => { // [2] -> 1, 2 // Assert - expect(lfu.toArray()).toEqual([4, 1, 2]); expect(lfu.size).toBe(3); }); }); @@ -106,7 +80,6 @@ describe('LFUCache', () => { // Act and Assert expect(cache.get('a')).toBe(1); - expect(cache.toArray()).toEqual([1]); expect(cache.size).toBe(1); }); @@ -129,7 +102,6 @@ describe('LFUCache', () => { // [2] -> 1, 2 // Assert - expect(lfu.toArray()).toEqual([4, 1, 2]); expect(lfu.get('c')).toBeNull(); expect(lfu.get('d')).toBe(4); expect(lfu.get('a')).toBe(1); @@ -145,24 +117,10 @@ describe('LFUCache', () => { lfu.put('b', 2); lfu.put('c', 3); - expect(lfu.toArray()).toEqual([1, 2, 3]); - expect(lfu.isEmpty).toBeFalsy(); - // Act lfu.clear(); // Assert - expect(lfu.toArray()).toEqual([]); - expect(lfu.isEmpty).toBeTruthy(); expect(lfu.size).toBe(0); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new LFUCache(6))).toBe( - '[object LFUCache]', - ); - }); - }); }); diff --git a/src/data-structures/cache/lfu/index.ts b/src/data-structures/cache/lfu/index.ts index 8b79a4f..01f1f8c 100644 --- a/src/data-structures/cache/lfu/index.ts +++ b/src/data-structures/cache/lfu/index.ts @@ -20,19 +20,6 @@ export class LFUCache return this.#storage.size; } - toArray( - callbackfn: (item: Payload) => T = (item) => - item.value as unknown as T, - ): T[] { - return Object.values(this.#storage.store) - .flatMap((list) => list?.toArray()) - .map(callbackfn); - } - - get isEmpty() { - return this.toArray().length === 0; - } - put(key: Key, value: Value) { const storage = this.#storage; @@ -41,7 +28,7 @@ export class LFUCache } if (storage.size === this.#capacity) { - storage.evictLeastFrequentItem(); + storage.evictLeastFrequentlyUsed(); } storage.setItem(key, value); @@ -65,13 +52,10 @@ export class LFUCache clear() { this.#storage.clear(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return 'LFUCache'; - } } +const INITIAL_MIN_FREQUENCY_VALUE = 1; + class Storage { #keyFrequencyMap = new Map(); @@ -82,9 +66,7 @@ class Storage { #keyNodeMap = new Map>>(); - #INITIAL_MIN_FREQUENCY_VALUE = 1; - - #currentMinFrequency = this.#INITIAL_MIN_FREQUENCY_VALUE; + #currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE; get size() { return this.#keyNodeMap.size; @@ -93,16 +75,26 @@ class Storage { get store() { return this.#frequencyBuckets; } - getItem(key: Key) { - return this.#keyNodeMap.get(key); + const node = this.#keyNodeMap.get(key); + if (!node) return null; + + this.#updateFrequency(key, node); + + return node; } setItem(key: Key, value: Value) { - this.#increaseFrequency(key); - this.#resetMinFrequencyIfNeeded(key); + if (this.#keyNodeMap.has(key)) { + const node = this.#keyNodeMap.get(key)!; + node.data.value = value; - const frequency = this.#keyFrequencyMap.get(key)!; + this.#updateFrequency(key, node); + + return; + } + + const frequency = INITIAL_MIN_FREQUENCY_VALUE; const bucket = this.#getOrCreateBucket(frequency); bucket.append({ key, value }); @@ -111,21 +103,29 @@ class Storage { const newNode = bucket.tail!; this.#keyNodeMap.set(key, newNode); this.#keyFrequencyMap.set(key, frequency); - } - - #increaseFrequency(key: Key) { - const INITIAL_FREQUENCY_VALUE = 0; - const frequency = this.#keyFrequencyMap.get(key) ?? INITIAL_FREQUENCY_VALUE; - this.#keyFrequencyMap.set(key, frequency + 1); + this.#currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE; } - #resetMinFrequencyIfNeeded(key: Key) { - const frequency = this.#keyFrequencyMap.get(key); + #updateFrequency(key: Key, node: DoublyLinkedListNode>) { + const oldFreq = this.#keyFrequencyMap.get(key)!; + const oldBucket = this.#frequencyBuckets[oldFreq]; + + oldBucket.deleteByRef(node); - if (frequency === this.#INITIAL_MIN_FREQUENCY_VALUE) { - this.#currentMinFrequency = frequency; + if (oldBucket.isEmpty && this.#currentMinFrequency === oldFreq) { + this.#currentMinFrequency += 1; } + + const newFreq = oldFreq + 1; + const newBucket = this.#getOrCreateBucket(newFreq); + + newBucket.append(node.data); + this.#frequencyBuckets[newFreq] = newBucket; + + const newNode = newBucket.tail!; + this.#keyNodeMap.set(key, newNode); + this.#keyFrequencyMap.set(key, newFreq); } #getOrCreateBucket(frequency: number) { @@ -149,13 +149,13 @@ class Storage { } } - evictLeastFrequentItem() { + evictLeastFrequentlyUsed() { const minFreq = this.#currentMinFrequency; const bucket = this.#frequencyBuckets[minFreq]; - const leastFrequentNode = bucket.removeHead()!; + const victim = bucket.removeHead()!; - const { key } = leastFrequentNode.data; + const { key } = victim.data; this.#keyNodeMap.delete(key); this.#keyFrequencyMap.delete(key); } @@ -164,6 +164,6 @@ class Storage { this.#frequencyBuckets = {}; this.#keyNodeMap.clear(); this.#keyFrequencyMap.clear(); - this.#currentMinFrequency = this.#INITIAL_MIN_FREQUENCY_VALUE; + this.#currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE; } } diff --git a/src/data-structures/cache/lru/lru-cache-on-map/index.spec.ts b/src/data-structures/cache/lru/lru-cache-on-map/index.spec.ts index 09f5d07..bec2d21 100644 --- a/src/data-structures/cache/lru/lru-cache-on-map/index.spec.ts +++ b/src/data-structures/cache/lru/lru-cache-on-map/index.spec.ts @@ -11,133 +11,66 @@ describe('LRUCacheOnMap', () => { it('returns initial state correctly', () => { // Act and Assert - expect(cache.toArray()).toEqual([]); - expect(cache.isEmpty).toBeTruthy(); expect(cache.size).toBe(0); }); - describe('toArray', () => { - it('returns values of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray()).toEqual([1, 2]); - }); - - it('returns keys of items', () => { + describe('put', () => { + it('adds and overwrites items correctly', () => { // Arrange cache.put('a', 1); cache.put('b', 2); + cache.put('a', 3); // Act and Assert - expect(cache.toArray(({ key }) => key)).toEqual(['a', 'b']); - }); - }); - - describe('put', () => { - it('adds item correctly', () => { - // Act - cache.put('one', 1); - - // Assert - expect(cache.toArray()).toEqual([1]); - expect(cache.isEmpty).toBeFalsy(); - expect(cache.size).toBe(1); - }); - - it('fills the cache', () => { - // Act - cache.put('one', 1); - cache.put('two', 2); - - // Assert - expect(cache.toArray()).toEqual([1, 2]); expect(cache.size).toBe(2); }); - it(`overwrites item's value correctly`, () => { - // Arrange - cache.put('key', 1); - - // Act - cache.put('key', 2); - - // Assert - expect(cache.toArray()).toEqual([2]); - expect(cache.size).toBe(1); - }); - - it('returns the correct size when exceeding capacity', () => { + it('evicts least recently used item when exceeding capacity', () => { // Arrange cache.put('one', 1); cache.put('two', 2); cache.put('three', 3); - // 2, 3, 4 // Act cache.put('four', 4); // Assert - expect(cache.toArray()).toEqual([2, 3, 4]); expect(cache.size).toBe(3); }); }); describe('get', () => { - it('returns null for a non-existing item', () => { + it('returns null for a missing item', () => { // Act and Assert - expect(cache.get('one')).toBeNull(); + expect(cache.get('missing')).toBeNull(); }); - it(`returns item's value correctly`, () => { - // Arrange - cache.put('one', 1); - - // Act and Assert - expect(cache.toArray()).toEqual([1]); - expect(cache.size).toBe(1); - }); - - it('evicts the least recently used item when exceeding capacity', () => { + it('retrieves item and updates order correctly', () => { // Arrange cache.put('one', 1); cache.put('two', 2); - cache.put('three', 3); cache.get('one'); // Act - cache.put('four', 4); // Should evict 2 + cache.put('three', 3); + cache.put('four', 4); // Should evict 'two' // Assert expect(cache.get('two')).toBeNull(); - expect(cache.toArray()).toEqual([3, 1, 4]); expect(cache.size).toBe(3); }); }); - it('clears cache', () => { + it('clears the cache', () => { // Arrange - const lfu = new LRUCache(3); - lfu.put('a', 1); - lfu.put('b', 2); - lfu.put('c', 3); + cache.put('a', 1); + cache.put('b', 2); + cache.put('c', 3); // Act - lfu.clear(); + cache.clear(); - expect(lfu.toArray()).toEqual([]); - expect(lfu.isEmpty).toBeTruthy(); - expect(lfu.size).toBe(0); - }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new LRUCache(6))).toBe( - '[object LRUCache]', - ); - }); + // Assert + expect(cache.size).toBe(0); }); }); diff --git a/src/data-structures/cache/lru/lru-cache-on-map/index.ts b/src/data-structures/cache/lru/lru-cache-on-map/index.ts index 386236d..67c4456 100644 --- a/src/data-structures/cache/lru/lru-cache-on-map/index.ts +++ b/src/data-structures/cache/lru/lru-cache-on-map/index.ts @@ -1,4 +1,4 @@ -import type { ICache, Payload } from '@/data-structures/cache/types'; +import type { ICache } from '@/data-structures/cache/types'; export class LRUCache implements ICache @@ -15,28 +15,14 @@ export class LRUCache return this.#storage.size; } - get isEmpty() { - return this.toArray().length === 0; - } - - toArray( - callbackfn: (item: Payload) => T = (item) => - item.value as unknown as T, - ) { - const iterable = this.#storage; - - return Array.from(iterable, ([key, value]) => ({ key, value })).map( - callbackfn, - ); - } - put(key: Key, value: Value) { if (this.#storage.has(key)) { this.#storage.delete(key); } if (this.#capacity === this.#storage.size) { - this.#evictLeastRecentItem(); + const victim = this.#storage.keys().next().value; + this.#storage.delete(victim!); } this.#storage.set(key, value); @@ -44,11 +30,6 @@ export class LRUCache return this; } - #evictLeastRecentItem() { - const lruKey = this.#storage.keys().next().value; - this.#storage.delete(lruKey!); - } - get(key: Key): Value | null { if (!this.#storage.has(key)) return null; @@ -67,9 +48,4 @@ export class LRUCache clear() { this.#storage.clear(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/cache/lru/lru-cache/index.spec.ts b/src/data-structures/cache/lru/lru-cache/index.spec.ts index ce6b9e9..25cda6c 100644 --- a/src/data-structures/cache/lru/lru-cache/index.spec.ts +++ b/src/data-structures/cache/lru/lru-cache/index.spec.ts @@ -11,27 +11,7 @@ describe('LRUCache', () => { it('returns initial state correctly', () => { // Act and Assert - expect(cache.toArray()).toEqual([]); - }); - - describe('toArray', () => { - it('returns values of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray()).toEqual([1, 2]); - }); - - it('returns keys of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray(({ key }) => key)).toEqual(['a', 'b']); - }); + expect(cache.size).toBe(0); }); describe('put', () => { @@ -40,7 +20,7 @@ describe('LRUCache', () => { cache.put('one', 1); // Assert - expect(cache.toArray()).toEqual([1]); + expect(cache.size).toBe(1); }); it('fills the cache', () => { @@ -49,7 +29,7 @@ describe('LRUCache', () => { cache.put('two', 2); // Assert - expect(cache.toArray()).toEqual([1, 2]); + expect(cache.size).toBe(2); }); it(`overwrites item's value correctly`, () => { @@ -60,7 +40,7 @@ describe('LRUCache', () => { cache.put('key', 2); // Assert - expect(cache.toArray()).toEqual([2]); + expect(cache.size).toBe(1); }); it('returns the correct size when exceeding capacity', () => { @@ -73,7 +53,7 @@ describe('LRUCache', () => { cache.put('four', 4); // Assert - expect(cache.toArray()).toEqual([2, 3, 4]); + expect(cache.size).toBe(3); }); }); @@ -88,7 +68,7 @@ describe('LRUCache', () => { cache.put('one', 1); // Act and Assert - expect(cache.toArray()).toEqual([1]); + expect(cache.get('one')).toBe(1); }); it('evicts the least recently used item when exceeding capacity', () => { @@ -103,7 +83,7 @@ describe('LRUCache', () => { // Assert expect(cache.get('two')).toBeNull(); - expect(cache.toArray()).toEqual([3, 1, 4]); + expect(cache.size).toBe(3); }); }); @@ -117,15 +97,6 @@ describe('LRUCache', () => { // Act lfu.clear(); - expect(lfu.toArray()).toEqual([]); - }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new LRUCache(6))).toBe( - '[object LRUCache]', - ); - }); + expect(lfu.size).toBe(0); }); }); diff --git a/src/data-structures/cache/lru/lru-cache/index.ts b/src/data-structures/cache/lru/lru-cache/index.ts index b1d83a1..afce7a0 100644 --- a/src/data-structures/cache/lru/lru-cache/index.ts +++ b/src/data-structures/cache/lru/lru-cache/index.ts @@ -12,9 +12,15 @@ export class LRUCache { #keyNodeMap = new Map(); - #head = new DoublyLinkedListNode>({ key: null, value: null }); + #head = new DoublyLinkedListNode>({ + key: null, + value: null, + }); - #tail = new DoublyLinkedListNode>({ key: null, value: null }); + #tail = new DoublyLinkedListNode>({ + key: null, + value: null, + }); constructor(capacity: number) { this.#capacity = capacity; @@ -22,6 +28,10 @@ export class LRUCache { this.#tail.prev = this.#head; } + get size() { + return this.#keyNodeMap.size; + } + get(key: K) { if (!this.#keyNodeMap.has(key)) return null; @@ -44,10 +54,10 @@ export class LRUCache { } if (this.#keyNodeMap.size === this.#capacity) { - const lru = this.#tail.prev!; + const victim = this.#tail.prev!; - this.#delete(lru); - this.#keyNodeMap.delete(lru.data.key); + this.#delete(victim); + this.#keyNodeMap.delete(victim.data.key); } const node = new DoublyLinkedListNode({ key, value: val }); @@ -71,31 +81,9 @@ export class LRUCache { this.#head.next = node; } - toArray( - callback: (data: { key: K; value: V }) => T = (data) => - data.value as unknown as T, - ): T[] { - let current = this.#tail.prev; - const array = []; - - while (current && current !== this.#head) { - // @ts-ignore - array.push(callback(current.data)); - - current = current.prev; - } - - return array; - } - clear() { this.#head.next = this.#tail; this.#tail.prev = this.#head; this.#keyNodeMap.clear(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/cache/mfu/index.spec.ts b/src/data-structures/cache/mfu/index.spec.ts index bbb65f7..fb518b5 100644 --- a/src/data-structures/cache/mfu/index.spec.ts +++ b/src/data-structures/cache/mfu/index.spec.ts @@ -4,230 +4,39 @@ import { MFUCache } from './index'; describe('MFUCache', () => { let cache: MFUCache; - // Arrange beforeEach(() => { cache = new MFUCache(2); }); - it('returns initial state correctly', () => { - // Assert - expect(cache.isEmpty).toBeTruthy(); - expect(cache.toArray()).toEqual([]); - expect(cache.size).toBe(0); + it('returns -1 for a missing key', () => { + expect(cache.get('a')).toBe(-1); }); - describe('toArray', () => { - it('returns values of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray()).toEqual([1, 2]); - }); - - it('returns keys of items', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - - // Act and Assert - expect(cache.toArray(({ key }) => key)).toEqual(['a', 'b']); - }); - }); - - describe('put', () => { - it('adds first item', () => { - // Act - cache.put('a', 1); - // [1] -> 1 - - // Assert - expect(cache.isEmpty).toBeFalsy(); - expect(cache.toArray()).toEqual([1]); - expect(cache.size).toBe(1); - }); - - it('adds second item', () => { - // Arrange - cache.put('a', 1); - - // Act - cache.put('b', 2); - // [1] -> 1, 2 - - // Assert - expect(cache.toArray()).toEqual([1, 2]); - expect(cache.size).toBe(2); - }); - - it('updates value of existing item', () => { - // Arrange - cache.put('a', 1); - - // Act - cache.put('a', 2); - // [2] -> 2 - - // Assert - expect(cache.toArray()).toEqual([2]); - expect(cache.size).toBe(1); - }); - - it('updates first item', () => { - // Arrange - cache.put('a', 1); - cache.put('b', 2); - // - - // Act - cache.put('a', 3); - // [1] -> 2 - // [2] -> 3 - - // Assert - expect(cache.toArray()).toEqual([2, 3]); - expect(cache.size).toBe(2); - }); - - it('updates existing items', () => { - // Arrange - const mfu = new MFUCache(3); - - // Act - mfu.put('a', 1); - mfu.put('b', 2).put('b', 2).put('b', 2); - mfu.put('c', 3).put('c', 3).put('c', 3).put('c', 3).put('c', 3); - // [1] -> 1 - // [3] -> 2 - // [5] -> 3 - - // Assert - expect(mfu.toArray()).toEqual([1, 2, 3]); - expect(mfu.size).toBe(3); - }); - - it('evicts the previous one item before the newest one item when capacity reached', () => { - // Arrange - cache.put('one', 1); - cache.put('two', 2); - // [1] -> 1, 2 - - // Act - cache.put('three', 3); // Should evict 2 - // [1] -> 1, 3 - - // Assert - expect(cache.toArray()).toEqual([1, 3]); - expect(cache.size).toBe(2); - }); - - it('evicts the most frequently used item when capacity reached', () => { - // Arrange - const mfu = new MFUCache(2); - mfu.put('one', 1).put('one', 1); - mfu.put('two', 2).put('two', 2).put('two', 2).put('two', 2); - // [2] -> 1 - // [4] -> 2 - - // Act - mfu.put('three', 3); // Should evict 2 - // [1] -> 3 - // [2] -> 1 - - // Assert - expect(mfu.toArray()).toEqual([3, 1]); - expect(mfu.size).toBe(2); - }); + it('stores and retrieves a value', () => { + cache.put('a', 1); + expect(cache.get('a')).toBe(1); }); - describe('get', () => { - it('returns null for a non-existing item', () => { - // Act and Assert - expect(cache.get('one')).toBeNull(); - }); - - it('returns value of existing item', () => { - // Arrange - cache.put('one', 1); - cache.put('two', 2); - // [1] -> 1, 2 - - // Act and Assert - expect(cache.get('one')).toBe(1); - // [1] -> 2 - // [2] -> 1 - - expect(cache.toArray()).toEqual([2, 1]); - expect(cache.size).toBe(2); - }); - - it('returns the values of the items', () => { - // Arrange - cache.put('one', 1); - cache.put('two', 2); - // [1] -> 1, 2 - - // Act and Assert - expect(cache.get('one')).toBe(1); - expect(cache.get('two')).toBe(2); - // [2] -> 1, 2 - - expect(cache.toArray()).toEqual([1, 2]); - expect(cache.size).toBe(2); - }); - - it('evicts the most frequently used item', () => { - // Arrange - const mfu = new MFUCache(2); - mfu.put('one', 1); - mfu.get('one'); - - mfu.put('two', 1); - mfu.get('two'); - mfu.get('two'); - mfu.get('two'); - // [2] -> 1 - // [4] -> 2 - - // Act - mfu.put('three', 3); // Should evict 2 - // [1] -> 3 - // [2] -> 1 - - // Assert - expect(mfu.toArray()).toEqual([3, 1]); - expect(mfu.size).toBe(2); - }); + it('updates an existing key and increments frequency', () => { + cache.put('a', 1); + cache.put('a', 2); + expect(cache.get('a')).toBe(2); }); - describe('clear', () => { - it('clears cache correctly', () => { - // Arrange - cache.put('one', 1); - cache.put('two', 2); - cache.put('three', 3); - - // Act - cache.clear(); - - // Assert - expect(cache.get('one')).toBeNull(); - expect(cache.get('two')).toBeNull(); - expect(cache.get('three')).toBeNull(); + it('evicts the most frequently used key when capacity is exceeded', () => { + cache.put('a', 1); + cache.put('b', 2); + cache.get('a'); // freq a = 2 + cache.put('c', 3); // evicts a (most frequently used) - expect(cache.isEmpty).toBeTruthy(); - expect(cache.toArray()).toEqual([]); - expect(cache.size).toBe(0); - }); + expect(cache.get('a')).toBe(-1); + expect(cache.get('b')).toBe(2); + expect(cache.get('c')).toBe(3); }); - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new MFUCache(3))).toBe( - '[object MFUCache]', - ); - }); + it('handles capacity of zero', () => { + const zeroCache = new MFUCache(0); + zeroCache.put('a', 1); + expect(zeroCache.get('a')).toBe(-1); }); }); diff --git a/src/data-structures/cache/mfu/index.ts b/src/data-structures/cache/mfu/index.ts index 5d56f60..dda56cb 100644 --- a/src/data-structures/cache/mfu/index.ts +++ b/src/data-structures/cache/mfu/index.ts @@ -1,197 +1,77 @@ -/* eslint-disable max-classes-per-file */ -import type { ICache, Payload } from '@/data-structures/cache/types'; -import { - DoublyLinkedList, - DoublyLinkedListNode, -} from '@/data-structures/linked-lists/doubly-linked-list'; - -export class MFUCache - implements ICache -{ - #capacity: number; +type CacheItem = { + value: V; + freq: number; +}; - #storage = new Storage(); +export class MFUCache { + #capacity: number; + #map: Map>; + #freqKeysMap: Map>; + #maxFreq: number; constructor(capacity: number) { this.#capacity = capacity; + this.#map = new Map(); + this.#freqKeysMap = new Map(); + this.#maxFreq = 0; } - get size(): number { - return this.#storage.size; - } - - get isEmpty() { - return this.#storage.size === 0; - } - - toArray( - callbackfn: (item: Payload) => T = (item) => - item.value as unknown as T, - ): T[] { - const { store } = this.#storage; - - return Object.values(store) - .flatMap((linkedList) => linkedList.toArray()) - .map(callbackfn); - } - - put(key: Key, value: Value) { - if (this.#storage.hasItem(key)) { - this.#storage.removeItem(key); - } + get(key: K): V | -1 { + if (!this.#map.has(key)) return -1; - if (this.#storage.size === this.#capacity) { - this.#storage.evictTheMostFrequentItem(); - } - - this.#storage.setItem(key, value); - - return this; - } + const { value, freq } = this.#map.get(key)!; - get(key: Key) { - if (!this.#storage.hasItem(key)) return null; - - const { value } = this.#storage.getItem(key)!.data; - this.#storage.removeItem(key); - this.#storage.setItem(key, value); + this.#removeFromFreqMap(key, freq); + this.#addToFreqMap(key, freq + 1); + this.#map.set(key, { value, freq: freq + 1 }); return value; } - clear() { - this.#storage.clear(); - } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return 'MFUCache'; - } -} - -interface FrequencyNode extends DoublyLinkedListNode {} -interface Bucket extends DoublyLinkedList> {} - -class Storage { - #keyFrequencyMap = new Map(); - - #frequencyFrequencyNodeMap = new Map(); - - #frequencyNodes = new DoublyLinkedList(); - - #frequencyBucketMap = {} as Record>; - - #keyNodeMap = new Map>>(); - - #INITIAL_FREQUENCY_VALUE = 0; - - #maxFrequency = this.#INITIAL_FREQUENCY_VALUE; - - get store() { - return this.#frequencyBucketMap; - } - - get size() { - return this.#keyNodeMap.size; - } + put(key: K, value: V): void { + if (this.#capacity === 0) return; - setItem(key: Key, value: Value) { - this.#increaseFrequency(key); + if (this.#map.has(key)) { + const { freq } = this.#map.get(key)!; + this.#map.set(key, { value, freq }); + this.get(key); - const frequency = this.#getFrequency(key)!; - if (!this.#frequencyFrequencyNodeMap.has(frequency)) { - this.#addNewFrequencyNode(frequency); - - this.#maxFrequency = this.#frequencyNodes.tail!.data; + return; } - this.#addNewNodeToBucket(key, value); - } - - #increaseFrequency(key: Key) { - const frequency = this.#getFrequency(key) ?? this.#INITIAL_FREQUENCY_VALUE; + if (this.#map.size >= this.#capacity) { + const keysWithMaxFreq = this.#freqKeysMap.get(this.#maxFreq)!; + const victim = keysWithMaxFreq.values().next().value as K; - this.#keyFrequencyMap.set(key, frequency + 1); - } - - #addNewNodeToBucket(key: Key, value: Value) { - const frequency = this.#getFrequency(key)!; - const bucket = this.#getOrCreateNewBucket(frequency); - - bucket.append({ key, value }); - const newNode = bucket.tail!; - - this.#keyNodeMap.set(key, newNode); - this.#frequencyBucketMap[frequency] = bucket; - } - - getItem(key: Key) { - return this.#keyNodeMap.get(key); - } - - #getOrCreateNewBucket(frequency: number) { - return ( - this.#frequencyBucketMap[frequency] ?? - new DoublyLinkedList>() - ); - } - - #addNewFrequencyNode(frequency: number) { - const newNode = - frequency === 1 - ? this.#frequencyNodes.prepend(frequency).head! - : this.#frequencyNodes.append(frequency).tail!; - - this.#frequencyFrequencyNodeMap.set(frequency, newNode); - } + keysWithMaxFreq.delete(victim); + this.#map.delete(victim); + } - hasItem(key: Key) { - return this.#keyNodeMap.has(key); + this.#map.set(key, { value, freq: 1 }); + this.#addToFreqMap(key, 1); + this.#maxFreq = Math.max(this.#maxFreq, 1); } - removeItem(key: Key) { - const frequency = this.#getFrequency(key)!; - const bucket = this.#frequencyBucketMap[frequency]!; - const node = this.#keyNodeMap.get(key)!; - - bucket.deleteByRef(node); - this.#keyNodeMap.delete(key); - - if (bucket.isEmpty) { - const frequencyNode = this.#frequencyFrequencyNodeMap.get(frequency)!; - - this.#frequencyNodes.deleteByRef(frequencyNode); - this.#frequencyFrequencyNodeMap.delete(frequency); - delete this.#frequencyBucketMap[frequency]; + #addToFreqMap(key: K, freq: number): void { + if (!this.#freqKeysMap.has(freq)) { + this.#freqKeysMap.set(freq, new Set()); } - } - #getFrequency(key: Key) { - return this.#keyFrequencyMap.get(key); + this.#freqKeysMap.get(freq)!.add(key); + this.#maxFreq = Math.max(this.#maxFreq, freq); } - evictTheMostFrequentItem() { - const maxFrequency = this.#maxFrequency; - const bucket = this.#frequencyBucketMap[maxFrequency]!; + #removeFromFreqMap(key: K, freq: number): void { + const keys = this.#freqKeysMap.get(freq); + if (!keys) return; - const deletedNode = bucket.removeTail()!.data; + keys.delete(key); + if (keys.size === 0) { + this.#freqKeysMap.delete(freq); - this.#keyNodeMap.delete(deletedNode.key); - this.#keyFrequencyMap.delete(deletedNode.key); - - if (bucket.isEmpty) { - this.#frequencyNodes.removeTail(); - this.#frequencyFrequencyNodeMap.delete(maxFrequency); - delete this.#frequencyBucketMap[maxFrequency]; + if (freq === this.#maxFreq) { + this.#maxFreq = Math.max(...this.#freqKeysMap.keys(), 0); + } } } - - clear() { - this.#keyFrequencyMap.clear(); - this.#frequencyFrequencyNodeMap.clear(); - this.#frequencyNodes.clear(); - this.#frequencyBucketMap = {}; - this.#keyNodeMap.clear(); - this.#maxFrequency = this.#INITIAL_FREQUENCY_VALUE; - } } diff --git a/src/data-structures/cache/types.ts b/src/data-structures/cache/types.ts index 966ea89..28d7e5a 100644 --- a/src/data-structures/cache/types.ts +++ b/src/data-structures/cache/types.ts @@ -5,9 +5,7 @@ export type Payload = { export interface ICache { get size(): number; - get isEmpty(): boolean; - toArray(callbackfn?: (item: Payload) => T): T[]; put(key: Key, value: Value): this; get(key: Key): Value | null; clear(): void; diff --git a/src/data-structures/deque/index.spec.ts b/src/data-structures/deque/index.spec.ts index 3a57f7f..81874f8 100644 --- a/src/data-structures/deque/index.spec.ts +++ b/src/data-structures/deque/index.spec.ts @@ -136,13 +136,4 @@ describe('Deque', () => { expect(deque.peekRear()).toBe(2); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new Deque())).toBe( - '[object Deque]', - ); - }); - }); }); diff --git a/src/data-structures/deque/index.ts b/src/data-structures/deque/index.ts index 7d64145..240011b 100644 --- a/src/data-structures/deque/index.ts +++ b/src/data-structures/deque/index.ts @@ -67,9 +67,4 @@ export class Deque implements IDeque { toString(callback?: Callback) { return this.#list.toString(callback); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/disjoint-set/index.ts b/src/data-structures/disjoint-set/index.ts index 984a3ea..09921a0 100644 --- a/src/data-structures/disjoint-set/index.ts +++ b/src/data-structures/disjoint-set/index.ts @@ -1,4 +1,3 @@ -// Explanation: // - Hello Byte: https://youtu.be/92UpvDXc8fs?si=rEbdXNPEIm_g97NK&t=237 export class DisjointSet { @@ -7,7 +6,7 @@ export class DisjointSet { #rank; constructor(n: number) { - this.#parent = Array.from({ length: n }, (_, i) => i); + this.#parent = Array.from({ length: n }, (_, index) => index); this.#rank = new Uint32Array(n); } @@ -37,7 +36,7 @@ export class DisjointSet { return true; } - connected(a: number, b: number) { - return this.find(a) === this.find(b); + connected(x: number, y: number) { + return this.find(x) === this.find(y); } } diff --git a/src/data-structures/hash-table/index.spec.ts b/src/data-structures/hash-table/index.spec.ts index ca18d3f..49a211b 100644 --- a/src/data-structures/hash-table/index.spec.ts +++ b/src/data-structures/hash-table/index.spec.ts @@ -104,13 +104,4 @@ describe('HashTable', () => { expect(hashTable.size).toBe(0); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new HashTable())).toBe( - '[object HashTable]', - ); - }); - }); }); diff --git a/src/data-structures/hash-table/index.ts b/src/data-structures/hash-table/index.ts index 224a097..59f9f1f 100644 --- a/src/data-structures/hash-table/index.ts +++ b/src/data-structures/hash-table/index.ts @@ -105,11 +105,6 @@ export class HashTable this.#buckets = createBuckets<{ key: Key; value: Value }>(this.#capacity); this.#size = 0; } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return 'HashTable'; - } } function getHash(key: T, length: number): number { diff --git a/src/data-structures/heap/heap.ts b/src/data-structures/heap/heap.ts index d6c3584..1cedead 100644 --- a/src/data-structures/heap/heap.ts +++ b/src/data-structures/heap/heap.ts @@ -172,9 +172,4 @@ export class Heap implements IHeap { toString() { return this.toArray().toString(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/heap/max-heap/index.spec.ts b/src/data-structures/heap/max-heap/index.spec.ts index 3bfc7f7..95f5e8f 100644 --- a/src/data-structures/heap/max-heap/index.spec.ts +++ b/src/data-structures/heap/max-heap/index.spec.ts @@ -16,38 +16,16 @@ describe('MaxHeap', () => { describe('insert', () => { it('adds items to the heap', () => { // Arrange - const maxHeap = MaxHeap.of(1); + const minHeap = new MaxHeap(); // Act - maxHeap.insert(10); - // Assert - expect(maxHeap.toArray()).toEqual([10, 1]); - expect(maxHeap.size).toBe(2); - expect(maxHeap.isEmpty).toBeFalsy(); - - // Act - maxHeap.insert(3); - // Assert - expect(maxHeap.toArray()).toEqual([10, 1, 3]); - expect(maxHeap.peek()).toBe(10); - - // Act - maxHeap.insert(5); - // Assert - expect(maxHeap.toArray()).toEqual([10, 5, 3, 1]); - expect(maxHeap.peek()).toBe(10); - - // Act - maxHeap.insert(8); - // Assert - expect(maxHeap.toArray()).toEqual([10, 8, 3, 1, 5]); - expect(maxHeap.peek()).toBe(10); + minHeap.insert(3).insert(1).insert(10).insert(5); - // // Act - maxHeap.insert(20); // Assert - expect(maxHeap.toArray()).toEqual([20, 8, 10, 1, 5, 3]); - expect(maxHeap.peek()).toBe(20); + expect(minHeap.peek()).toBe(10); + expect(minHeap.size).toBe(4); + expect(minHeap.isEmpty).toBeFalsy(); + expect(Array.from(minHeap)).toEqual([10, 5, 3, 1]); }); }); @@ -82,31 +60,12 @@ describe('MaxHeap', () => { it('removes the top element from the heap', () => { // Arrange - const maxHeap = MaxHeap.of(1).insert(5).insert(3).insert(8).insert(12); - - // Act and Assert - expect(maxHeap.poll()).toBe(12); - expect(maxHeap.toArray()).toEqual([8, 5, 3, 1]); - expect(maxHeap.size).toBe(4); - - // Act and Assert - expect(maxHeap.poll()).toBe(8); - expect(maxHeap.toArray()).toEqual([5, 1, 3]); - expect(maxHeap.size).toBe(3); - - // Act and Assert - expect(maxHeap.poll()).toBe(5); - expect(maxHeap.toArray()).toEqual([3, 1]); - expect(maxHeap.size).toBe(2); - - // Act and Assert - expect(maxHeap.poll()).toBe(3); - expect(maxHeap.toArray()).toEqual([1]); - expect(maxHeap.size).toBe(1); + const minHeap = MaxHeap.of(1).insert(5).insert(3).insert(8).insert(2); // Act and Assert - expect(maxHeap.poll()).toBe(1); - expect(maxHeap.isEmpty).toBeTruthy(); + expect(minHeap.poll()).toBe(8); + expect(minHeap.size).toBe(4); + expect(Array.from(minHeap)).toEqual([5, 3, 2, 1]); }); }); @@ -124,38 +83,46 @@ describe('MaxHeap', () => { // Assert expect(removedValue).toBeNull(); - expect(maxHeap.toArray()).toEqual([12, 8, 7, 3, 5]); expect(maxHeap.size).toBe(5); + expect(Array.from(maxHeap)).toEqual([12, 8, 7, 5, 3]); }); - it('deletes the last element', () => { + it('removes the last element', () => { // Act const deletedElement = maxHeap.remove((x) => x === 5); // Assert expect(deletedElement).toBe(5); - expect(maxHeap.toArray()).toEqual([12, 8, 7, 3]); expect(maxHeap.size).toBe(4); + expect(Array.from(maxHeap)).toEqual([12, 8, 7, 3]); }); - it('deletes element', () => { + it('removes an arbitrary element', () => { // Act const removedValue = maxHeap.remove((x) => x === 8); // Assert expect(removedValue).toBe(8); - expect(maxHeap.toArray()).toEqual([12, 5, 7, 3]); expect(maxHeap.size).toBe(4); + expect(Array.from(maxHeap)).toEqual([12, 7, 5, 3]); }); - it('deletes the top element', () => { + it('removes the top element', () => { // Act const deletedElement = maxHeap.remove((x) => x === 12); // Assert expect(deletedElement).toBe(12); expect(maxHeap.peek()).toBe(8); - expect(maxHeap.toArray()).toEqual([8, 5, 7, 3]); + expect(Array.from(maxHeap)).toEqual([8, 7, 5, 3]); + }); + }); + + describe('Symbol.Iterator', () => { + it('should yield elements in descending order', () => { + const maxHeap = MaxHeap.fromArray([1, 2, 5, 8]); + + expect(Array.from(maxHeap)).toEqual([8, 5, 2, 1]); }); }); @@ -171,12 +138,4 @@ describe('MaxHeap', () => { expect(maxHeap.isEmpty).toBeTruthy(); }); }); - - describe('Symbol.Iterator', () => { - it('should yield elements in descending order', () => { - const maxHeap = MaxHeap.fromArray([1, 2, 5, 8]); - - expect(Array.from(maxHeap)).toEqual([8, 5, 2, 1]); - }); - }); }); diff --git a/src/data-structures/heap/max-heap/index.ts b/src/data-structures/heap/max-heap/index.ts index d6a8230..e9efcec 100644 --- a/src/data-structures/heap/max-heap/index.ts +++ b/src/data-structures/heap/max-heap/index.ts @@ -74,9 +74,4 @@ export class MaxHeap { toString() { return this.#items.toString(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/heap/min-heap/index.spec.ts b/src/data-structures/heap/min-heap/index.spec.ts index ed92ea8..0bfcf5f 100644 --- a/src/data-structures/heap/min-heap/index.spec.ts +++ b/src/data-structures/heap/min-heap/index.spec.ts @@ -20,41 +20,13 @@ describe('MinHeap', () => { const minHeap = new MinHeap(); // Act - minHeap.insert(10).insert(1); + minHeap.insert(10).insert(1).insert(3).insert(5); // Assert - expect(minHeap.toArray()).toEqual([1, 10]); expect(minHeap.peek()).toBe(1); - expect(minHeap.size).toBe(2); + expect(minHeap.size).toBe(4); expect(minHeap.isEmpty).toBeFalsy(); - - // Act; - minHeap.insert(3); - - // Assert - expect(minHeap.toArray()).toEqual([1, 10, 3]); - expect(minHeap.peek()).toBe(1); - - // Act - minHeap.insert(5); - - // Assert - expect(minHeap.toArray()).toEqual([1, 5, 3, 10]); - expect(minHeap.peek()).toBe(1); - - // Act - minHeap.insert(8); - - // Assert - expect(minHeap.toArray()).toEqual([1, 5, 3, 10, 8]); - expect(minHeap.peek()).toBe(1); - - // Act - minHeap.insert(20); - - // Assert - expect(minHeap.toArray()).toEqual([1, 5, 3, 10, 8, 20]); - expect(minHeap.peek()).toBe(1); + expect(Array.from(minHeap)).toEqual([1, 3, 5, 10]); }); }); @@ -64,9 +36,9 @@ describe('MinHeap', () => { const minHeap = MinHeap.of(1); // Assert - expect(minHeap.toArray()).toEqual([1]); expect(minHeap.size).toBe(1); expect(minHeap.isEmpty).toBeFalsy(); + expect(Array.from(minHeap)).toEqual([1]); }); }); @@ -92,31 +64,10 @@ describe('MinHeap', () => { // Arrange const minHeap = MinHeap.of(1).insert(5).insert(3).insert(8).insert(2); - expect(minHeap.toArray()).toEqual([1, 2, 3, 8, 5]); - // Act and Assert expect(minHeap.poll()).toBe(1); - expect(minHeap.toArray()).toEqual([2, 5, 3, 8]); expect(minHeap.size).toBe(4); - - // Act and Assert - expect(minHeap.poll()).toBe(2); - expect(minHeap.toArray()).toEqual([3, 5, 8]); - expect(minHeap.size).toBe(3); - - // Act and Assert - expect(minHeap.poll()).toBe(3); - expect(minHeap.toArray()).toEqual([5, 8]); - expect(minHeap.size).toBe(2); - - // Act and Assert - expect(minHeap.poll()).toBe(5); - expect(minHeap.toArray()).toEqual([8]); - expect(minHeap.size).toBe(1); - - // Act and Assert - expect(minHeap.poll()).toBe(8); - expect(minHeap.isEmpty).toBeTruthy(); + expect(Array.from(minHeap)).toEqual([2, 3, 5, 8]); }); }); @@ -134,8 +85,8 @@ describe('MinHeap', () => { // Assert expect(removedValue).toBeNull(); - expect(minHeap.toArray()).toEqual([3, 5, 8, 12, 7]); expect(minHeap.size).toBe(5); + expect(Array.from(minHeap)).toEqual([3, 5, 7, 8, 12]); }); it('removes the last element', () => { @@ -144,25 +95,25 @@ describe('MinHeap', () => { // Assert expect(removedElement).toBe(7); - expect(minHeap.toArray()).toEqual([3, 5, 8, 12]); expect(minHeap.size).toBe(4); + expect(Array.from(minHeap)).toEqual([3, 5, 8, 12]); }); - it('removes element', () => { + it('removes an arbitrary element', () => { // Act const deletedValue = minHeap.remove((x) => x === 5); // Assert expect(deletedValue).toBe(5); - expect(minHeap.toArray()).toEqual([3, 7, 8, 12]); expect(minHeap.size).toBe(4); + expect(Array.from(minHeap)).toEqual([3, 7, 8, 12]); }); it('removes the top element', () => { // Act and Assert expect(minHeap.remove((x) => x === 3)).toBe(3); expect(minHeap.peek()).toBe(5); - expect(minHeap.toArray()).toEqual([5, 7, 8, 12]); + expect(Array.from(minHeap)).toEqual([5, 7, 8, 12]); }); }); @@ -184,6 +135,8 @@ describe('MinHeap', () => { // Assert expect(minHeap.isEmpty).toBeTruthy(); + expect(minHeap.size).toBe(0); + expect(minHeap.toArray()).toEqual([]); }); }); }); diff --git a/src/data-structures/heap/min-heap/index.ts b/src/data-structures/heap/min-heap/index.ts index e43564a..e9a3d16 100644 --- a/src/data-structures/heap/min-heap/index.ts +++ b/src/data-structures/heap/min-heap/index.ts @@ -74,9 +74,4 @@ export class MinHeap { toString() { return this.#items.toString(); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/linked-lists/doubly-linked-list/index.spec.ts b/src/data-structures/linked-lists/doubly-linked-list/index.spec.ts index 5e2fdb7..59fb9f2 100644 --- a/src/data-structures/linked-lists/doubly-linked-list/index.spec.ts +++ b/src/data-structures/linked-lists/doubly-linked-list/index.spec.ts @@ -557,13 +557,4 @@ describe('DoublyLinkedList', () => { expect(list.size).toBe(3); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new DoublyLinkedList())).toBe( - '[object DoublyLinkedList]', - ); - }); - }); }); diff --git a/src/data-structures/linked-lists/doubly-linked-list/index.ts b/src/data-structures/linked-lists/doubly-linked-list/index.ts index d789b0a..d94d21b 100644 --- a/src/data-structures/linked-lists/doubly-linked-list/index.ts +++ b/src/data-structures/linked-lists/doubly-linked-list/index.ts @@ -205,8 +205,4 @@ export class DoublyLinkedList extends AbstractLinkedList< return this; } - - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/linked-lists/linked-list/index.spec.ts b/src/data-structures/linked-lists/linked-list/index.spec.ts index e1f9fdc..4f2c088 100644 --- a/src/data-structures/linked-lists/linked-list/index.spec.ts +++ b/src/data-structures/linked-lists/linked-list/index.spec.ts @@ -631,13 +631,4 @@ describe('LinkedList', () => { expect(list.isEmpty).toBeTruthy(); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new LinkedList())).toBe( - '[object LinkedList]', - ); - }); - }); }); diff --git a/src/data-structures/linked-lists/linked-list/index.ts b/src/data-structures/linked-lists/linked-list/index.ts index 5f87295..81f374a 100644 --- a/src/data-structures/linked-lists/linked-list/index.ts +++ b/src/data-structures/linked-lists/linked-list/index.ts @@ -173,8 +173,4 @@ export class LinkedList extends AbstractLinkedList { return this; } - - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/priority-queue/index.spec.ts b/src/data-structures/priority-queue/index.spec.ts index ad20c80..2542ae2 100644 --- a/src/data-structures/priority-queue/index.spec.ts +++ b/src/data-structures/priority-queue/index.spec.ts @@ -96,31 +96,10 @@ describe('PriorityQueue', () => { .enqueue(8) .enqueue(2); - expect(pq.toArray()).toEqual([1, 2, 3, 8, 5]); - // Act and Assert expect(pq.dequeue()).toBe(1); - expect(pq.toArray()).toEqual([2, 5, 3, 8]); expect(pq.size).toBe(4); - - // Act and Assert - expect(pq.dequeue()).toBe(2); - expect(pq.toArray()).toEqual([3, 5, 8]); - expect(pq.size).toBe(3); - - // Act and Assert - expect(pq.dequeue()).toBe(3); - expect(pq.toArray()).toEqual([5, 8]); - expect(pq.size).toBe(2); - - // Act and Assert - expect(pq.dequeue()).toBe(5); - expect(pq.toArray()).toEqual([8]); - expect(pq.size).toBe(1); - - // Act and Assert - expect(pq.dequeue()).toBe(8); - expect(pq.isEmpty).toBeTruthy(); + expect(Array.from(pq)).toEqual([2, 3, 5, 8]); }); }); @@ -142,8 +121,8 @@ describe('PriorityQueue', () => { // Assert expect(removedValue).toBeNull(); - expect(minHeap.toArray()).toEqual([3, 5, 8, 12, 7]); expect(minHeap.size).toBe(5); + expect(Array.from(minHeap)).toEqual([3, 5, 7, 8, 12]); }); it('removes the last element', () => { @@ -152,8 +131,8 @@ describe('PriorityQueue', () => { // Assert expect(removedElement).toBe(7); - expect(minHeap.toArray()).toEqual([3, 5, 8, 12]); expect(minHeap.size).toBe(4); + expect(Array.from(minHeap)).toEqual([3, 5, 8, 12]); }); it('removes element', () => { @@ -162,15 +141,15 @@ describe('PriorityQueue', () => { // Assert expect(deletedValue).toBe(5); - expect(minHeap.toArray()).toEqual([3, 7, 8, 12]); expect(minHeap.size).toBe(4); + expect(Array.from(minHeap)).toEqual([3, 7, 8, 12]); }); it('removes the top element', () => { // Act and Assert expect(minHeap.remove((x) => x === 3)).toBe(3); expect(minHeap.front()).toBe(5); - expect(minHeap.toArray()).toEqual([5, 7, 8, 12]); + expect(Array.from(minHeap)).toEqual([5, 7, 8, 12]); }); }); diff --git a/src/data-structures/promise/index.ts b/src/data-structures/promise/index.ts index 45828cb..e909b42 100644 --- a/src/data-structures/promise/index.ts +++ b/src/data-structures/promise/index.ts @@ -1,5 +1,6 @@ // Promises/A+ spec // https://github.com/promises-aplus/promises-spec +import isFunction from 'lodash.isfunction'; import { ValueOf } from 'type-fest'; /** @@ -259,7 +260,7 @@ export class MyPromise implements IMyPromise { ); } - if (!isCallable(executor)) { + if (!isFunction(executor)) { throw new TypeError( `${this.constructor.name} resolver ${typeof executor} is not a function`, ); @@ -323,7 +324,7 @@ export class MyPromise implements IMyPromise { try { const thenAction = (resolution as PromiseLike).then; - if (!isCallable(thenAction)) { + if (!isFunction(thenAction)) { fulfillPromise(resolution); return; @@ -357,8 +358,8 @@ export class MyPromise implements IMyPromise { const promiseCapability = newPromiseCapability( MyPromise, ); - const onFulfilledCallback = !isCallable(onfulfilled) ? null : onfulfilled; - const onRejectedCallback = !isCallable(onrejected) ? null : onrejected; + const onFulfilledCallback = !isFunction(onfulfilled) ? null : onfulfilled; + const onRejectedCallback = !isFunction(onrejected) ? null : onrejected; const fulfillReaction: FulfillPromiseReaction = { capability: promiseCapability, @@ -397,14 +398,14 @@ export class MyPromise implements IMyPromise { finally(onfinally?: (() => void) | undefined | null) { return this.then( (value) => { - if (isCallable(onfinally)) { + if (isFunction(onfinally)) { onfinally(); } return value; }, (reason) => { - if (isCallable(onfinally)) { + if (isFunction(onfinally)) { onfinally(); } @@ -505,15 +506,11 @@ function newPromiseCapability(C: typeof MyPromise) { } function isThenable(it: any): it is PromiseLike { - return isObject(it) && isCallable(it.then); + return isObject(it) && isFunction(it.then); } function isObject(it: unknown) { - return type(it) === 'object' ? it !== null : isCallable(it); -} - -function isCallable(it: unknown): it is (...args: any[]) => any { - return type(it) === 'function'; + return type(it) === 'object' ? it !== null : isFunction(it); } function type(it: unknown) { diff --git a/src/data-structures/queue/index.spec.ts b/src/data-structures/queue/index.spec.ts index 6f7ee76..3386300 100644 --- a/src/data-structures/queue/index.spec.ts +++ b/src/data-structures/queue/index.spec.ts @@ -156,13 +156,4 @@ describe('Queue', () => { expect(queue.size).toBe(0); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new Queue())).toBe( - '[object Queue]', - ); - }); - }); }); diff --git a/src/data-structures/queue/index.ts b/src/data-structures/queue/index.ts index 2b470f4..ae7ef13 100644 --- a/src/data-structures/queue/index.ts +++ b/src/data-structures/queue/index.ts @@ -65,9 +65,4 @@ export class Queue implements IQueue { toString(callback?: Callback) { return this.#list.toString(callback); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/data-structures/stack/index.spec.ts b/src/data-structures/stack/index.spec.ts index 59100a5..164379f 100644 --- a/src/data-structures/stack/index.spec.ts +++ b/src/data-structures/stack/index.spec.ts @@ -113,13 +113,4 @@ describe('Stack', () => { expect(stack.size).toBe(0); }); }); - - describe('toStringTag', () => { - it('returns correct string representation', () => { - // Assert - expect(Object.prototype.toString.call(new Stack())).toBe( - '[object Stack]', - ); - }); - }); }); diff --git a/src/data-structures/stack/index.ts b/src/data-structures/stack/index.ts index 30b51b1..01a8b09 100644 --- a/src/data-structures/stack/index.ts +++ b/src/data-structures/stack/index.ts @@ -56,9 +56,4 @@ export class Stack implements IStack { toString(callback?: Callback) { return this.#list.toString(callback); } - - // eslint-disable-next-line class-methods-use-this - get [Symbol.toStringTag]() { - return `${this.constructor.name}`; - } } diff --git a/src/shared/abstract-linked-list/abstract-linked-list.ts b/src/shared/abstract-linked-list/abstract-linked-list.ts index 3164323..04c504a 100644 --- a/src/shared/abstract-linked-list/abstract-linked-list.ts +++ b/src/shared/abstract-linked-list/abstract-linked-list.ts @@ -1,6 +1,7 @@ import { Comparator, CompareFn } from '@/shared/comparator'; import { type Callback } from '@/shared/node'; import { Nullable } from '@/shared/types'; +import isFunction from 'lodash.isfunction'; import { ListNode } from './list-node'; export { ListNode }; @@ -161,7 +162,3 @@ export abstract class AbstractLinkedList< abstract removeTail(): Nullable; abstract reverse(): this; } - -function isFunction(it: unknown) { - return typeof it === 'function'; -} From 784c40e89b35c3d7cddba53332a4a027c2f68eaa Mon Sep 17 00:00:00 2001 From: Maxim Alyoshin Date: Sun, 3 May 2026 22:00:42 +0300 Subject: [PATCH 2/2] refactor(hash-table): rewrite main logic --- .eslintrc.cjs | 1 + src/data-structures/hash-table/index.ts | 117 +++++++++++++----------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1bc696c..73120e4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { }, }, rules: { + 'no-bitwise': 0, 'prefer-const': 0, 'no-param-reassign': 0, 'no-restricted-syntax': 0, diff --git a/src/data-structures/hash-table/index.ts b/src/data-structures/hash-table/index.ts index 59f9f1f..83e7a2c 100644 --- a/src/data-structures/hash-table/index.ts +++ b/src/data-structures/hash-table/index.ts @@ -12,61 +12,24 @@ export class HashTable implements IHashTable { #INITIAL_CAPACITY = 5; - - #RESIZE_THRESHOLD = 0.7; + #RESIZE_THRESHOLD = 0.75; #capacity = this.#INITIAL_CAPACITY; - #buckets = createBuckets<{ - key: Key; - value: Value; - }>(this.#capacity); - + #buckets = createBuckets<{ key: Key; value: Value }>(this.#capacity); #size = 0; get size() { return this.#size; } - #checkResize() { - const loadFactor = this.#size / this.#buckets.length; - if (loadFactor < this.#RESIZE_THRESHOLD) return; - - this.#resize(); - } - - #resize() { - const newCapacity = this.#capacity * 2; - const newBuckets = createBuckets<{ key: Key; value: Value }>(newCapacity); - - // Rehash existing key-value pairs into the new buckets - for (const bucket of this.#buckets) { - for (const node of bucket) { - const { data } = node; - - const index = getHash(data.key as Key, newCapacity); - - newBuckets[index].append(data); - } - } - - this.#buckets = newBuckets; - this.#capacity = newCapacity; - } - - #getBucket(key: Key) { - return this.#buckets[getHash(key, this.#capacity)]; - } - set(key: Key, value: Value): this { this.#checkResize(); - const bucket = this.#getBucket(key); - const node = bucket.find((pair) => pair.key === key); + const { bucket, node } = this.#resolve(key); if (!node) { bucket.append({ key, value }); - this.#size += 1; } else { node.data.value = value; @@ -75,29 +38,24 @@ export class HashTable return this; } - #findNode(key: Key) { - return this.#getBucket(key).find((pair) => pair.key === key); - } - get(key: Key): Value | undefined { - return this.#findNode(key)?.data.value as Value | undefined; + return this.#resolve(key).node?.data.value; } has(key: Key): boolean { - return Boolean(this.#findNode(key)); + return Boolean(this.#resolve(key).node); } delete(key: Key): boolean { const bucket = this.#getBucket(key); + const deletedNode = bucket.removeByValue((pair) => pair.key === key); - if (deletedNode) { - this.#size -= 1; + if (!deletedNode) return false; - return true; - } + this.#size -= 1; - return false; + return true; } clear(): void { @@ -105,13 +63,62 @@ export class HashTable this.#buckets = createBuckets<{ key: Key; value: Value }>(this.#capacity); this.#size = 0; } + + get [Symbol.toStringTag]() { + return 'HashTable'; + } + + // ----------------- internals ----------------- + + #resolve(key: Key) { + const bucket = this.#getBucket(key); + const node = bucket.find((pair) => pair.key === key); + + return { bucket, node }; + } + + #getBucket(key: Key) { + return this.#buckets[getHash(key, this.#capacity)]; + } + + #checkResize() { + const loadFactor = this.#size / this.#capacity; + + if (loadFactor < this.#RESIZE_THRESHOLD) return; + + this.#resize(); + } + + #resize() { + const oldBuckets = this.#buckets; + + const newCapacity = this.#capacity * 2; + const newBuckets = createBuckets<{ key: Key; value: Value }>(newCapacity); + + for (const bucket of oldBuckets) { + for (const node of bucket) { + const { key, value } = node.data; + + const index = getHash(key, newCapacity); + newBuckets[index].append({ key, value }); + } + } + + this.#buckets = newBuckets; + this.#capacity = newCapacity; + } } +// ----------------- helpers ----------------- + function getHash(key: T, length: number): number { - const hash = Array.from(String(key)).reduce( - (acc, char) => acc + char.charCodeAt(0), - 0, - ); + let hash = 0; + + const str = String(key); + + for (let i = 0; i < str.length; i += 1) { + hash = (hash * 31 + str.charCodeAt(i)) >>> 0; + } return hash % length; }