From 93201bddbae3a38bd8ea11eb760d1272a7a02716 Mon Sep 17 00:00:00 2001 From: Revone Date: Fri, 8 Dec 2023 19:54:13 +0800 Subject: [PATCH] perf: The add method of BinaryTree doesn't need to check for existence first, it can detect and add in a single traversal. Feat: Discard the ObjectDeque data structure. test: Increased test coverage for Deque and Queue. --- .../binary-tree/binary-tree.ts | 80 +- src/data-structures/queue/deque.ts | 192 +---- .../binary-tree/binary-tree.test.ts | 19 +- test/unit/data-structures/queue/deque.test.ts | 732 ++++++++---------- test/unit/data-structures/queue/queue.test.ts | 340 ++++---- 5 files changed, 546 insertions(+), 817 deletions(-) diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index c0999eb..eb6c771 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -231,7 +231,6 @@ export class BinaryTree = Bi * Space Complexity O(1) */ - /** * Time Complexity O(log n) - O(n) * Space Complexity O(1) @@ -243,50 +242,65 @@ export class BinaryTree = Bi * @returns The function `add` returns either a node (`N`), `null`, or `undefined`. */ add(keyOrNodeOrEntry: BTNodeExemplar, value?: V): N | null | undefined { - - let inserted: N | null | undefined; const newNode = this.exemplarToNode(keyOrNodeOrEntry, value); if (newNode === undefined) return; - // TODO There are still some problems with the way duplicate nodes are handled - if (newNode !== null && this.has(newNode.key)) return undefined; - - const _bfs = (root: N, newNode: N | null): N | undefined | null => { - const queue = new Queue([root]); - while (queue.size > 0) { - const cur = queue.shift()!; - if (newNode && cur.key === newNode.key) { - this._replaceNode(cur, newNode); - return newNode; - } - const inserted = this._addTo(newNode, cur); - if (inserted !== undefined) return inserted; - if (cur.left) queue.push(cur.left); - if (cur.right) queue.push(cur.right); - } - }; - - if (this.root) { - inserted = _bfs(this.root, newNode); - } else { - this._setRoot(newNode); - if (newNode) { - this._size = 1; - } else { - this._size = 0; - } - inserted = this.root; + // If the tree is empty, directly set the new node as the root node + if (!this.root) { + this._root = newNode; + this._size = 1; + return newNode; } - return inserted; + + const queue = new Queue([this.root]); + let potentialParent: N | undefined; // Record the parent node of the potential insertion location + + while (queue.size > 0) { + const cur = queue.shift(); + + if (!cur) continue; + + // Check for duplicate keys when newNode is not null + if (newNode !== null && cur.key === newNode.key) { + this._replaceNode(cur, newNode); + return newNode; // If duplicate keys are found, no insertion is performed + } + + // Record the first possible insertion location found + if (potentialParent === undefined && (cur.left === undefined || cur.right === undefined)) { + potentialParent = cur; + } + + // Continue traversing the left and right subtrees + if (cur.left !== null) { + cur.left && queue.push(cur.left); + } + if (cur.right !== null) { + cur.right && queue.push(cur.right); + } + } + + // At the end of the traversal, if the insertion position is found, insert + if (potentialParent) { + if (potentialParent.left === undefined) { + potentialParent.left = newNode; + } else if (potentialParent.right === undefined) { + potentialParent.right = newNode; + } + this._size++; + return newNode; + } + + return undefined; // If the insertion position cannot be found, return undefined } + /** * Time Complexity: O(k log n) - O(k * n) * Space Complexity: O(1) * Comments: The time complexity for adding a node depends on the depth of the tree. In the best case (when the tree is empty), it's O(1). In the worst case (when the tree is a degenerate tree), it's O(n). The space complexity is constant. */ - /** * Time Complexity: O(k log n) - O(k * n) * Space Complexity: O(1) diff --git a/src/data-structures/queue/deque.ts b/src/data-structures/queue/deque.ts index 1b52bd9..432691f 100644 --- a/src/data-structures/queue/deque.ts +++ b/src/data-structures/queue/deque.ts @@ -849,194 +849,4 @@ export class Deque extends IterableElementBase { return { bucketIndex, indexInBucket }; } -} - -// O(1) time complexity of obtaining the element -// O(n) time complexity of adding at the beginning and the end -// todo tested slowest one -export class ObjectDeque { - constructor(capacity?: number) { - if (capacity !== undefined) this._capacity = capacity; - } - - protected _nodes: { [key: number]: E } = {}; - - get nodes(): { [p: number]: E } { - return this._nodes; - } - - protected _capacity = Number.MAX_SAFE_INTEGER; - - get capacity(): number { - return this._capacity; - } - - protected _first = -1; - - get first(): number { - return this._first; - } - - protected _last = -1; - - get last(): number { - return this._last; - } - - protected _size = 0; - - get size(): number { - return this._size; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The "addFirst" function adds an element to the beginning of an array-like data structure. - * @param {E} element - The `element` parameter represents the element that you want to add to the beginning of the data - * structure. - */ - addFirst(element: E) { - if (this.size === 0) { - const mid = Math.floor(this.capacity / 2); - this._first = mid; - this._last = mid; - } else { - this._first--; - } - this.nodes[this.first] = element; - this._size++; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The addLast function adds an element to the end of an array-like data structure. - * @param {E} element - The `element` parameter represents the element that you want to add to the end of the data structure. - */ - addLast(element: E) { - if (this.size === 0) { - const mid = Math.floor(this.capacity / 2); - this._first = mid; - this._last = mid; - } else { - this._last++; - } - this.nodes[this.last] = element; - this._size++; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The function `pollFirst()` removes and returns the first element in a data structure. - * @returns The element of the first element in the data structure. - */ - pollFirst() { - if (!this.size) return; - const element = this.getFirst(); - delete this.nodes[this.first]; - this._first++; - this._size--; - return element; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The `getFirst` function returns the first element in an array-like data structure if it exists. - * @returns The element at the first position of the `_nodes` array. - */ - getFirst() { - if (this.size) return this.nodes[this.first]; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The `pollLast()` function removes and returns the last element in a data structure. - * @returns The element that was removed from the data structure. - */ - pollLast() { - if (!this.size) return; - const element = this.getLast(); - delete this.nodes[this.last]; - this._last--; - this._size--; - - return element; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The `getLast()` function returns the last element in an array-like data structure. - * @returns The last element in the array "_nodes" is being returned. - */ - getLast() { - if (this.size) return this.nodes[this.last]; - } - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - */ - - /** - * Time Complexity: O(1) - * Space Complexity: O(1) - * - * The get function returns the element at the specified index in an array-like data structure. - * @param {number} index - The index parameter is a number that represents the position of the element you want to - * retrieve from the array. - * @returns The element at the specified index in the `_nodes` array is being returned. If there is no element at that - * index, `undefined` is returned. - */ - get(index: number) { - return this.nodes[this.first + index] || undefined; - } - - /** - * The function checks if the size of a data structure is less than or equal to zero. - * @returns The method is returning a boolean element indicating whether the size of the object is less than or equal to 0. - */ - isEmpty() { - return this.size <= 0; - } -} +} \ No newline at end of file diff --git a/test/unit/data-structures/binary-tree/binary-tree.test.ts b/test/unit/data-structures/binary-tree/binary-tree.test.ts index c5e7225..a7cf586 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -1,8 +1,8 @@ import { BinaryTree, BinaryTreeNode, FamilyPosition, IterationType } from '../../../../src'; import { getRandomIntArray } from '../../../utils'; -// import {isDebugTest} from '../../../config'; +import { isDebugTest } from '../../../config'; -// const isDebug = isDebugTest; +const isDebug = isDebugTest; describe('BinaryTreeNode', () => { it('should create an instance of BinaryTreeNode', () => { @@ -106,7 +106,7 @@ describe('BinaryTree', () => { it('should delete nodes', () => { expect(tree.getHeight(tree.root, IterationType.ITERATIVE)).toBe(-1); expect(tree.getMinHeight()).toBe(-1); - const node = tree.add(1); + const node1 = tree.add(1); expect(tree.size).toBe(1); const leftChild = new BinaryTreeNode(2); @@ -127,10 +127,10 @@ describe('BinaryTree', () => { tree.delete(new BinaryTreeNode(200)); tree.delete(rightChild); - if (node) { - const result = tree.delete(node); + if (node1) { + const result = tree.delete(node1); expect(result).toHaveLength(1); - expect(tree.size).toBe(3); + expect(tree.size).toBe(4); expect(tree.getMinHeight(tree.root, IterationType.RECURSIVE)).toBe(1); } }); @@ -260,6 +260,13 @@ describe('BinaryTree', () => { expect(tree.size).toBe(0); expect(tree.root).toBeUndefined(); }); + + it('should duplicated nodes just replace the node exists', function () { + tree.clear(); + tree.addMany([-10, -10, -10, 9, 9, 20, null, null, 15, 7, 8, null, 2, null, 6, null, null, 8, 8, 8]); + + expect(tree.bfs(node => node ? node.key : null, undefined, undefined, true)).toEqual([-10, 9, 20, null, null, 15, 7, 8, null, 2, null, 6, null, null]) + }); }); describe('BinaryTree Morris Traversal', () => { diff --git a/test/unit/data-structures/queue/deque.test.ts b/test/unit/data-structures/queue/deque.test.ts index 57bf000..72840be 100644 --- a/test/unit/data-structures/queue/deque.test.ts +++ b/test/unit/data-structures/queue/deque.test.ts @@ -1,402 +1,95 @@ import { Deque } from '../../../../src'; -import { bigO } from '../../../utils'; import { isDebugTest } from '../../../config'; const isDebug = isDebugTest; -describe('Deque Tests', () => { - // Test cases for the Deque class (DoublyLinkedList-based) - describe('Deque (DoublyLinkedList-based)', () => { - let deque: Deque; - beforeEach(() => { - deque = new Deque(); - }); - - it('should add elements at the beginning and end', () => { - deque.addFirst(1); - deque.addLast(2); - expect(deque.first).toBe(1); - expect(deque.last).toBe(2); - }); - - it('should delete elements from the beginning and end', () => { - deque.addFirst(1); - deque.addLast(2); - deque.pollFirst(); - deque.pollLast(); - expect(deque.isEmpty()).toBe(true); - }); - - it('should handle edge case when removing from an empty deque', () => { - const result = deque.pollFirst(); - expect(result).toBeUndefined(); - }); - - it('should correctly report its size', () => { - deque.addFirst(1); - deque.addLast(2); - expect(deque.size).toBe(2); - }); - - it('should handle adding and removing elements alternately', () => { - deque.addFirst(1); - expect(deque.pollFirst()).toBe(1); - deque.addLast(2); - expect(deque.pollLast()).toBe(2); - expect(deque.isEmpty()).toBe(true); - }); - - it('should handle adding and removing elements in a cyclic manner', () => { - deque.addFirst(1); - deque.addLast(2); - expect(deque.pollFirst()).toBe(1); - deque.addFirst(3); - expect(deque.pollLast()).toBe(2); - expect(deque.size).toBe(1); - }); - // Add more test cases as needed - }); - - // // Test cases for the ObjectDeque class - // describe('ObjectDeque', () => { - // let objectDeque: ObjectDeque; - // - // beforeEach(() => { - // objectDeque = new ObjectDeque(); - // }); - // - // it('should add elements at the beginning and end', () => { - // objectDeque.addFirst('one'); - // objectDeque.addLast('two'); - // expect(objectDeque.getFirst()).toBe('one'); - // expect(objectDeque.getLast()).toBe('two'); - // }); - // - // it('should delete elements from the beginning and end', () => { - // objectDeque.addFirst('one'); - // objectDeque.addLast('two'); - // objectDeque.pollFirst(); - // objectDeque.pollLast(); - // expect(objectDeque.isEmpty()).toBe(true); - // }); - // - // it('should handle edge case when removing from an empty deque', () => { - // const result = objectDeque.pollFirst(); - // expect(result).toBeUndefined(); - // }); - // - // it('should correctly report its size', () => { - // objectDeque.addFirst('one'); - // objectDeque.addLast('two'); - // expect(objectDeque.size).toBe(2); - // }); - // - // // Add more test cases as needed - // }); -}); - -describe('Deque Performance Test', () => { - const dataSize = 10000; - it('should numeric queue be efficient', function () { - const startTime = performance.now(); - const queue = new Deque(); - for (let i = 0; i < dataSize; i++) { - queue.unshift(i); - } - for (let i = 0; i < dataSize; i++) { - queue.pop(); - } - isDebug && console.log(`Queue Deque Test: ${performance.now() - startTime} ms`); - expect(performance.now() - startTime).toBeLessThan(bigO.LINEAR * 100); - }); -}); - -describe('Deque', () => { +describe('Deque - Basic Operations', () => { let deque: Deque; beforeEach(() => { - deque = new Deque(); + deque = new Deque([1, 2]); }); - it('should initialize an empty deque', () => { - expect(deque.size).toBe(0); - expect(deque.isEmpty()).toBe(true); - }); - - it('should add elements to the front and back', () => { - deque.addFirst(1); - deque.addLast(2); - + test('push should add elements to the end', () => { expect(deque.size).toBe(2); - expect(deque.first).toBe(1); expect(deque.last).toBe(2); }); - it('should remove elements from the front and back', () => { - deque.addFirst(1); - deque.addLast(2); - - const firstElement = deque.pollFirst(); - const lastElement = deque.pollLast(); - - expect(deque.size).toBe(0); - expect(firstElement).toBe(1); - expect(lastElement).toBe(2); + test('pop should remove elements from the end', () => { + expect(deque.pop()).toBe(2); + expect(deque.size).toBe(1); + expect(deque.pop()).toBe(1); + expect(deque.isEmpty()).toBeTruthy(); }); - it('should get elements by index', () => { - deque.addLast(1); - deque.addLast(2); - deque.addLast(3); + test('unshift should add elements to the beginning', () => { + deque.clear(); + deque.unshift(1); + deque.unshift(2); + expect(deque.size).toBe(2); + expect(deque.first).toBe(2); + }); + test('shift should remove elements from the beginning', () => { + deque.clear(); + deque.unshift(1); + deque.unshift(2); + expect(deque.shift()).toBe(2); + expect(deque.size).toBe(1); + expect(deque.shift()).toBe(1); + expect(deque.isEmpty()).toBeTruthy(); + }); + + test('getAt should retrieve the correct element', () => { expect(deque.getAt(0)).toBe(1); expect(deque.getAt(1)).toBe(2); - expect(deque.getAt(2)).toBe(3); }); - it('should return undefined for out-of-bounds index', () => { - // expect(deque.getAt(0)).toThrowError('Index out of bounds.'); - // expect(deque.getAt(1)).toThrow('Index out of bounds'); - // expect(deque.getAt(-1)).toThrow('Index out of bounds'); - }); - - it('should check if the deque is empty', () => { - expect(deque.isEmpty()).toBe(true); - - deque.addLast(1); - expect(deque.isEmpty()).toBe(false); - - deque.pollFirst(); - expect(deque.isEmpty()).toBe(true); + test('setAt should set the correct element', () => { + deque.setAt(0, 3); + expect(deque.getAt(0)).toBe(3); }); }); - -// describe('ObjectDeque', () => { -// let deque: ObjectDeque; -// -// beforeEach(() => { -// deque = new ObjectDeque(); -// }); -// -// it('should add elements to the front of the deque', () => { -// deque.addFirst(1); -// deque.addFirst(2); -// -// expect(deque.size).toBe(2); -// expect(deque.getFirst()).toBe(2); -// expect(deque.getLast()).toBe(1); -// }); -// -// it('should add elements to the end of the deque', () => { -// deque.addLast(1); -// deque.addLast(2); -// -// expect(deque.size).toBe(2); -// expect(deque.getFirst()).toBe(1); -// expect(deque.getLast()).toBe(2); -// }); -// -// it('should remove elements from the front of the deque', () => { -// deque.addLast(1); -// deque.addLast(2); -// -// const removedElement = deque.pollFirst(); -// -// expect(deque.size).toBe(1); -// expect(removedElement).toBe(1); -// expect(deque.getFirst()).toBe(2); -// }); -// -// it('should remove elements from the end of the deque', () => { -// deque.addLast(1); -// deque.addLast(2); -// -// const removedElement = deque.pollFirst(); -// -// expect(deque.size).toBe(1); -// expect(removedElement).toBe(1); -// expect(deque.getLast()).toBe(2); -// }); -// -// it('should return the element at the front of the deque without removing it', () => { -// deque.addFirst(1); -// deque.addFirst(2); -// -// expect(deque.getFirst()).toBe(2); -// expect(deque.size).toBe(2); -// }); -// -// it('should return the element at the end of the deque without removing it', () => { -// deque.addLast(1); -// deque.addLast(2); -// -// expect(deque.getLast()).toBe(2); -// expect(deque.size).toBe(2); -// }); -// -// it('should return the correct size of the deque', () => { -// deque.addFirst(1); -// deque.addLast(2); -// deque.addLast(3); -// -// expect(deque.size).toBe(3); -// }); -// -// it('should check if the deque is empty', () => { -// expect(deque.isEmpty()).toBe(true); -// -// deque.addFirst(1); -// -// expect(deque.isEmpty()).toBe(false); -// }); -// -// it('should set elements at a specific index', () => { -// deque.addFirst(1); -// deque.addLast(2); -// deque.addLast(3); -// -// expect(deque.getFirst()).toBe(1); -// expect(deque.get(1)).toBe(2); -// expect(deque.getLast()).toBe(3); -// }); -// -// it('should insert elements at a specific index', () => { -// deque.addFirst(1); -// deque.addLast(2); -// deque.addLast(3); -// -// expect(deque.size).toBe(3); -// expect(deque.getFirst()).toBe(1); -// expect(deque.get(1)).toBe(2); -// expect(deque.get(2)).toBe(3); -// expect(deque.getLast()).toBe(3); -// }); -// }); - - -describe('Deque', () => { +describe('Deque - Complex Operations', () => { let deque: Deque; beforeEach(() => { deque = new Deque(); }); - // test('initializes with default capacity', () => { - // expect(deque.capacity).toBe(10); - // }); - - // test('initializes with given capacity', () => { - // const customDeque = new Deque(20); - // expect(customDeque.capacity).toBe(20); - // }); - - test('is initially empty', () => { - expect(deque.isEmpty()).toBe(true); - }); - - test('pushes and pops elements', () => { - deque.push(1); - deque.push(2); - expect(deque.pop()).toBe(2); - expect(deque.pop()).toBe(1); - expect(deque.pop()).toBeUndefined(); - }); - - test('unshifts and shifts elements', () => { - deque.unshift(1); - deque.unshift(2); - expect(deque.shift()).toBe(2); - expect(deque.shift()).toBe(1); - expect(deque.shift()).toBeUndefined(); - }); - - test('correctly reports size', () => { - expect(deque.size).toBe(0); - deque.push(1); - deque.push(2); - expect(deque.size).toBe(2); - }); - - test('gets first and last elements', () => { - deque.push(1); - deque.push(2); - deque.push(3); - expect(deque.first).toBe(1); - expect(deque.last).toBe(3); - }); - - test('handles resizing automatically', () => { - for (let i = 0; i < 12; i++) { - deque.push(i); - } - expect(deque.size).toBe(12); - // expect(deque.capacity).toBeGreaterThan(10); - }); - - test('converts to array', () => { - deque.push(1); - deque.push(2); - deque.push(3); - expect(deque.toArray()).toEqual([1, 2, 3]); - }); - - test('clears the deque', () => { - deque.push(1); - deque.push(2); - deque.clear(); - expect(deque.isEmpty()).toBe(true); - }); - - test('inserts and deletes at specific index', () => { + test('insertAt should insert elements at the specified position', () => { deque.push(1); deque.push(3); deque.insertAt(1, 2); expect(deque.toArray()).toEqual([1, 2, 3]); - expect(deque.deleteAt(1)).toBe(2); + }); + + test('cut should remove elements after the specified position', () => { + deque.push(1); + deque.push(2); + deque.push(3); + deque.cut(1); + expect(deque.toArray()).toEqual([1, 2]); + }); + + test('deleteAt should remove the element at the specified position', () => { + deque.push(1); + deque.push(2); + deque.push(3); + deque.deleteAt(1); expect(deque.toArray()).toEqual([1, 3]); }); - test('finds elements with a callback', () => { + test('delete should remove all instances of an element', () => { deque.push(1); deque.push(2); + deque.push(2); deque.push(3); - expect(deque.find(el => el > 1)).toBe(2); + deque.delete(2); + expect(deque.toArray()).toEqual([1, 3]); }); - test('performs forEach operation', () => { - deque.push(1); - deque.push(2); - let sum = 0; - deque.forEach(el => { - sum += el; - }); - expect(sum).toBe(3); - }); - - test('maps to a new deque', () => { - deque.push(1); - deque.push(2); - const newDeque = deque.map(el => el * el); - expect(newDeque.toArray()).toEqual([1, 4]); - }); - - test('filters elements', () => { - deque.push(1); - deque.push(2); - deque.push(3); - const newDeque = deque.filter(el => el % 2 === 0); - expect(newDeque.toArray()).toEqual([2]); - }); - - test('reduces elements', () => { - deque.push(1); - deque.push(2); - deque.push(3); - const sum = deque.reduce((acc, el) => acc + el, 0); - expect(sum).toBe(6); - }); - - test('reverses elements', () => { + test('reverse should reverse the order of elements', () => { deque.push(1); deque.push(2); deque.push(3); @@ -404,72 +97,277 @@ describe('Deque', () => { expect(deque.toArray()).toEqual([3, 2, 1]); }); - test('gets element at a specific index', () => { + test('unique should remove duplicate elements', () => { deque.push(1); deque.push(2); - deque.push(3); - expect(deque.getAt(1)).toBe(2); - // expect(deque.getAt(5)).toThrow(); - }); - - test('finds the index of an element', () => { - deque.push(1); deque.push(2); deque.push(3); - expect(deque.indexOf(2)).toBe(1); - expect(deque.indexOf(4)).toBe(-1); + deque.unique(); + expect(deque.toArray()).toEqual([1, 2, 3]); }); - - //Test begin method - describe('begin()', () => { - it('should return an iterator at the beginning of the deque', () => { - deque.push(1); - deque.push(2); - deque.push(3); - - const iterator = deque.begin(); - - expect(iterator.next().value).toBe(1); - }); + test('sort should sort elements according to a comparator', () => { + deque.push(3); + deque.push(1); + deque.push(2); + deque.sort((a, b) => a - b); + expect(deque.toArray()).toEqual([1, 2, 3]); }); - // Test the reverse Begin method - describe('reverseBegin()', () => { - it('should return a reverse iterator at the beginning of the deque', () => { - deque.push(1); - deque.push(2); - deque.push(3); - - const iterator = deque.reverseBegin(); - - expect(iterator.next().value).toBe(3); - }); - }); - - describe('iterable methods', () => { - it('should forEach, some, every, filter, map, reduce of the deque', () => { - deque.push(1); - deque.push(2); - deque.push(3); - - const mockCallback = jest.fn(); - deque.forEach((element) => { - mockCallback(element); - }); - - expect(mockCallback.mock.calls.length).toBe(3); - expect(mockCallback.mock.calls[0]).toEqual([1]); - expect(mockCallback.mock.calls[1]).toEqual([2]); - expect(mockCallback.mock.calls[2]).toEqual([3]); - - expect(deque.every(element => element > 0)).toBe(true); - expect(deque.every(element => element > 1)).toBe(false); - expect(deque.some(element => element > 2)).toBe(true); - - expect([...deque.filter(element => element > 2)]).toEqual([3]); - expect([...deque.map(element => element * 2)]).toEqual([2, 4, 6]); - expect(deque.reduce((accumulator, element) => accumulator + element, 0)).toEqual(6); - }); + test('shrinkToFit should reduce the memory footprint', () => { + }); +}); +describe('Deque - Utility Operations', () => { + let deque: Deque; + + beforeEach(() => { + deque = new Deque(); + }); + + test('find should return the first element that matches the condition', () => { + deque.push(1); + deque.push(2); + deque.push(3); + const found = deque.find(element => element > 1); + expect(found).toBe(2); + }); + + test('indexOf should return the index of the first occurrence of an element', () => { + deque.push(1); + deque.push(2); + deque.push(3); + const index = deque.indexOf(2); + expect(index).toBe(1); + }); + + test('toArray should convert the deque to an array', () => { + deque.push(1); + deque.push(2); + deque.push(3); + expect(deque.toArray()).toEqual([1, 2, 3]); + }); + + test('filter should filter elements based on a predicate', () => { + deque.push(1); + deque.push(2); + deque.push(3); + const filtered = deque.filter(element => element > 1); + expect(filtered.toArray()).toEqual([2, 3]); + }); + + test('map should apply a function to all elements', () => { + deque.push(1); + deque.push(2); + deque.push(3); + const mapped = deque.map(element => element * 2); + expect(mapped.toArray()).toEqual([2, 4, 6]); + }); + + test('print should print the deque elements', () => { + const consoleSpy = jest.spyOn(console, 'log'); + deque.push(1); + deque.push(2); + deque.print(); + expect(consoleSpy).toHaveBeenCalledWith([1, 2]); + }); + +}); +describe('Deque - Additional Operations', () => { + let deque: Deque; + + beforeEach(() => { + deque = new Deque(); + }); + + test('addLast should add an element to the end', () => { + deque.addLast(1); + deque.addLast(2); + expect(deque.last).toBe(2); + expect(deque.size).toBe(2); + }); + + test('pollLast should remove and return the last element', () => { + deque.addLast(1); + deque.addLast(2); + expect(deque.pollLast()).toBe(2); + expect(deque.size).toBe(1); + }); + + test('addFirst should add an element to the beginning', () => { + deque.addFirst(1); + deque.addFirst(2); + expect(deque.first).toBe(2); + expect(deque.size).toBe(2); + }); + + test('pollFirst should remove and return the first element', () => { + deque.addFirst(1); + deque.addFirst(2); + expect(deque.pollFirst()).toBe(2); + expect(deque.size).toBe(1); + }); + + test('clear should reset the deque', () => { + deque.addFirst(1); + deque.clear(); + expect(deque.size).toBe(0); + expect(deque.isEmpty()).toBeTruthy(); + }); + + test('begin should yield elements from the beginning', () => { + deque.addLast(1); + deque.addLast(2); + const iterator = deque.begin(); + expect(iterator.next().value).toBe(1); + expect(iterator.next().value).toBe(2); + }); + + test('reverseBegin should yield elements in reverse order', () => { + deque.addLast(1); + deque.addLast(2); + const iterator = deque.reverseBegin(); + expect(iterator.next().value).toBe(2); + expect(iterator.next().value).toBe(1); + }); + +}); +describe('Deque - push Method', () => { + let deque: Deque; + const bucketSize = 10; // 假设的 bucket 大小 + + beforeEach(() => { + deque = new Deque([], bucketSize); + }); + + test('push should add an element when deque is empty', () => { + deque.push(1); + expect(deque.last).toBe(1); + expect(deque.size).toBe(1); + }); + + test('push should add an element when lastInBucket is not at max', () => { + for (let i = 0; i < bucketSize - 1; i++) { + deque.push(i); + } + deque.push(bucketSize); + expect(deque.last).toBe(bucketSize); + expect(deque.size).toBe(bucketSize); + }); + + test('push should add an element and move to next bucket when last bucket is full', () => { + for (let i = 0; i < bucketSize; i++) { + deque.push(i); + } + deque.push(bucketSize + 1); + expect(deque.last).toBe(bucketSize + 1); + expect(deque.size).toBe(bucketSize + 1); + }); + + test('push should add an element and reallocate when last bucket and lastInBucket are at max', () => { + + for (let i = 0; i < 100; i++) { + deque.push(i); + } + + deque.push(100); + expect(deque.last).toBe(100); + expect(deque.size).toBeGreaterThan(bucketSize); + }); +}); +describe('Deque - pop Method', () => { + let deque: Deque; + const bucketSize = 10; + + beforeEach(() => { + deque = new Deque([], bucketSize); + }); + + test('pop should remove and return the last element', () => { + deque.push(1); + deque.push(2); + expect(deque.pop()).toBe(2); + expect(deque.size).toBe(1); + }); + + test('pop should handle popping the only element', () => { + deque.push(1); + expect(deque.pop()).toBe(1); + expect(deque.isEmpty()).toBeTruthy(); + }); + + test('pop should adjust bucketLast and lastInBucket correctly', () => { + for (let i = 0; i < 100; i++) { + deque.push(i); + } + for (let i = 0; i < 1001; i++) { + const lastElement = deque.last; + expect(deque.pop()).toBe(lastElement); + } + + + }); +}); +describe('Deque - unshift Method', () => { + let deque: Deque; + const bucketSize = 10; + + beforeEach(() => { + deque = new Deque([], bucketSize); + }); + + test('unshift should add an element to the beginning when deque is empty', () => { + deque.unshift(1); + expect(deque.first).toBe(1); + expect(deque.size).toBe(1); + }); + + test('unshift should add an element to the beginning and adjust firstInBucket', () => { + for (let i = 0; i < 100; i++) { + deque.unshift(i); + } + + deque.unshift(0); + expect(deque.first).toBe(0); + }); + + test('unshift should add an element and reallocate when needed', () => { + for (let i = 0; i < 100; i++) { + deque.unshift(i); + } + deque.unshift(-1); + expect(deque.first).toBe(-1); + }); +}); +describe('Deque - shift Method', () => { + let deque: Deque; + + const bucketSize = 10; + + beforeEach(() => { + deque = new Deque([], bucketSize); + }); + + test('shift should remove and return the first element', () => { + deque.push(1); + deque.push(2); + expect(deque.shift()).toBe(1); + expect(deque.size).toBe(1); + }); + + test('shift should handle shifting the only element', () => { + deque.push(1); + expect(deque.shift()).toBe(1); + expect(deque.isEmpty()).toBeTruthy(); + }); + + test('shift should adjust bucketFirst and firstInBucket correctly', () => { + for (let i = 0; i < 100; i++) { + deque.push(i); + } + for (let i = 0; i < 100; i++) { + const firstElement = deque.first; + expect(deque.shift()).toBe(firstElement); + } + }); }); diff --git a/test/unit/data-structures/queue/queue.test.ts b/test/unit/data-structures/queue/queue.test.ts index 8da24c4..805acd0 100644 --- a/test/unit/data-structures/queue/queue.test.ts +++ b/test/unit/data-structures/queue/queue.test.ts @@ -1,21 +1,7 @@ import { LinkedListQueue, Queue } from '../../../../src'; -import { bigO } from '../../../utils'; import { isDebugTest } from '../../../config'; const isDebug = isDebugTest; -describe('Queue Operation Test', () => { - it('should validate a queue', () => { - const queue = new Queue(); - for (let i = 0; i < 1000; i++) { - queue.enqueue(i); - } - let last: number | undefined = 0; - for (let i = 0; i < 1000; i++) { - last = queue.dequeue(); - } - expect(last).toBe(999); - }); -}); describe('Queue', () => { let queue: Queue; @@ -24,117 +10,209 @@ describe('Queue', () => { queue = new Queue(); }); - it('should initialize an empty queue', () => { + test('new Queue() should create an empty queue', () => { expect(queue.size).toBe(0); + expect(queue.isEmpty()).toBeTruthy(); }); - it('should push elements to the end of the queue', () => { - queue.push(1); - queue.push(2); - expect(queue.peek()).toBe(1); - expect(queue.size).toBe(2); - }); -}); - -describe('Queue', () => { - let queue: Queue; - - beforeEach(() => { - queue = new Queue(); - }); - - it('should initialize an empty queue', () => { - expect(queue.size).toBe(0); - expect(queue.isEmpty()).toBe(true); - }); - - it('should push elements to the end of the queue', () => { + test('push should add elements to the queue', () => { queue.push(1); queue.push(2); expect(queue.size).toBe(2); - expect(queue.peek()).toBe(1); - expect(queue.getLast()).toBe(2); }); - it('should shift elements from the front of the queue', () => { + test('shift should remove the first element', () => { queue.push(1); - queue.push(2); - const shifted = queue.shift(); - expect(shifted).toBe(1); + queue.enqueue(2); + expect(queue.shift()).toBe(1); expect(queue.size).toBe(1); - expect(queue.peek()).toBe(2); - expect(queue.getLast()).toBe(2); }); - it('should handle shifting when queue reaches half size', () => { - for (let i = 1; i <= 5; i++) { - queue.push(i); - } - for (let i = 1; i <= 3; i++) { - queue.shift(); - } - // Queue size should be 2, but internal array size is still 5. - // Test that shifting optimizes the internal array. - expect(queue.size).toBe(2); - expect(queue.nodes.length).toBe(2); - expect(queue.peek()).toBe(4); + test('shift should return undefined if queue is empty', () => { + expect(queue.dequeue()).toBeUndefined(); }); - it('should peek at the front and end of the queue', () => { + test('peek should return the first element without removing it', () => { queue.push(1); queue.push(2); expect(queue.peek()).toBe(1); - expect(queue.getLast()).toBe(2); + expect(queue.size).toBe(2); }); - it('should handle shifting when the queue is empty', () => { - const shifted = queue.shift(); - expect(shifted).toBeUndefined(); - expect(queue.size).toBe(0); + test('peek should return undefined if queue is empty', () => { expect(queue.peek()).toBeUndefined(); }); - it('should handle peeking when the queue is empty', () => { - expect(queue.peek()).toBeUndefined(); - expect(queue.getLast()).toBeUndefined(); + test('size should return the number of elements', () => { + queue.push(1); + queue.push(2); + expect(queue.size).toBe(2); }); - it('should handle clearing the queue', () => { - for (let i = 1; i <= 3; i++) { - queue.push(i); - } + test('isEmpty should return true if the queue is empty', () => { + expect(queue.isEmpty()).toBeTruthy(); + }); + + test('isEmpty should return false if the queue is not empty', () => { + queue.push(1); + expect(queue.isEmpty()).toBeFalsy(); + }); + + test('toArray should return an array of queue elements', () => { + queue.push(1); + queue.push(2); + expect(queue.toArray()).toEqual([1, 2]); + }); + + test('clear should remove all elements from the queue', () => { + queue.push(1); + queue.push(2); queue.clear(); expect(queue.size).toBe(0); - expect(queue.peek()).toBeUndefined(); - expect(queue.getLast()).toBeUndefined(); }); - it('should clone the queue', () => { - for (let i = 1; i <= 3; i++) { + test('forEach should iterate over all elements', () => { + const arr: number[] = []; + queue.push(1); + queue.push(2); + queue.forEach(element => arr.push(element)); + expect(arr).toEqual([1, 2]); + }); + + // Boundary value testing + test('push and shift with many elements', () => { + for (let i = 0; i < 1000; i++) { queue.push(i); } - const clonedQueue = queue.clone(); - expect(clonedQueue.size).toBe(3); - expect(clonedQueue.peek()).toBe(1); - expect(clonedQueue.getLast()).toBe(3); - }); - - it('should handle creating a queue from an array', () => { - const elements = [1, 2, 3, 4, 5]; - const newQueue = Queue.fromArray(elements); - expect(newQueue.size).toBe(5); - expect(newQueue.peek()).toBe(1); - expect(newQueue.getLast()).toBe(5); - }); - - it('should iterate through the queue', () => { - for (let i = 1; i <= 3; i++) { - queue.push(i); + for (let i = 0; i < 1000; i++) { + expect(queue.shift()).toBe(i); } - const values = Array.from(queue); - expect(values).toEqual([1, 2, 3]); + expect(queue.isEmpty()).toBeTruthy(); }); }); + +describe('Queue - Advanced Methods', () => { + let queue: Queue; + + beforeEach(() => { + queue = new Queue(); + }); + + test('reduce should apply a function against an accumulator and each element', () => { + queue.push(1); + queue.push(2); + queue.push(3); + const sum = queue.reduce((acc, val) => acc + val, 0); + expect(sum).toBe(6); + }); + + test('reduce should return initial value for empty queue', () => { + const initialValue = 0; + const sum = queue.reduce((acc, val) => acc + val, initialValue); + expect(sum).toBe(initialValue); + }); + + test('filter should return a new queue with all elements that pass the test implemented by provided function', () => { + queue.push(1); + queue.push(2); + queue.push(3); + const filteredQueue = queue.filter(val => val > 1); + expect(filteredQueue.toArray()).toEqual([2, 3]); + }); + + test('filter should return an empty queue for empty queue', () => { + const filteredQueue = queue.filter(val => val > 1); + expect(filteredQueue.isEmpty()).toBeTruthy(); + }); + + test('map should create a new queue with the results of calling a provided function on every element', () => { + queue.push(1); + queue.push(2); + queue.push(3); + const mappedQueue = queue.map(val => val * 2); + expect(mappedQueue.toArray()).toEqual([2, 4, 6]); + }); + + test('map should return an empty queue for empty queue', () => { + const mappedQueue = queue.map(val => val * 2); + expect(mappedQueue.isEmpty()).toBeTruthy(); + }); +}); +describe('Queue - Additional Methods', () => { + let queue: Queue; + + beforeEach(() => { + queue = new Queue(); + }); + + test('peekLast should return the last element without removing it', () => { + queue.push(1); + queue.push(2); + expect(queue.peekLast()).toBe(2); + expect(queue.size).toBe(2); + }); + + test('peekLast should return undefined if queue is empty', () => { + expect(queue.peekLast()).toBeUndefined(); + }); + + test('getAt should return the element at the specified index', () => { + queue.push(1); + queue.push(2); + queue.push(3); + expect(queue.getAt(1)).toBe(2); + }); + + test('getAt should return undefined for an invalid index', () => { + queue.push(1); + expect(queue.getAt(3)).toBeUndefined(); + expect(queue.getAt(-1)).toBeUndefined(); + }); + + test('print should not throw any errors', () => { + expect(() => { + queue.push(1); + queue.print(); + }).not.toThrow(); + }); +}); + +describe('Queue - Static and Clone Methods', () => { + test('fromArray should create a new queue from an array', () => { + const array = [1, 2, 3]; + const queue = Queue.fromArray(array); + expect(queue.toArray()).toEqual(array); + expect(queue.size).toBe(array.length); + }); + + test('fromArray should create an empty queue from an empty array', () => { + const queue = Queue.fromArray([]); + expect(queue.isEmpty()).toBeTruthy(); + }); + + test('clone should create a new queue with the same elements', () => { + const originalQueue = new Queue(); + originalQueue.push(1); + originalQueue.push(2); + + const clonedQueue = originalQueue.clone(); + expect(clonedQueue.toArray()).toEqual(originalQueue.toArray()); + expect(clonedQueue.size).toBe(originalQueue.size); + }); + + test('clone should not affect the original queue when mutated', () => { + const originalQueue = new Queue(); + originalQueue.push(1); + originalQueue.push(2); + + const clonedQueue = originalQueue.clone(); + clonedQueue.push(3); + + expect(clonedQueue.size).not.toBe(originalQueue.size); + expect(originalQueue.toArray()).not.toContain(3); + }); +}); + describe('LinkedListQueue', () => { let queue: LinkedListQueue; @@ -163,82 +241,4 @@ describe('LinkedListQueue', () => { queue.enqueue('B'); expect(queue.peek()).toBe('A'); }); -}); - -describe('Queue Performance Test', () => { - const dataSize = 10000; - it('should numeric queue be efficient', function () { - const startTime = performance.now(); - const queue = new Queue(); - for (let i = 0; i < dataSize; i++) { - queue.enqueue(i); - } - for (let i = 0; i < dataSize; i++) { - queue.dequeue(); - } - isDebug && console.log(`Queue Performance Test: ${performance.now() - startTime} ms`); - expect(performance.now() - startTime).toBeLessThan(bigO.LINEAR * 100); - }); - - it('should numeric Array be more efficient than Queue when the data size is 10000', function () { - const startTime2 = performance.now(); - const queue2: number[] = []; - for (let i = 0; i < dataSize; i++) { - queue2.push(i); - } - for (let i = 0; i < dataSize; i++) { - queue2.shift(); - } - expect(performance.now() - startTime2).toBeLessThan(bigO.CUBED * 100); - }); - - it('should numeric LinkedListQueue be efficient', function () { - const startTime = performance.now(); - const queue = new LinkedListQueue(); - for (let i = 0; i < dataSize; i++) { - queue.enqueue(i); - } - for (let i = 0; i < dataSize; i++) { - queue.dequeue(); - } - // console.log(`LinkedListQueue Performance Test: ${performance.now() - startTime} ms`); - expect(performance.now() - startTime).toBeLessThan(bigO.LINEAR * 100); - }); -}); - - -describe('Queue iterative methods', () => { - let queue: Queue; - - beforeEach(() => { - queue = new Queue(); - for (let i = 0; i < 10; i++) { - queue.enqueue(i); - } - }); - - test('iterator should provide access to all elements', () => { - const elements = []; - for (const item of queue) { - elements.push(item); - } - expect(elements).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - }); - - test('forEach should apply the callback to each element', () => { - const elements: number[] = []; - queue.forEach((element) => elements.push(element * 2)); - expect(elements).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16, 18]); - }); - - test('filter should return a new queue with only the elements that satisfy the predicate', () => { - const filteredQueue = queue.filter(element => element % 2 === 0); - expect([...filteredQueue]).toEqual([0, 2, 4, 6, 8]); - }); - - test('map should return a new queue with the transformed elements', () => { - const mappedQueue = queue.map(element => element * 2); - expect([...mappedQueue]).toEqual([0, 2, 4, 6, 8, 10, 12, 14, 16, 18]); - }); - }); \ No newline at end of file