diff --git a/CHANGELOG.md b/CHANGELOG.md index 921159b..5837ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. - [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - [`auto-changelog`](https://github.com/CookPete/auto-changelog) -## [v1.54.3](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming) +## [v2.0.0](https://github.com/zrwusa/data-structure-typed/compare/v1.51.5...main) (upcoming) ### Changes diff --git a/package-lock.json b/package-lock.json index 562f4dc..73ebc20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "data-structure-typed", - "version": "1.54.3", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "data-structure-typed", - "version": "1.54.3", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@eslint/compat": "^1.2.2", diff --git a/src/data-structures/hash/hash-map.ts b/src/data-structures/hash/hash-map.ts index 66334d0..08b70ce 100644 --- a/src/data-structures/hash/hash-map.ts +++ b/src/data-structures/hash/hash-map.ts @@ -21,6 +21,52 @@ import { isWeakKey, rangeCheck } from '../../utils'; * 3. Unique Keys: Keys are unique. * If you try to insert another entry with the same key, the new one will replace the old entry. * 4. Unordered Collection: HashMap does not guarantee the order of entries, and the order may change over time. + * @example + * // should maintain insertion order + * const linkedHashMap = new LinkedHashMap(); + * linkedHashMap.set(1, 'A'); + * linkedHashMap.set(2, 'B'); + * linkedHashMap.set(3, 'C'); + * + * const result = Array.from(linkedHashMap); + * console.log(result); // [ + * // [1, 'A'], + * // [2, 'B'], + * // [3, 'C'] + * // ] + * @example + * // fast lookup of values by key + * const hashMap = new HashMap(); + * hashMap.set(1, 'A'); + * hashMap.set(2, 'B'); + * hashMap.set(3, 'C'); + * + * console.log(hashMap.get(1)); // 'A' + * console.log(hashMap.get(2)); // 'B' + * console.log(hashMap.get(3)); // 'C' + * console.log(hashMap.get(99)); // undefined + * @example + * // remove duplicates when adding multiple entries + * const hashMap = new HashMap(); + * hashMap.set(1, 'A'); + * hashMap.set(2, 'B'); + * hashMap.set(1, 'C'); // Update value for key 1 + * + * console.log(hashMap.size); // 2 + * console.log(hashMap.get(1)); // 'C' + * console.log(hashMap.get(2)); // 'B' + * @example + * // count occurrences of keys + * const data = [1, 2, 1, 3, 2, 1]; + * + * const countMap = new HashMap(); + * for (const key of data) { + * countMap.set(key, (countMap.get(key) || 0) + 1); + * } + * + * console.log(countMap.get(1)); // 3 + * console.log(countMap.get(2)); // 2 + * console.log(countMap.get(3)); // 1 */ export class HashMap extends IterableEntryBase { /** diff --git a/src/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts index 4b1da3c..da012cf 100644 --- a/src/data-structures/queue/queue.ts +++ b/src/data-structures/queue/queue.ts @@ -15,6 +15,53 @@ import { LinearBase } from '../base/linear-base'; * 5. Data Buffering: Acting as a buffer for data packets in network communication. * 6. Breadth-First Search (BFS): In traversal algorithms for graphs and trees, queues store elements that are to be visited. * 7. Real-time Queuing: Like queuing systems in banks or supermarkets. + * @example + * // Sliding Window using Queue + * const nums = [2, 3, 4, 1, 5]; + * const k = 2; + * const queue = new Queue(); + * + * let maxSum = 0; + * let currentSum = 0; + * + * nums.forEach((num, i) => { + * queue.push(num); + * currentSum += num; + * + * if (queue.length > k) { + * currentSum -= queue.shift()!; + * } + * + * if (queue.length === k) { + * maxSum = Math.max(maxSum, currentSum); + * } + * }); + * + * console.log(maxSum); // 7 + * @example + * // Breadth-First Search (BFS) using Queue + * const graph: { [key in number]: number[] } = { + * 1: [2, 3], + * 2: [4, 5], + * 3: [], + * 4: [], + * 5: [] + * }; + * + * const queue = new Queue(); + * const visited: number[] = []; + * + * queue.push(1); + * + * while (!queue.isEmpty()) { + * const node = queue.shift()!; + * if (!visited.includes(node)) { + * visited.push(node); + * graph[node].forEach(neighbor => queue.push(neighbor)); + * } + * } + * + * console.log(visited); // [1, 2, 3, 4, 5] */ export class Queue extends LinearBase { constructor(elements: Iterable | Iterable = [], options?: QueueOptions) { diff --git a/test/unit/data-structures/binary-tree/avl-tree.test.ts b/test/unit/data-structures/binary-tree/avl-tree.test.ts index ac3477c..7e3dfab 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -1,4 +1,4 @@ -import { AVLTree, AVLTreeNode, BinaryTreeNode, BSTNode, Range } from '../../../../src'; +import { AVLTree, AVLTreeNode, BinaryTreeNode, BSTNode } from '../../../../src'; describe('AVL Tree Test', () => { it('should perform various operations on a AVL Tree', () => { diff --git a/test/unit/data-structures/binary-tree/red-black-tree.test.ts b/test/unit/data-structures/binary-tree/red-black-tree.test.ts index 43a12ca..c1bbf5c 100644 --- a/test/unit/data-structures/binary-tree/red-black-tree.test.ts +++ b/test/unit/data-structures/binary-tree/red-black-tree.test.ts @@ -1,4 +1,4 @@ -import { BinaryTreeNode, BSTNode, Range, RedBlackTree, RedBlackTreeNode } from '../../../../src'; +import { BinaryTreeNode, BSTNode, RedBlackTree, RedBlackTreeNode } from '../../../../src'; import { getRandomInt, getRandomIntArray, magnitude } from '../../../utils'; import { OrderedMap } from 'js-sdsl'; diff --git a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts index 394504c..08ba951 100644 --- a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts @@ -1,4 +1,4 @@ -import { BinaryTreeNode, BSTNode, Range, TreeMultiMap, TreeMultiMapNode } from '../../../../src'; +import { BinaryTreeNode, BSTNode, TreeMultiMap, TreeMultiMapNode } from '../../../../src'; import { getRandomInt } from '../../../utils'; import { isDebugTest } from '../../../config'; diff --git a/test/unit/data-structures/hash/hash-map.test.ts b/test/unit/data-structures/hash/hash-map.test.ts index 510edb9..dd04762 100644 --- a/test/unit/data-structures/hash/hash-map.test.ts +++ b/test/unit/data-structures/hash/hash-map.test.ts @@ -842,3 +842,138 @@ describe('LinkedHashMap', () => { }); }); }); + +describe('classic uses', () => { + it('@example should maintain insertion order', () => { + const linkedHashMap = new LinkedHashMap(); + linkedHashMap.set(1, 'A'); + linkedHashMap.set(2, 'B'); + linkedHashMap.set(3, 'C'); + + const result = Array.from(linkedHashMap); + expect(result).toEqual([ + [1, 'A'], + [2, 'B'], + [3, 'C'] + ]); + }); + + it('should allow reverse iteration', () => { + const linkedHashMap = new LinkedHashMap(); + linkedHashMap.set(1, 'A'); + linkedHashMap.set(2, 'B'); + linkedHashMap.set(3, 'C'); + + const result = Array.from(linkedHashMap.reverseBegin()); + expect(result).toEqual([ + [3, 'C'], + [2, 'B'], + [1, 'A'] + ]); + }); + + it('should allow fast deletion at an index', () => { + const linkedHashMap = new LinkedHashMap(); + linkedHashMap.set(1, 'A'); + linkedHashMap.set(2, 'B'); + linkedHashMap.set(3, 'C'); + + linkedHashMap.deleteAt(1); + + const result = Array.from(linkedHashMap); + expect(result).toEqual([ + [1, 'A'], + [3, 'C'] + ]); + }); + + it('should filter entries correctly', () => { + const linkedHashMap = new LinkedHashMap(); + linkedHashMap.set(1, 'A'); + linkedHashMap.set(2, 'B'); + linkedHashMap.set(3, 'C'); + + const filteredMap = linkedHashMap.filter((key, value) => value !== 'B'); + + const result = Array.from(filteredMap); + expect(result).toEqual([ + [1, 'A'], + [3, 'C'] + ]); + }); + + it('should map entries to a new LinkedHashMap', () => { + const linkedHashMap = new LinkedHashMap(); + linkedHashMap.set(1, 'A'); + linkedHashMap.set(2, 'B'); + + const mappedMap = linkedHashMap.map((key, value) => [value, key]); + + const result = Array.from(mappedMap); + expect(result).toEqual([ + ['A', 1], + ['B', 2] + ]); + }); +}); + +describe('classic uses', () => { + it('@example fast lookup of values by key', () => { + const hashMap = new HashMap(); + hashMap.set(1, 'A'); + hashMap.set(2, 'B'); + hashMap.set(3, 'C'); + + expect(hashMap.get(1)).toBe('A'); + expect(hashMap.get(2)).toBe('B'); + expect(hashMap.get(3)).toBe('C'); + expect(hashMap.get(99)).toBeUndefined(); // Key not present + }); + + it('@example remove duplicates when adding multiple entries', () => { + const hashMap = new HashMap(); + hashMap.set(1, 'A'); + hashMap.set(2, 'B'); + hashMap.set(1, 'C'); // Update value for key 1 + + expect(hashMap.size).toBe(2); + expect(hashMap.get(1)).toBe('C'); + expect(hashMap.get(2)).toBe('B'); + }); + + it('@example count occurrences of keys', () => { + const data = [1, 2, 1, 3, 2, 1]; + + const countMap = new HashMap(); + for (const key of data) { + countMap.set(key, (countMap.get(key) || 0) + 1); + } + + expect(countMap.get(1)).toBe(3); + expect(countMap.get(2)).toBe(2); + expect(countMap.get(3)).toBe(1); + }); + + it('should group entries by a key-derived property', () => { + const entries = [ + { id: 1, group: 'A' }, + { id: 2, group: 'B' }, + { id: 3, group: 'A' }, + { id: 4, group: 'B' } + ]; + + const groupedMap = new HashMap(); + + for (const entry of entries) { + const group = entry.group; + const id = entry.id; + if (!groupedMap.has(group)) { + groupedMap.set(group, []); + } + groupedMap.get(group)?.push(id); + } + + expect(groupedMap.get('A')).toEqual([1, 3]); + expect(groupedMap.get('B')).toEqual([2, 4]); + }); +}); diff --git a/test/unit/data-structures/queue/queue.test.ts b/test/unit/data-structures/queue/queue.test.ts index c02e687..7c7df34 100644 --- a/test/unit/data-structures/queue/queue.test.ts +++ b/test/unit/data-structures/queue/queue.test.ts @@ -454,3 +454,217 @@ describe('LinkedListQueue', () => { expect(cloned.length).toBe(2); }); }); + +describe('Queue', () => { + // Test queue initialization + it('should initialize correctly with no elements', () => { + const queue = new Queue(); + expect(queue.isEmpty()).toBe(true); + expect(queue.length).toBe(0); + expect(queue.first).toBeUndefined(); + expect(queue.last).toBeUndefined(); + }); + + it('should initialize correctly with given elements', () => { + const queue = new Queue([1, 2, 3]); + expect(queue.length).toBe(3); + expect(queue.first).toBe(1); + expect(queue.last).toBe(3); + }); + + // Test push and pushMany + it('should add elements to the queue', () => { + const queue = new Queue(); + queue.push(1); + queue.push(2); + expect(queue.length).toBe(2); + expect(queue.first).toBe(1); + expect(queue.last).toBe(2); + }); + + it('should add multiple elements using pushMany', () => { + const queue = new Queue(); + queue.pushMany([1, 2, 3]); + expect(queue.length).toBe(3); + expect(queue.elements).toEqual([1, 2, 3]); + }); + + // Test shift + it('should remove the first element from the queue', () => { + const queue = new Queue([1, 2, 3]); + const shifted = queue.shift(); + expect(shifted).toBe(1); + expect(queue.length).toBe(2); + expect(queue.first).toBe(2); + }); + + // Test delete and deleteAt + it('should delete an element from the queue', () => { + const queue = new Queue([1, 2, 3]); + const result = queue.delete(2); + expect(result).toBe(true); + expect(queue.elements).toEqual([1, 3]); + }); + + it('should delete an element at a specific index', () => { + const queue = new Queue([1, 2, 3]); + const deleted = queue.deleteAt(1); + expect(deleted).toBe(2); + expect(queue.elements).toEqual([1, 3]); + }); + + // Test at + it('should retrieve an element by index', () => { + const queue = new Queue([1, 2, 3]); + expect(queue.at(0)).toBe(1); + expect(queue.at(2)).toBe(3); + }); + + // Test reverse + it('should reverse the queue', () => { + const queue = new Queue([1, 2, 3]); + queue.reverse(); + expect(queue.elements).toEqual([3, 2, 1]); + expect(queue.first).toBe(3); + expect(queue.last).toBe(1); + }); + + // Test addAt + it('should add an element at a specific index', () => { + const queue = new Queue([1, 3]); + const result = queue.addAt(1, 2); + expect(result).toBe(true); + expect(queue.elements).toEqual([1, 2, 3]); + }); + + // Test setAt + it('should set an element at a specific index', () => { + const queue = new Queue([1, 2, 3]); + const result = queue.setAt(1, 10); + expect(result).toBe(true); + expect(queue.elements).toEqual([1, 10, 3]); + }); + + // Test clear + it('should clear the queue', () => { + const queue = new Queue([1, 2, 3]); + queue.clear(); + expect(queue.isEmpty()).toBe(true); + expect(queue.length).toBe(0); + }); + + // Test compact + it('should compact the queue', () => { + const queue = new Queue([1, 2, 3]); + queue.shift(); + queue.shift(); + queue.compact(); + expect(queue.elements).toEqual([3]); + }); + + // Test splice + it('should splice elements from the queue', () => { + const queue = new Queue([1, 2, 3, 4]); + const removed = queue.splice(1, 2); + expect(removed.elements).toEqual([2, 3]); + expect(queue.elements).toEqual([1, 4]); + }); + + // Test clone + it('should create a clone of the queue', () => { + const queue = new Queue([1, 2, 3]); + const clone = queue.clone(); + expect(clone.elements).toEqual(queue.elements); + clone.push(4); + expect(queue.elements).not.toContain(4); + }); + + // Test filter + it('should filter elements based on a predicate', () => { + const queue = new Queue([1, 2, 3, 4]); + const filtered = queue.filter(el => el % 2 === 0); + expect(filtered.elements).toEqual([2, 4]); + }); + + // Test map + it('should map elements to a new queue', () => { + const queue = new Queue([1, 2, 3]); + const mapped = queue.map(el => el * 2); + expect(mapped.elements).toEqual([2, 4, 6]); + }); +}); + +describe('classic uses', () => { + it('@example Sliding Window using Queue', () => { + const nums = [2, 3, 4, 1, 5]; + const k = 2; + const queue = new Queue(); + + let maxSum = 0; + let currentSum = 0; + + nums.forEach((num, i) => { + queue.push(num); + currentSum += num; + + if (queue.length > k) { + currentSum -= queue.shift()!; + } + + if (queue.length === k) { + maxSum = Math.max(maxSum, currentSum); + } + }); + + expect(maxSum).toBe(7); // Maximum sum is from subarray [3, 4]. + }); + + it('@example Breadth-First Search (BFS) using Queue', () => { + const graph: { [key in number]: number[] } = { + 1: [2, 3], + 2: [4, 5], + 3: [], + 4: [], + 5: [] + }; + + const queue = new Queue(); + const visited: number[] = []; + + queue.push(1); + + while (!queue.isEmpty()) { + const node = queue.shift()!; + if (!visited.includes(node)) { + visited.push(node); + graph[node].forEach(neighbor => queue.push(neighbor)); + } + } + + expect(visited).toEqual([1, 2, 3, 4, 5]); // Expected BFS traversal order. + }); + + it('Task Scheduling using Queue', () => { + const tasks = ['A', 'A', 'A', 'B', 'B', 'B']; + const cooldown = 2; + + const taskQueue = new Queue(); + const cooldownQueue = new Queue(); + + for (const task of tasks) { + while (!cooldownQueue.isEmpty() && cooldownQueue.first === task) { + cooldownQueue.shift(); + taskQueue.push('idle'); + } + + taskQueue.push(task); + cooldownQueue.push(task); + if (cooldownQueue.length > cooldown) { + cooldownQueue.shift(); + } + } + + const scheduled = taskQueue.elements; + expect(scheduled).toEqual(['A', 'idle', 'A', 'idle', 'A', 'B', 'B', 'idle', 'idle', 'B']); + }); +}); diff --git a/test/unit/data-structures/stack/stack.test.ts b/test/unit/data-structures/stack/stack.test.ts index 1875298..57f811a 100644 --- a/test/unit/data-structures/stack/stack.test.ts +++ b/test/unit/data-structures/stack/stack.test.ts @@ -162,3 +162,168 @@ describe('Stack iterative methods', () => { }).toThrow('toElementFn must be a function type'); }); }); + +describe('classic uses', () => { + it('@example Balanced Parentheses or Brackets', () => { + type ValidCharacters = ')' | '(' | ']' | '[' | '}' | '{'; + + const stack = new Stack(); + const input: ValidCharacters[] = '[({})]'.split('') as ValidCharacters[]; + const matches: { [key in ValidCharacters]?: ValidCharacters } = { ')': '(', ']': '[', '}': '{' }; + for (const char of input) { + if ('([{'.includes(char)) { + stack.push(char); + } else if (')]}'.includes(char)) { + if (stack.pop() !== matches[char]) { + fail('Parentheses are not balanced'); + } + } + } + expect(stack.isEmpty()).toBe(true); + }); + + it('@example Expression Evaluation and Conversion', () => { + const stack = new Stack(); + const expression = [5, 3, '+']; // Equivalent to 5 + 3 + expression.forEach(token => { + if (typeof token === 'number') { + stack.push(token); + } else { + const b = stack.pop()!; + const a = stack.pop()!; + stack.push(token === '+' ? a + b : 0); // Only handling '+' here + } + }); + expect(stack.pop()).toBe(8); + }); + + it('@example Depth-First Search (DFS)', () => { + const stack = new Stack(); + const graph: { [key in number]: number[] } = { 1: [2, 3], 2: [4], 3: [5], 4: [], 5: [] }; + const visited: number[] = []; + stack.push(1); + while (!stack.isEmpty()) { + const node = stack.pop()!; + if (!visited.includes(node)) { + visited.push(node); + graph[node].forEach(neighbor => stack.push(neighbor)); + } + } + expect(visited).toEqual([1, 3, 5, 2, 4]); // Example DFS order + }); + + it('@example Backtracking Algorithms', () => { + const stack = new Stack<[number, number]>(); + const maze = [ + ['S', ' ', 'X'], + ['X', ' ', 'X'], + [' ', ' ', 'E'] + ]; + const start: [number, number] = [0, 0]; + const end = [2, 2]; + const directions = [ + [0, 1], // To the right + [1, 0], // down + [0, -1], // left + [-1, 0] // up + ]; + + const visited = new Set(); // Used to record visited nodes + stack.push(start); + const path: number[][] = []; + + while (!stack.isEmpty()) { + const [x, y] = stack.pop()!; + if (visited.has(`${x},${y}`)) continue; // Skip already visited nodes + visited.add(`${x},${y}`); + + path.push([x, y]); + + if (x === end[0] && y === end[1]) { + break; // Find the end point and exit + } + + for (const [dx, dy] of directions) { + const nx = x + dx; + const ny = y + dy; + if ( + maze[nx]?.[ny] === ' ' || // feasible path + maze[nx]?.[ny] === 'E' // destination + ) { + stack.push([nx, ny]); + } + } + } + + expect(path).toContainEqual(end); + }); + + it('@example Function Call Stack', () => { + const functionStack = new Stack(); + functionStack.push('main'); + functionStack.push('foo'); + functionStack.push('bar'); + expect(functionStack.pop()).toBe('bar'); + expect(functionStack.pop()).toBe('foo'); + expect(functionStack.pop()).toBe('main'); + }); + + it('@example Simplify File Paths', () => { + const stack = new Stack(); + const path = '/a/./b/../../c'; + path.split('/').forEach(segment => { + if (segment === '..') stack.pop(); + else if (segment && segment !== '.') stack.push(segment); + }); + expect(stack.elements.join('/')).toBe('c'); + }); + + it('@example Stock Span Problem', () => { + const stack = new Stack(); + const prices = [100, 80, 60, 70, 60, 75, 85]; + const spans: number[] = []; + prices.forEach((price, i) => { + while (!stack.isEmpty() && prices[stack.peek()!] <= price) { + stack.pop(); + } + spans.push(stack.isEmpty() ? i + 1 : i - stack.peek()!); + stack.push(i); + }); + expect(spans).toEqual([1, 1, 1, 2, 1, 4, 6]); + }); + + it('Browser Navigation', () => { + const backStack = new Stack(); + const forwardStack = new Stack(); + backStack.push('Page 1'); + backStack.push('Page 2'); + forwardStack.push(backStack.pop()!); + expect(backStack.size).toBe(1); + expect(forwardStack.size).toBe(1); + }); + + it('String Reversal', () => { + const stack = new Stack(); + const input = 'hello'; + const reversed = []; + input.split('').forEach(char => stack.push(char)); + while (!stack.isEmpty()) { + reversed.push(stack.pop()); + } + expect(reversed.join('')).toBe('olleh'); + }); + + it('Next Greater Element', () => { + const stack = new Stack(); + const array = [4, 5, 2, 25]; + const nextGreater = new Array(array.length).fill(-1); + array.forEach((_, i) => { + while (!stack.isEmpty() && array[stack.peek()!] < array[i]) { + const idx = stack.pop()!; + nextGreater[idx] = array[i]; + } + stack.push(i); + }); + expect(nextGreater).toEqual([5, 25, 25, -1]); + }); +});