UNPKG

data-structure-typed

Version:
633 lines (543 loc) 17.2 kB
import { FibonacciHeap, Heap, MaxHeap, MinHeap } from '../../../../src'; import { logBigOMetricsWrap } from '../../../utils'; describe('Heap Operation Test', () => { it('should initiate heap', function () { const hp = new Heap<number>(); hp.add(1); expect(hp.size).toBe(1); }); it('should heap add and delete work well', function () { const hp = new MinHeap<number>(); expect(hp.delete(1)).toBe(false); hp.add(2); hp.add(3); hp.add(1); hp.add(4); hp.add(6); hp.add(5); hp.add(7); hp.delete(4); hp.delete(7); hp.delete(1); expect(hp.size).toBe(4); expect(hp.peek()).toBe(2); }); it('should numeric heap work well', function () { const minNumHeap = new MinHeap<number>(); expect(minNumHeap.poll()).toBe(undefined); minNumHeap.add(1); minNumHeap.add(6); minNumHeap.add(2); minNumHeap.add(0); minNumHeap.add(5); minNumHeap.add(9); expect(minNumHeap.has(1)).toBe(true); expect(minNumHeap.has(2)).toBe(true); expect(minNumHeap.poll()).toBe(0); expect(minNumHeap.poll()).toBe(1); expect(minNumHeap.peek()).toBe(2); expect(!minNumHeap.has(1)); expect(minNumHeap.has(2)); const arrFromHeap = minNumHeap.toArray(); expect(arrFromHeap.length).toBe(4); expect(arrFromHeap[0]).toBe(2); expect(arrFromHeap[1]).toBe(5); expect(arrFromHeap[2]).toBe(9); expect(arrFromHeap[3]).toBe(6); expect(minNumHeap.sort()).toEqual([2, 5, 6, 9]); }); it('should clone', function () { const minNumHeap = new Heap<string>([], { comparator: (a, b) => Number(a) - Number(b) }); minNumHeap.add('1'); minNumHeap.add('6'); minNumHeap.add('2'); minNumHeap.add('0'); minNumHeap.add('5'); minNumHeap.add('9'); minNumHeap.delete('2'); expect([...minNumHeap]).toEqual(['0', '1', '9', '6', '5']); const cloned = minNumHeap.clone(); expect([...cloned]).toEqual(['0', '1', '9', '6', '5']); minNumHeap.delete('5'); expect([...minNumHeap]).toEqual(['0', '1', '9', '6']); expect([...cloned]).toEqual(['0', '1', '9', '6', '5']); }); it('should object heap work well', function () { const minHeap = new MinHeap<{ a: string; key: number; }>([], { comparator: (a, b) => a.key - b.key }); minHeap.add({ key: 1, a: 'a1' }); minHeap.add({ key: 6, a: 'a6' }); minHeap.add({ key: 2, a: 'a2' }); minHeap.add({ key: 0, a: 'a0' }); expect(minHeap.peek()).toEqual({ a: 'a0', key: 0 }); expect(minHeap.toArray().map(item => ({ a: item.a }))).toEqual([ { a: 'a0' }, { a: 'a1' }, { a: 'a2' }, { a: 'a6' } ]); let i = 0; const expectPolled = [{ a: 'a0' }, { a: 'a1' }, { a: 'a2' }, { a: 'a6' }]; while (minHeap.size > 0) { expect({ a: minHeap.poll()?.a }).toEqual(expectPolled[i]); i++; } const maxHeap = new MaxHeap<{ key: number; a: string; }>([], { comparator: (a, b) => b.key - a.key }); maxHeap.add({ key: 1, a: 'a1' }); maxHeap.add({ key: 6, a: 'a6' }); maxHeap.add({ key: 5, a: 'a5' }); maxHeap.add({ key: 2, a: 'a2' }); maxHeap.add({ key: 0, a: 'a0' }); maxHeap.add({ key: 9, a: 'a9' }); expect(maxHeap.peek()).toEqual({ a: 'a9', key: 9 }); expect(maxHeap.toArray().map(item => ({ a: item.a }))).toEqual([ { a: 'a9' }, { a: 'a2' }, { a: 'a6' }, { a: 'a1' }, { a: 'a0' }, { a: 'a5' } ]); const maxExpectPolled = [{ a: 'a9' }, { a: 'a6' }, { a: 'a5' }, { a: 'a2' }, { a: 'a1' }, { a: 'a0' }]; let maxI = 0; while (maxHeap.size > 0) { expect({ a: maxHeap.poll()?.a }).toEqual(maxExpectPolled[maxI]); maxI++; } }); it('should object heap map & filter', function () { const minHeap = new MinHeap<{ a: string; key: number; }>( [ { key: 1, a: 'a1' }, { key: 6, a: 'a6' }, { key: 5, a: 'a5' }, { key: 3, a: 'a3' }, { key: 2, a: 'a2' }, { key: 4, a: 'a4' }, { key: 0, a: 'a0' } ], { comparator: (a, b) => a.key - b.key } ); const mappedMinHeap = minHeap.map( item => item.key, (a, b) => a - b ); expect(mappedMinHeap.peek()).toBe(0); expect(mappedMinHeap.sort()).toEqual([0, 1, 2, 3, 4, 5, 6]); const mappedToElementFnMinHeap = minHeap.map<string, { id: string }>( item => item.key.toString(), (a, b) => Number(a) - Number(b), rawElement => rawElement.id ); expect(mappedToElementFnMinHeap.peek()).toBe('0'); expect(mappedToElementFnMinHeap.sort()).toEqual(['0', '1', '2', '3', '4', '5', '6']); const filteredHeap = minHeap.filter(item => item.key > 3); expect(filteredHeap.peek()).toEqual({ a: 'a4', key: 4 }); expect(filteredHeap.sort()).toEqual([ { a: 'a4', key: 4 }, { a: 'a5', key: 5 }, { a: 'a6', key: 6 } ]); }); it('should object heap', () => { const heap = new Heap<{ rawItem: { id: number }; }>( [ { rawItem: { id: 4 } }, { rawItem: { id: 8 } }, { rawItem: { id: 6 } }, { rawItem: { id: 7 } }, { rawItem: { id: 1 } }, { rawItem: { id: 3 } }, { rawItem: { id: 5 } } ], { comparator: (a, b) => a.rawItem.id - b.rawItem.id } ); expect([...heap.sort()]).toEqual([ { rawItem: { id: 1 } }, { rawItem: { id: 3 } }, { rawItem: { id: 4 } }, { rawItem: { id: 5 } }, { rawItem: { id: 6 } }, { rawItem: { id: 7 } }, { rawItem: { id: 8 } } ]); }); it('should toElementFn', () => { const heap = new Heap<number, { rawItem: { id: number } }>( [ { rawItem: { id: 4 } }, { rawItem: { id: 8 } }, { rawItem: { id: 6 } }, { rawItem: { id: 7 } }, { rawItem: { id: 1 } }, { rawItem: { id: 3 } }, { rawItem: { id: 5 } } ], { toElementFn: rawElement => rawElement.rawItem.id } ); expect([...heap.sort()]).toEqual([1, 3, 4, 5, 6, 7, 8]); }); it('should getter leaf', function () { const hp = new Heap<number>(); expect(hp.leaf).toBe(undefined); hp.add(1); expect(hp.leaf).toBe(1); }); it('should error', function () { expect(() => { new Heap([{ key: 1 }, { key: 2 }, { key: 3 }]); }).toThrow( "When comparing object types, a custom comparator must be defined in the constructor's options parameter." ); }); }); describe('Heap HOF', () => { let hp: Heap; beforeEach(() => { hp = new Heap([{ key: 1 }, { key: 2 }, { key: 3 }], { comparator: (a, b) => a.key - b.key }); }); it('should filter', () => { const filtered = hp.filter(({ key }) => key % 2 === 1); expect([...filtered]).toEqual([{ key: 1 }, { key: 3 }]); }); it('should map', () => { const mapped = hp.map( ({ key }) => [key, key], (a, b) => a[0] - b[0] ); expect([...mapped]).toEqual([ [1, 1], [2, 2], [3, 3] ]); }); }); describe('FibonacciHeap', () => { let heap: FibonacciHeap<number>; beforeEach(() => { heap = new FibonacciHeap<number>(); }); it('push & peek', () => { heap.push(10); heap.push(5); expect(heap.peek()).toBe(5); }); it('pop', () => { heap.push(10); heap.push(5); heap.push(15); expect(heap.pop()).toBe(5); expect(heap.pop()).toBe(10); expect(heap.pop()).toBe(15); }); it('pop on an empty heap', () => { expect(heap.pop()).toBeUndefined(); }); it('size', () => { expect(heap.size).toBe(0); heap.push(10); expect(heap.size).toBe(1); heap.pop(); expect(heap.size).toBe(0); }); it('clear', () => { heap.push(10); heap.push(5); heap.clear(); expect(heap.size).toBe(0); expect(heap.peek()).toBeUndefined(); }); it('custom comparator', () => { const maxHeap = new FibonacciHeap<number>((a, b) => b - a); maxHeap.push(10); maxHeap.push(5); expect(maxHeap.peek()).toBe(10); }); }); describe('FibonacciHeap', () => { let heap: FibonacciHeap<number>; beforeEach(() => { heap = new FibonacciHeap<number>(); }); it('should initialize an empty heap', () => { expect(heap.size).toBe(0); expect(heap.peek()).toBeUndefined(); }); it('should push items into the heap and update size', () => { heap.push(10); heap.push(5); expect(heap.size).toBe(2); }); it('should peek the minimum item', () => { heap.push(10); heap.push(5); heap.push(15); expect(heap.peek()).toBe(5); }); it('should pop the minimum item and update size', () => { heap.push(10); heap.push(5); heap.push(15); const minItem = heap.pop(); expect(minItem).toBe(5); expect(heap.size).toBe(2); }); it('should correctly merge two heaps', () => { const heap1 = new FibonacciHeap<number>(); const heap2 = new FibonacciHeap<number>(); heap1.push(10); heap2.push(5); heap1.merge(heap2); expect(heap1.size).toBe(2); expect(heap1.peek()).toBe(5); }); it('should clear the heap', () => { heap.push(10); heap.push(5); heap.clear(); expect(heap.size).toBe(0); expect(heap.peek()).toBeUndefined(); }); it('should handle custom comparators', () => { const customComparator = (a: number, b: number) => b - a; const customHeap = new FibonacciHeap<number>(customComparator); customHeap.push(10); customHeap.push(5); customHeap.push(15); expect(customHeap.peek()).toBe(15); }); describe('FibonacciHeap Merge', () => { it('should merge two Fibonacci heaps correctly', () => { const heap1 = new FibonacciHeap<number>(); heap1.push(5).push(10); const heap2 = new FibonacciHeap<number>(); heap2.push(3).push(7); heap1.merge(heap2); expect(heap1.size).toBe(4); // Combined size of both heaps expect(heap2.size).toBe(0); // Merged heap should be empty expect(heap1.peek()).toBe(3); // Minimum element should be 3 }); }); }); describe('FibonacciHeap Stress Test', () => { it('should handle a large number of elements efficiently', () => { const testByMagnitude = (magnitude: number) => { const heap = new FibonacciHeap<number>(); // Add 1000 elements to the heap for (let i = 1; i <= magnitude; i++) { heap.push(i); } // Verify that the minimum element is 1 (smallest element) expect(heap.peek()).toBe(1); // Remove all 1000 elements from the heap const elements = []; while (heap.size > 0) { elements.push(heap.pop()); } // Verify that all elements were removed in ascending order for (let i = 1; i <= magnitude; i++) { expect(elements[i - 1]).toBe(i); } // Verify that the heap is now empty expect(heap.size).toBe(0); }; testByMagnitude(1000); // [ // 10, 100, 1000, 5000, 10000, 20000, 50000, 75000, 100000, // 150000, 200000, 250000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000 // ].forEach(m => logBigOMetricsWrap<typeof testByMagnitude>(testByMagnitude, [m])); // [ // 10, 100, 1000, 5000, 10000, 20000, 50000, 75000, 100000, 150000, 200000, 250000, 300000, 400000, 500000, 600000, // 700000, 800000, 900000, 1000000 // ] [10, 100, 1000, 5000].forEach(m => logBigOMetricsWrap( (c: number) => { const result: number[] = []; for (let i = 0; i < c; i++) result.push(i); return result; }, [m], 'loopPush' ) ); }); }); describe('classic use', () => { it('@example Use Heap to sort an array', () => { function heapSort(arr: number[]): number[] { const heap = new Heap<number>(arr, { comparator: (a, b) => a - b }); const sorted: number[] = []; while (!heap.isEmpty()) { sorted.push(heap.poll()!); // Poll minimum element } return sorted; } const array = [5, 3, 8, 4, 1, 2]; expect(heapSort(array)).toEqual([1, 2, 3, 4, 5, 8]); }); it('@example Use Heap to solve top k problems', () => { function topKElements(arr: number[], k: number): number[] { const heap = new Heap<number>([], { comparator: (a, b) => b - a }); // Max heap arr.forEach(num => { heap.add(num); if (heap.size > k) heap.poll(); // Keep the heap size at K }); return heap.toArray(); } const numbers = [10, 30, 20, 5, 15, 25]; expect(topKElements(numbers, 3)).toEqual([15, 10, 5]); }); it('@example Use Heap to merge sorted sequences', () => { function mergeSortedSequences(sequences: number[][]): number[] { const heap = new Heap<{ value: number; seqIndex: number; itemIndex: number }>([], { comparator: (a, b) => a.value - b.value // Min heap }); // Initialize heap sequences.forEach((seq, seqIndex) => { if (seq.length) { heap.add({ value: seq[0], seqIndex, itemIndex: 0 }); } }); const merged: number[] = []; while (!heap.isEmpty()) { const { value, seqIndex, itemIndex } = heap.poll()!; merged.push(value); if (itemIndex + 1 < sequences[seqIndex].length) { heap.add({ value: sequences[seqIndex][itemIndex + 1], seqIndex, itemIndex: itemIndex + 1 }); } } return merged; } const sequences = [ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]; expect(mergeSortedSequences(sequences)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); }); it('@example Use Heap to dynamically maintain the median', () => { class MedianFinder { private low: MaxHeap<number>; // Max heap, stores the smaller half private high: MinHeap<number>; // Min heap, stores the larger half constructor() { this.low = new MaxHeap<number>([]); this.high = new MinHeap<number>([]); } addNum(num: number): void { if (this.low.isEmpty() || num <= this.low.peek()!) this.low.add(num); else this.high.add(num); // Balance heaps if (this.low.size > this.high.size + 1) this.high.add(this.low.poll()!); else if (this.high.size > this.low.size) this.low.add(this.high.poll()!); } findMedian(): number { if (this.low.size === this.high.size) return (this.low.peek()! + this.high.peek()!) / 2; return this.low.peek()!; } } const medianFinder = new MedianFinder(); medianFinder.addNum(10); expect(medianFinder.findMedian()).toBe(10); medianFinder.addNum(20); expect(medianFinder.findMedian()).toBe(15); medianFinder.addNum(30); expect(medianFinder.findMedian()).toBe(20); medianFinder.addNum(40); expect(medianFinder.findMedian()).toBe(25); medianFinder.addNum(50); expect(medianFinder.findMedian()).toBe(30); }); it('@example Use Heap for load balancing', () => { function loadBalance(requests: number[], servers: number): number[] { const serverHeap = new Heap<{ id: number; load: number }>([], { comparator: (a, b) => a.load - b.load }); // min heap const serverLoads = new Array(servers).fill(0); for (let i = 0; i < servers; i++) { serverHeap.add({ id: i, load: 0 }); } requests.forEach(req => { const server = serverHeap.poll()!; serverLoads[server.id] += req; server.load += req; serverHeap.add(server); // The server after updating the load is re-entered into the heap }); return serverLoads; } const requests = [5, 2, 8, 3, 7]; expect(loadBalance(requests, 3)).toEqual([12, 8, 5]); }); it('@example Use Heap to schedule tasks', () => { type Task = [string, number]; function scheduleTasks(tasks: Task[], machines: number): Map<number, Task[]> { const machineHeap = new Heap<{ id: number; load: number }>([], { comparator: (a, b) => a.load - b.load }); // Min heap const allocation = new Map<number, Task[]>(); // Initialize the load on each machine for (let i = 0; i < machines; i++) { machineHeap.add({ id: i, load: 0 }); allocation.set(i, []); } // Assign tasks tasks.forEach(([task, load]) => { const machine = machineHeap.poll()!; allocation.get(machine.id)!.push([task, load]); machine.load += load; machineHeap.add(machine); // The machine after updating the load is re-entered into the heap }); return allocation; } const tasks: Task[] = [ ['Task1', 3], ['Task2', 1], ['Task3', 2], ['Task4', 5], ['Task5', 4] ]; const expectedMap = new Map<number, Task[]>(); expectedMap.set(0, [ ['Task1', 3], ['Task4', 5] ]); expectedMap.set(1, [ ['Task2', 1], ['Task3', 2], ['Task5', 4] ]); expect(scheduleTasks(tasks, 2)).toEqual(expectedMap); }); });