UNPKG

data-structure-typed

Version:
1,006 lines (896 loc) 30.8 kB
import { BinaryTreeNode, BSTNode, TreeMultiMap, TreeMultiMapNode } from '../../../../src'; import { getRandomInt } from '../../../utils'; import { isDebugTest } from '../../../config'; import { costOfLiving } from './data/cost-of-living-by-country'; const isDebug = isDebugTest; // const isDebug = true; describe('TreeMultiMap 1', () => { let tmm: TreeMultiMap<number>; beforeEach(() => { tmm = new TreeMultiMap<number>(); }); it('Should add and delete values', () => { tmm.add(3, 3); tmm.add(3, 33); tmm.add(3, 333); expect(tmm.get(3)).toEqual([3, 33, 333]); tmm.deleteValue(3, 33); expect(tmm.get(3)).toEqual([3, 333]); tmm.deleteValue(3, 3); expect(tmm.get(3)).toEqual([333]); tmm.deleteValue(3, 333); expect(tmm.get(3)).toBe(undefined); tmm.add(3, 3); tmm.add([3, [3333, 33333]]); expect(tmm.get(3)).toEqual([3, 3333, 33333]); }); describe('add and getNode', () => { it('should add and find a node in the tmm', () => { tmm.add(10); tmm.add(20); tmm.add(5); expect(tmm.getNode(10)).toBeInstanceOf(TreeMultiMapNode); expect(tmm.getNode(20)).toBeInstanceOf(TreeMultiMapNode); expect(tmm.getNode(5)).toBeInstanceOf(TreeMultiMapNode); expect(tmm.getNode(15)).toBe(undefined); }); it('should add and find nodes with negative keys', () => { tmm.add(-10); tmm.add(-20); expect(tmm.getNode(-10)).toBeInstanceOf(TreeMultiMapNode); expect(tmm.getNode(-20)).toBeInstanceOf(TreeMultiMapNode); }); }); describe('deleteNode', () => { it('should delete a node from the tmm', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.delete(20); expect(tmm.getNode(20)).toBe(undefined); }); it('should handle deleting a non-existent node', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.delete(15); expect(tmm.getNode(15)).toBe(undefined); }); it('should getNode performance O(log n)', () => { for (let i = 0; i < 10; i++) tmm.add(i); tmm.getNode(6); }); }); describe('minimum', () => { it('should find the minimum node in the tmm', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.add(15); tmm.add(3); const minNode = tmm.getLeftMost(node => node, tmm.root); expect(minNode?.key).toBe(3); }); it('should handle an empty tmm', () => { const minNode = tmm.getLeftMost(node => node, tmm.root); expect(minNode).toBe(undefined); }); }); describe('getRightMost', () => { it('should find the getRightMost node in the tmm', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.add(15); tmm.add(25); const maxNode = tmm.getRightMost(node => node, tmm.root); expect(maxNode?.key).toBe(25); }); it('should handle an empty tmm', () => { const maxNode = tmm.getRightMost(node => node, tmm.root); expect(maxNode).toBe(undefined); }); }); describe('getSuccessor', () => { it('should find the getSuccessor of a node', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.add(15); tmm.add(25); const node = tmm.getNode(15); const successorNode = tmm.getSuccessor(node!); expect(successorNode?.key).toBe(20); }); it('should handle a node with no getSuccessor', () => { tmm.add(10); tmm.add(5); const node = tmm.getNode(10); const successorNode = tmm.getSuccessor(node!); // TODO not sure if it should be undefined or tmm.NIL expect(successorNode).toBe(undefined); }); }); describe('getPredecessor', () => { it('should find the getPredecessor of a node', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.add(15); tmm.add(25); const node = tmm.getNode(20); const predecessorNode = tmm.getPredecessor(node!); expect(predecessorNode?.key).toBe(15); }); it('should handle a node with no getPredecessor', () => { tmm.add(10); tmm.add(20); const node = tmm.getNode(20); const predecessorNode = tmm.getPredecessor(node!); // TODO not sure if it should be tmm.NIL or something else. expect(predecessorNode).toBe(tmm.getNode(20)); }); }); it('should the clone method', () => { function checkTreeStructure(tmm: TreeMultiMap<string, number>) { expect(tmm.size).toBe(4); expect(tmm.root?.key).toBe('2'); expect(tmm.root?.left?.key).toBe('1'); expect(tmm.root?.left?.left?.key).toBe(NaN); expect(tmm.root?.left?.right?.key).toBe(NaN); expect(tmm.root?.right?.key).toBe('4'); expect(tmm.root?.right?.left?.key).toBe(NaN); expect(tmm.root?.right?.right?.key).toBe('5'); } const tmm = new TreeMultiMap<string, number>(); tmm.addMany([ ['2', 2], ['4', 4], ['5', 5], ['3', 3], ['1', 1] ]); expect(tmm.size).toBe(5); expect(tmm.root?.key).toBe('2'); expect(tmm.root?.left?.key).toBe('1'); expect(tmm.root?.left?.left?.key).toBe(NaN); expect(tmm.root?.left?.right?.key).toBe(NaN); expect(tmm.root?.right?.key).toBe('4'); expect(tmm.root?.right?.left?.key).toBe('3'); expect(tmm.root?.right?.right?.key).toBe('5'); tmm.delete('3'); checkTreeStructure(tmm); const cloned = tmm.clone(); checkTreeStructure(cloned); cloned.delete('1'); expect(tmm.size).toBe(4); expect(cloned.size).toBe(3); }); it('should add value', () => { const tmm = new TreeMultiMap<number, string>([4, 5, [1, ['1']], 2, 3]); expect(tmm.get(1)).toEqual(['1']); expect(tmm.getNode(1)?.value).toEqual([]); tmm.add(1, 'a'); expect(tmm.get(1)).toEqual(['1', 'a']); tmm.add([1, ['b']]); expect(tmm.getNode(1)?.value).toEqual([]); expect(tmm.get(1)).toEqual(['1', 'a', 'b']); const tmmMapped = new TreeMultiMap<number>([4, 5, [1, ['1']], 2, 3]); expect(tmmMapped.get(1)).toEqual(['1']); expect(tmmMapped.getNode(1)?.value).toEqual([]); tmmMapped.add(1, 'a'); expect(tmmMapped.get(1)).toEqual(['1', 'a']); tmmMapped.add([1, ['b']]); expect(tmmMapped.getNode(1)?.value).toEqual([]); expect(tmmMapped.get(1)).toEqual(['1', 'a', 'b']); }); }); describe('TreeMultiMap 2', () => { let tmm: TreeMultiMap<number>; beforeEach(() => { tmm = new TreeMultiMap<number>(); }); it('should add nodes into the tmm', () => { tmm.add(10); expect(tmm.getNode(10)).toBeDefined(); tmm.add(20); expect(tmm.getNode(20)).toBeDefined(); tmm.add(5); expect(tmm.getNode(5)).toBeDefined(); }); it('should delete nodes from the tmm', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.delete(20); expect(tmm.getNode(20)).toBe(undefined); }); it('should get the successor of a node', () => { tmm.add(10); tmm.add(20); const node = tmm.getNode(10); const successor = tmm.getSuccessor(node!); expect(successor?.key).toBe(20); }); it('should get the predecessor of a node', () => { tmm.add(10); tmm.add(20); const node = tmm.getNode(20); const predecessor = tmm.getPredecessor(node!); expect(predecessor?.key).toBe(20); }); it('should rotate nodes to the left', () => { tmm.add(10); tmm.add(20); tmm.add(5); const node = tmm.getNode(10); tmm.add(15); // Verify that rotation has occurred expect(node?.left?.key).toBe(5); expect(node?.right?.key).toBe(20); }); it('should rotate nodes to the right', () => { tmm.add(10); tmm.add(20); tmm.add(5); const node = tmm.getNode(20); tmm.add(25); // Verify that rotation has occurred expect(node?.left?.key).toBeNaN(); expect(node?.right?.key).toBe(25); }); it('should all node attributes fully conform to the red-black tmm standards.', () => { tmm.add(10); tmm.add(20); tmm.add(5); tmm.add(15); tmm.add(21); tmm.add(6); tmm.add(2); let node10F = tmm.getNode(10); let node20F = tmm.getNode(20); let node5F = tmm.getNode(5); let node15F = tmm.getNode(15); let node21F = tmm.getNode(21); let node6F = tmm.getNode(6); let node2F = tmm.getNode(2); expect(node10F?.key).toBe(10); expect(node10F?.color).toBe('BLACK'); expect(node10F?.left).toBe(node5F); expect(node10F?.right).toBe(node20F); expect(node10F?.parent).toBe(undefined); expect(node20F?.key).toBe(20); expect(node20F?.color).toBe('BLACK'); expect(node20F?.left).toBe(node15F); expect(node20F?.right).toBe(node21F); expect(node20F?.parent).toBe(node10F); expect(node5F?.key).toBe(5); expect(node5F?.color).toBe('BLACK'); expect(node5F?.left).toBe(node2F); expect(node5F?.right).toBe(node6F); expect(node5F?.parent).toBe(node10F); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node20F); expect(node21F?.key).toBe(21); expect(node21F?.color).toBe('RED'); expect(node21F?.left).toBe(tmm.NIL); expect(node21F?.right).toBe(tmm.NIL); expect(node21F?.parent).toBe(node20F); expect(node6F?.key).toBe(6); expect(node6F?.color).toBe('RED'); expect(node6F?.left).toBe(tmm.NIL); expect(node6F?.right).toBe(tmm.NIL); expect(node6F?.parent).toBe(node5F); expect(node2F?.key).toBe(2); expect(node2F?.color).toBe('RED'); expect(node2F?.left).toBe(tmm.NIL); expect(node2F?.right).toBe(tmm.NIL); expect(node2F?.parent).toBe(node5F); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node20F); tmm.delete(5); node10F = tmm.getNode(10); node20F = tmm.getNode(20); node5F = tmm.getNode(5); node15F = tmm.getNode(15); node21F = tmm.getNode(21); node6F = tmm.getNode(6); node2F = tmm.getNode(2); expect(node10F?.key).toBe(10); expect(node10F?.color).toBe('BLACK'); expect(node10F?.left).toBe(node6F); expect(node10F?.right).toBe(node20F); expect(node10F?.parent).toBe(undefined); expect(node20F?.key).toBe(20); expect(node20F?.color).toBe('BLACK'); expect(node20F?.left).toBe(node15F); expect(node20F?.right).toBe(node21F); expect(node20F?.parent).toBe(node10F); expect(node5F).toBe(undefined); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node20F); expect(node21F?.key).toBe(21); expect(node21F?.color).toBe('RED'); expect(node21F?.left).toBe(tmm.NIL); expect(node21F?.right).toBe(tmm.NIL); expect(node21F?.parent).toBe(node20F); expect(node6F?.key).toBe(6); expect(node6F?.color).toBe('BLACK'); expect(node6F?.left).toBe(node2F); expect(node6F?.right).toBe(tmm.NIL); expect(node6F?.parent).toBe(node10F); expect(node2F?.key).toBe(2); expect(node2F?.color).toBe('RED'); expect(node2F?.left).toBe(tmm.NIL); expect(node2F?.right).toBe(tmm.NIL); expect(node2F?.parent).toBe(node6F); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node20F); tmm.delete(20); node10F = tmm.getNode(10); node20F = tmm.getNode(20); node5F = tmm.getNode(5); node15F = tmm.getNode(15); node21F = tmm.getNode(21); node6F = tmm.getNode(6); node2F = tmm.getNode(2); expect(node10F?.key).toBe(10); expect(node10F?.color).toBe('BLACK'); expect(node10F?.left).toBe(node6F); expect(node10F?.right).toBe(node21F); expect(node10F?.parent).toBe(undefined); expect(node20F).toBe(undefined); expect(node5F).toBe(undefined); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node21F); expect(node21F?.key).toBe(21); expect(node21F?.color).toBe('BLACK'); expect(node21F?.left).toBe(node15F); expect(node21F?.right).toBe(tmm.NIL); expect(node21F?.parent).toBe(node10F); expect(node6F?.key).toBe(6); expect(node6F?.color).toBe('BLACK'); expect(node6F?.left).toBe(node2F); expect(node6F?.right).toBe(tmm.NIL); expect(node6F?.parent).toBe(node10F); expect(node2F?.key).toBe(2); expect(node2F?.color).toBe('RED'); expect(node2F?.left).toBe(tmm.NIL); expect(node2F?.right).toBe(tmm.NIL); expect(node2F?.parent).toBe(node6F); expect(node15F?.key).toBe(15); expect(node15F?.color).toBe('RED'); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(node21F); }); it('should fix the tmm after insertion', () => { tmm.add(1); tmm.add(2); tmm.add(5); tmm.add(15); const node15F = tmm.getNode(15); expect(node15F?.left).toBe(tmm.NIL); expect(node15F?.right).toBe(tmm.NIL); expect(node15F?.parent).toBe(tmm.getNode(5)); tmm.add(25); tmm.add(10); tmm.add(8); tmm.add(28); tmm.add(111); tmm.add(12); tmm.delete(2); tmm.add(22); tmm.add(50); tmm.add(155); tmm.add(225); const node225F = tmm.getNode(225); expect(node225F?.left).toBe(tmm.NIL); expect(node225F?.right).toBe(tmm.NIL); expect(node225F?.parent?.key).toBe(155); tmm.add(7); if (isDebug) tmm.print(); const node15S = tmm.getNode(15); expect(node15S?.left?.key).toBe(10); expect(node15S?.right?.key).toBe(25); expect(tmm.root).toBe(tmm.getNode(8)); expect(node15S?.parent?.key).toBe(28); tmm.delete(15); expect(tmm.root?.key).toBe(8); expect(tmm.root?.parent).toBe(undefined); const node15T = tmm.getNode(15); expect(node15T).toBe(undefined); tmm.add(23); tmm.add(33); tmm.add(15); const nodeLM = tmm.getLeftMost(); expect(nodeLM).toBe(1); const node50 = tmm.getNode(50); expect(node50?.key).toBe(50); expect(node50?.left?.key).toBe(33); expect(node50?.right).toBe(tmm.NIL); const node15Fo = tmm.getNode(15); expect(node15Fo?.key).toBe(15); expect(node15Fo?.left).toBe(tmm.NIL); const node225S = tmm.getNode(225); expect(node225S?.left).toBe(tmm.NIL); expect(node225S?.right).toBe(tmm.NIL); expect(node225S?.parent?.key).toBe(155); // TODO // expect(tmm.getNode(0)).toBe(undefined); tmm.add(2); tmm.add(3); tmm.add(4); tmm.add(6); tmm.add(9); tmm.add(11); tmm.add(13); tmm.add(14); tmm.add(16); tmm.add(17); tmm.add(18); tmm.add(19); tmm.add(110); if (isDebug) tmm.print(); expect(tmm.dfs()).toEqual([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 25, 28, 33, 50, 110, 111, 155, 225 ]); expect(tmm.isBST()).toBe(true); }); it('should fix the tmm after insertion and deletion', () => { for (let i = 0; i < 100; i++) { tmm.add(i); } for (let i = 0; i < 49; i++) { tmm.delete(i); } expect(tmm.size).toBe(51); expect(tmm.isBST()).toBe(true); expect(tmm.isBST(tmm.root, 'RECURSIVE')).toBe(true); expect(tmm.dfs(n => n.key, 'IN', false, tmm.root, 'ITERATIVE')).toEqual([ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 ]); expect(tmm.dfs(n => n.key, 'IN', false, tmm.root, 'RECURSIVE')).toEqual([ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 ]); }); it('should fix the tmm after large scale insertion and deletion', () => { for (let i = 0; i < 10000; i++) { tmm.add(i); } for (let i = 0; i < 10000; i++) { tmm.delete(i); } expect(tmm.size).toBe(0); expect(tmm.isBST()).toBe(true); expect(tmm.dfs(n => n.key, 'IN', false, tmm.root, 'ITERATIVE')).toEqual([]); tmm.clear(); for (let i = 0; i < 1000; i++) { tmm.add(getRandomInt(-100, 1000)); tmm.delete(getRandomInt(-100, 1000)); } // TODO there is a bug when dfs the tmm with NIL node // expect(tmm.isBST()).toBe(true); }); it('duplicates', () => { tmm.addMany([9, 8, 7, 8, 8, 8, 2, 3, 6, 5, 5, 4]); if (isDebug) tmm.print(); expect(tmm.size).toBe(8); expect(tmm.isBST()).toBe(true); expect(tmm.isAVLBalanced()).toBe(true); tmm.addMany([10, 5, 2, 11]); expect(tmm.size).toBe(10); expect(tmm.isBST()).toBe(true); expect(tmm.isAVLBalanced()).toBe(true); tmm.clear(); tmm.addMany([10, 20, 30, 40, 50, 60]); expect(tmm.isAVLBalanced()).toBe(false); }); describe('TreeMultiMap delete test', function () { const tmm = new TreeMultiMap<number, number>(); const inputSize = 100; // Adjust input sizes as needed beforeEach(() => { tmm.clear(); }); it('The structure remains normal after random deletion', function () { for (let i = 0; i < inputSize; i++) { tmm.add(i); } for (let i = 0; i < inputSize; i++) { const num = getRandomInt(0, inputSize - 1); tmm.delete(num); } let nanCount = 0; const dfs = (cur: TreeMultiMapNode<number>) => { if (isNaN(cur.key)) nanCount++; if (cur.left) dfs(cur.left); if (cur.right) dfs(cur.right); }; if (tmm.root) dfs(tmm.root); expect(tmm.size).toBeLessThanOrEqual(inputSize); expect(tmm.getHeight()).toBeLessThan(Math.log2(inputSize) * 2); expect(nanCount).toBeLessThanOrEqual(inputSize); }); it(`Random additions, complete deletions of structures are normal`, function () { for (let i = 0; i < inputSize; i++) { const num = getRandomInt(0, inputSize - 1); if (i === 0 && isDebug) console.log(`first:`, num); tmm.add(num); } for (let i = 0; i < inputSize; i++) { tmm.delete(i); } let nanCount = 0; const dfs = (cur: TreeMultiMapNode<number>) => { if (isNaN(cur.key)) nanCount++; if (cur.left) dfs(cur.left); if (cur.right) dfs(cur.right); }; if (tmm.root) dfs(tmm.root); expect(tmm.size).toBe(0); expect(tmm.getHeight()).toBe(-1); expect(nanCount).toBeLessThanOrEqual(inputSize); if (isDebug) tmm.print(); }); }); describe('TreeMultiMap iterative methods test', () => { let tmm: TreeMultiMap<number, string, object, string, string, object>; beforeEach(() => { tmm = new TreeMultiMap(); tmm.add([1, ['a']]); tmm.add(2, 'b'); tmm.add([3, ['c']]); }); it('The node obtained by get Node should match the node type', () => { const node3 = tmm.getNode(3); expect(node3).toBeInstanceOf(BinaryTreeNode); expect(node3).toBeInstanceOf(BSTNode); expect(node3).toBeInstanceOf(TreeMultiMapNode); }); it('forEach should iterate over all elements', () => { const mockCallback = jest.fn(); tmm.forEach((key, value) => { mockCallback(key, value); }); expect(mockCallback.mock.calls.length).toBe(3); expect(mockCallback.mock.calls[0]).toEqual([1, ['a']]); expect(mockCallback.mock.calls[1]).toEqual([2, ['b']]); expect(mockCallback.mock.calls[2]).toEqual([3, ['c']]); }); it('filter should return a new tmm with filtered elements', () => { const filteredTree = tmm.filter(key => key > 1); expect(filteredTree.size).toBe(2); expect([...filteredTree]).toEqual([ [2, ['b']], [3, ['c']] ]); }); it('map should return a new tmm with modified elements', () => { const tmmMapped = tmm.map((key, value) => [(key * 2).toString(), value ? value : []]); expect(tmmMapped.size).toBe(3); expect([...tmmMapped]).toEqual([ ['2', ['a']], ['4', ['b']], ['6', ['c']] ]); }); it('reduce should accumulate values', () => { const sum = tmm.reduce((acc, value, key) => acc + key, 0); expect(sum).toBe(6); }); it('[Symbol.iterator] should provide an iterator', () => { const entries = []; for (const entry of tmm) { entries.push(entry); } expect(entries.length).toBe(3); expect(entries).toEqual([ [1, ['a']], [2, ['b']], [3, ['c']] ]); }); }); }); describe('TreeMultiMap - _deleteFixup', () => { let tmm: TreeMultiMap<number, number>; beforeEach(() => { tmm = new TreeMultiMap(); }); it('should handle deleting a red leaf node', () => { tmm.add(10, 10); tmm.add(5, 5); // Red leaf tmm.add(20, 20); expect(tmm.delete(5)).toHaveLength(1); // Delete red leaf expect(tmm.root?.left).toBe(tmm.NIL); // Left child should be NIL }); it('should handle deleting a black leaf node', () => { tmm.add(10, 10); tmm.add(5, 5); // Black node tmm.add(20, 20); tmm.add(1, 1); // Black leaf node expect(tmm.delete(1)).toHaveLength(1); // Delete black leaf expect(tmm.root?.left?.left).toBe(tmm.NIL); }); it('should handle deleting black node with red sibling', () => { tmm.add(10, 10); tmm.add(5, 5); // Black node tmm.add(20, 20); // Red sibling tmm.add(25, 25); // Force the sibling to be red expect(tmm.delete(5)).toHaveLength(1); // Delete black node expect(tmm.root?.right?.color).toBe('BLACK'); // Ensure sibling color is black after fixup }); it('should handle deleting black node with black sibling', () => { tmm.add(10, 10); tmm.add(5, 5); // Black node tmm.add(20, 20); // Black sibling expect(tmm.delete(5)).toHaveLength(1); // Delete black node expect(tmm.root?.left).toBe(tmm.NIL); }); it('should handle deleting the root node', () => { tmm.add(10, 10); // Root node tmm.add(5, 5); tmm.add(20, 20); expect(tmm.delete(10)).toHaveLength(1); // Delete root node expect(tmm.root?.key).toBe(20); // New root should be 20 }); it('should handle complex case with multiple rotations', () => { tmm.add(10, 10); tmm.add(5, 5); tmm.add(15, 15); tmm.add(12, 12); tmm.add(18, 18); tmm.add(16, 16); // Delete a node that will cause rotations and color changes expect(tmm.delete(5)).toHaveLength(1); // Verify the color and structure after fixup expect(tmm.root?.color).toBe('BLACK'); expect(tmm.root?.left).toBe(tmm.NIL); expect(tmm.root?.right?.left?.color).toBe('BLACK'); }); it('should handle complex delete fixup scenarios', () => { const tmm = new TreeMultiMap<number, number>(); // Build a tmm that will require complex fixup tmm.add(20, 20); tmm.add(10, 10); tmm.add(30, 30); tmm.add(5, 5); tmm.add(15, 15); tmm.add(25, 25); tmm.add(35, 35); tmm.add(2, 2); tmm.add(8, 8); // This deletion should trigger a complex fixup tmm.delete(2); // tmm.print(tmm.root, { isShowNull: true, isShowRedBlackNIL: true, isShowUndefined: false }); expect(tmm.isLeaf(2)).toBe(false); expect(tmm.isLeaf(8)).toBe(true); expect(tmm.isLeaf(15)).toBe(true); expect(tmm.isLeaf(25)).toBe(true); expect(tmm.isLeaf(35)).toBe(true); expect(tmm.isLeaf(20)).toBe(false); expect(tmm.isLeaf(30)).toBe(false); // Verify tmm structure and colors after fixup expect(tmm.root?.color).toBe('BLACK'); expect(tmm.root?.key).toBe(20); expect(tmm.root?.left?.color).toBe('RED'); expect(tmm.root?.left?.key).toBe(10); expect(tmm.root?.right?.color).toBe('BLACK'); expect(tmm.root?.right?.key).toBe(30); expect(tmm.root?.left?.left?.color).toBe('BLACK'); expect(tmm.root?.left?.left?.key).toBe(5); expect(tmm.root?.left?.right?.color).toBe('BLACK'); expect(tmm.root?.left?.right?.key).toBe(15); expect(tmm.leaves(node => (node === null ? '' : `${node.key} ${node.color}`), tmm.root, 'RECURSIVE')).toEqual([ '8 RED', '15 BLACK', '25 RED', '35 RED' ]); expect(tmm.listLevels(node => (node === tmm.NIL ? 'NIL' : `${node.key} ${node.color}`))).toEqual([ ['20 BLACK'], ['10 RED', '30 BLACK'], ['5 BLACK', '15 BLACK', '25 RED', '35 RED'], ['NIL', '8 RED', 'NIL', 'NIL', 'NIL', 'NIL', 'NIL', 'NIL'], ['NIL', 'NIL'] ]); }); }); describe('real world data', () => { it('cost of living', () => { const indexedByRank = new TreeMultiMap(costOfLiving, { specifyComparable: node => node.rank, toEntryFn: raw => [raw, undefined] }); expect(indexedByRank.size).toBe(7); expect(indexedByRank.dfs(node => node?.key?.country)).toEqual([ 'Switzerland', 'New Zealand', 'Mexico', 'South Africa', 'Japan', 'Brazil', 'Taiwan' ]); }); }); describe('classic use', () => { // Test suite for TreeMultiMap with player ranking and equipment it('@example players ranked by score with their equipment', () => { type Equipment = { name: string; // Equipment name quality: 'legendary' | 'epic' | 'rare' | 'common'; level: number; }; type Player = { name: string; score: number; equipments: Equipment[]; }; // Mock player data with their scores and equipment const players: Player[] = [ { name: 'DragonSlayer', score: 8750, equipments: [ { name: 'AWM', quality: 'legendary', level: 85 }, { name: 'Level 3 Helmet', quality: 'epic', level: 80 }, { name: 'Extended Quickdraw Mag', quality: 'rare', level: 75 }, { name: 'Compensator', quality: 'epic', level: 78 }, { name: 'Vertical Grip', quality: 'rare', level: 72 } ] }, { name: 'ShadowNinja', score: 7200, equipments: [ { name: 'M416', quality: 'epic', level: 75 }, { name: 'Ghillie Suit', quality: 'rare', level: 70 }, { name: 'Red Dot Sight', quality: 'common', level: 65 }, { name: 'Extended QuickDraw Mag', quality: 'rare', level: 68 } ] }, { name: 'RuneMaster', score: 9100, equipments: [ { name: 'KAR98K', quality: 'legendary', level: 90 }, { name: 'Level 3 Vest', quality: 'legendary', level: 85 }, { name: 'Holographic Sight', quality: 'epic', level: 82 }, { name: 'Suppressor', quality: 'legendary', level: 88 }, { name: 'Level 3 Backpack', quality: 'epic', level: 80 } ] }, { name: 'BattleKing', score: 8500, equipments: [ { name: 'AUG', quality: 'epic', level: 82 }, { name: 'Red Dot Sight', quality: 'rare', level: 75 }, { name: 'Extended Mag', quality: 'common', level: 70 }, { name: 'Tactical Stock', quality: 'rare', level: 76 } ] }, { name: 'SniperElite', score: 7800, equipments: [ { name: 'M24', quality: 'legendary', level: 88 }, { name: 'Compensator', quality: 'epic', level: 80 }, { name: 'Scope 8x', quality: 'legendary', level: 85 }, { name: 'Level 2 Helmet', quality: 'rare', level: 75 } ] }, { name: 'RushMaster', score: 7500, equipments: [ { name: 'Vector', quality: 'rare', level: 72 }, { name: 'Level 2 Helmet', quality: 'common', level: 65 }, { name: 'Quickdraw Mag', quality: 'common', level: 60 }, { name: 'Laser Sight', quality: 'rare', level: 68 } ] }, { name: 'GhostWarrior', score: 8200, equipments: [ { name: 'SCAR-L', quality: 'epic', level: 78 }, { name: 'Extended Quickdraw Mag', quality: 'rare', level: 70 }, { name: 'Holographic Sight', quality: 'epic', level: 75 }, { name: 'Suppressor', quality: 'rare', level: 72 }, { name: 'Vertical Grip', quality: 'common', level: 65 } ] }, { name: 'DeathDealer', score: 7300, equipments: [ { name: 'SKS', quality: 'epic', level: 76 }, { name: 'Holographic Sight', quality: 'rare', level: 68 }, { name: 'Extended Mag', quality: 'common', level: 65 } ] }, { name: 'StormRider', score: 8900, equipments: [ { name: 'MK14', quality: 'legendary', level: 92 }, { name: 'Level 3 Backpack', quality: 'legendary', level: 85 }, { name: 'Scope 8x', quality: 'epic', level: 80 }, { name: 'Suppressor', quality: 'legendary', level: 88 }, { name: 'Tactical Stock', quality: 'rare', level: 75 } ] }, { name: 'CombatLegend', score: 7600, equipments: [ { name: 'UMP45', quality: 'rare', level: 74 }, { name: 'Level 2 Vest', quality: 'common', level: 67 }, { name: 'Red Dot Sight', quality: 'common', level: 62 }, { name: 'Extended Mag', quality: 'rare', level: 70 } ] } ]; // Create a TreeMultiMap for player rankings const playerRankings = new TreeMultiMap<number, Equipment, Player>(players, { toEntryFn: ({ score, equipments }) => [score, equipments], isMapMode: false }); const topPlayersEquipments = playerRankings.rangeSearch([8900, 10000], node => playerRankings.get(node)); expect(topPlayersEquipments).toEqual([ [ { name: 'MK14', quality: 'legendary', level: 92 }, { name: 'Level 3 Backpack', quality: 'legendary', level: 85 }, { name: 'Scope 8x', quality: 'epic', level: 80 }, { name: 'Suppressor', quality: 'legendary', level: 88 }, { name: 'Tactical Stock', quality: 'rare', level: 75 } ], [ { name: 'KAR98K', quality: 'legendary', level: 90 }, { name: 'Level 3 Vest', quality: 'legendary', level: 85 }, { name: 'Holographic Sight', quality: 'epic', level: 82 }, { name: 'Suppressor', quality: 'legendary', level: 88 }, { name: 'Level 3 Backpack', quality: 'epic', level: 80 } ] ]); }); });