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.

This commit is contained in:
Revone 2023-12-08 19:54:13 +08:00
parent c3b79b70f6
commit 93201bddba
5 changed files with 546 additions and 817 deletions

View file

@ -231,7 +231,6 @@ export class BinaryTree<K = any, V = any, N extends BinaryTreeNode<K, V, N> = Bi
* Space Complexity O(1)
*/
/**
* Time Complexity O(log n) - O(n)
* Space Complexity O(1)
@ -243,50 +242,65 @@ export class BinaryTree<K = any, V = any, N extends BinaryTreeNode<K, V, N> = Bi
* @returns The function `add` returns either a node (`N`), `null`, or `undefined`.
*/
add(keyOrNodeOrEntry: BTNodeExemplar<K, V, N>, 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<N>([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<N>([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)

View file

@ -849,194 +849,4 @@ export class Deque<E> extends IterableElementBase<E> {
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<E = number> {
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;
}
}
}

View file

@ -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<number>(2);
@ -127,10 +127,10 @@ describe('BinaryTree', () => {
tree.delete(new BinaryTreeNode<number>(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', () => {

View file

@ -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<number>;
beforeEach(() => {
deque = new Deque<number>();
});
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<string>;
//
// beforeEach(() => {
// objectDeque = new ObjectDeque<string>();
// });
//
// 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<number>();
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<number>;
beforeEach(() => {
deque = new Deque<number>();
deque = new Deque<number>([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<number>;
//
// beforeEach(() => {
// deque = new ObjectDeque<number>();
// });
//
// 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<number>;
beforeEach(() => {
deque = new Deque<number>();
});
// 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<number>;
beforeEach(() => {
deque = new Deque<number>();
});
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<number>;
beforeEach(() => {
deque = new Deque<number>();
});
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<number>;
const bucketSize = 10; // 假设的 bucket 大小
beforeEach(() => {
deque = new Deque<number>([], 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<number>;
const bucketSize = 10;
beforeEach(() => {
deque = new Deque<number>([], 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<number>;
const bucketSize = 10;
beforeEach(() => {
deque = new Deque<number>([], 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<number>;
const bucketSize = 10;
beforeEach(() => {
deque = new Deque<number>([], 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);
}
});
});

View file

@ -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<number>();
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<number>;
@ -24,117 +10,209 @@ describe('Queue', () => {
queue = new Queue<number>();
});
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<number>;
beforeEach(() => {
queue = new Queue<number>();
});
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<number>;
beforeEach(() => {
queue = new Queue<number>();
});
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<number>;
beforeEach(() => {
queue = new Queue<number>();
});
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<number>();
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<number>();
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<string>;
@ -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<number>();
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<number>();
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<number>;
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]);
});
});