docs: Added example code for HashMap, Queue, and Stack.

This commit is contained in:
Revone 2024-12-06 10:38:51 +13:00
parent de0a2f1053
commit 69f8e7de3d
10 changed files with 613 additions and 6 deletions

View file

@ -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

4
package-lock.json generated
View file

@ -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",

View file

@ -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<number, string>();
* 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<number, string>();
* 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<number, string>();
* 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<number, number>();
* 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<K = any, V = any, R = [K, V]> extends IterableEntryBase<K, V> {
/**

View file

@ -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<number>();
*
* 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<number>();
* 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<E = any, R = any> extends LinearBase<E, R> {
constructor(elements: Iterable<E> | Iterable<R> = [], options?: QueueOptions<E, R>) {

View file

@ -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', () => {

View file

@ -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';

View file

@ -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';

View file

@ -842,3 +842,138 @@ describe('LinkedHashMap', () => {
});
});
});
describe('classic uses', () => {
it('@example should maintain insertion order', () => {
const linkedHashMap = new LinkedHashMap<number, string>();
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<number, string>();
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<number, string>();
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<number, string>();
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<number, string>();
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<number, string>();
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<number, string>();
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<number, number>();
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<string, number[]>();
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]);
});
});

View file

@ -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<number>();
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<number>();
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<number>();
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<number>();
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<string>();
const cooldownQueue = new Queue<string>();
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']);
});
});

View file

@ -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<string>();
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<number>();
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<number>();
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<string>(); // 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<string>();
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<string>();
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<number>();
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<string>();
const forwardStack = new Stack<string>();
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<string>();
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<number>();
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]);
});
});