diff --git a/package.json b/package.json index 631f8b9..1de45ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dastal", - "version": "1.3.1", + "version": "1.4.0", "description": "Data Structures & Algorithms implementations", "main": "lib/src/index.js", "types": "lib/src/index.d.ts", diff --git a/src/heap/binaryHeap.ts b/src/heap/binaryHeap.ts index bb0ff35..8f73faa 100644 --- a/src/heap/binaryHeap.ts +++ b/src/heap/binaryHeap.ts @@ -1,28 +1,34 @@ -import { bubbleUp, heapify, sinkDown } from './heapify'; -import { Comparator } from '.'; +import { CompareFn } from '..'; import { Heap } from './heap'; - -export abstract class BinaryHeap implements Heap { - protected _comparator: Comparator; - protected array: Array; - - constructor(comparator: Comparator, array: Array = []) { - this._comparator = comparator; - this.array = array; +import { bubbleUp, heapify, sinkDown } from './utils'; + +/** + * + */ +export class BinaryHeap implements Heap { + /** + * @ignore + */ + protected array: T[]; + /** + * @ignore + */ + protected compare: CompareFn; + + constructor(compareFn: CompareFn, elements?: Iterable) { + this.compare = compareFn; + this.array = Array.from(elements ?? []); + heapify(this.compare, this.array); } - protected abstract isAboveOrEqual(a: T, b: T): boolean; - abstract heapify(...iterables: Iterable[]): BinaryHeap; - clear(): void { this.array.length = 0; } - comparator(): Comparator { - return this._comparator; + comparator(): CompareFn { + return this.compare; } - /* contains(element: T): boolean { return this.array.indexOf(element) >= 0; } @@ -42,17 +48,32 @@ export abstract class BinaryHeap implements Heap { // Add the last value to the // deleted index and update the heap this.array[index] = last; - sinkDown(index, (a, b) => this.isAboveOrEqual(a, b), this.array); + sinkDown(index, this.compare, this.array); + bubbleUp(index, this.compare, this.array); return true; } - */ - merge(heap: Heap): this { - for (const element of heap) { - this.array.push(element); + *dump(): Iterable { + for (let i = 0; i < this.array.length; ++i) { + yield this.array[i]; + } + } + + merge(elements: Iterable): number { + const array = this.array; + const length = array.length; + try { + for (const element of elements) { + array.push(element); + } + } catch (error) { + throw error; + } finally { + if (length != array.length) { + heapify(this.compare, array); + } } - heapify((a, b) => this.isAboveOrEqual(a, b), this.array); - return this; + return this.size; } peek(): T | undefined { @@ -73,7 +94,7 @@ export abstract class BinaryHeap implements Heap { // Add the last value to // the root and update the heap this.array[0] = last!; - sinkDown(0, (a, b) => this.isAboveOrEqual(a, b), this.array); + sinkDown(0, this.compare, this.array); } return value; @@ -84,37 +105,40 @@ export abstract class BinaryHeap implements Heap { this.array.push(value); // Update the heap - bubbleUp(this.array.length - 1, (a, b) => this.isAboveOrEqual(a, b), this.array); + bubbleUp(this.array.length - 1, this.compare, this.array); return this.size; } // Push a new value to the heap and then pop the root pushPop(value: T): T { // If empty or value is above or equal to root - if (this.array.length < 1 || this.isAboveOrEqual(value, this.array[0])) { + if (this.array.length < 1 || this.compare(value, this.array[0]) <= 0) { return value; } // Swap the root and value const root = this.array[0]; this.array[0] = value; - sinkDown(0, (a, b) => this.isAboveOrEqual(a, b), this.array); + sinkDown(0, this.compare, this.array); return root; } // Pop the root of the heap and then push a new value - replace(value: T): T { - // If not empty - if (this.array.length > 0) { - // Swap the root with value - const root = this.array[0]; - this.array[0] = value; - value = root; - - // Update the heap - sinkDown(0, (a, b) => this.isAboveOrEqual(a, b), this.array); + replace(value: T): T | undefined { + // If empty + if (this.array.length < 1) { + this.array.push(value); + return undefined; } + // Swap the root with value + const root = this.array[0]; + this.array[0] = value; + value = root; + + // Update the heap + sinkDown(0, this.compare, this.array); + return value; } @@ -122,22 +146,18 @@ export abstract class BinaryHeap implements Heap { return this.array.length; } - /* - update(element: T): boolean { - const index = this.array.indexOf(element); + [Symbol.iterator](): Iterator { + return Array.from(this.array).sort(this.compare)[Symbol.iterator](); + } + + update(curElement: T, newElement: T): boolean { + const index = this.array.indexOf(curElement); if (index < 0) { return false; } - const fn = (a: T, b: T) => this.isAboveOrEqual(a, b); - sinkDown(index, fn, this.array); - bubbleUp(index, fn, this.array); + this.array[index] = newElement; + sinkDown(index, this.compare, this.array); + bubbleUp(index, this.compare, this.array); return true; } - */ - - [Symbol.iterator](): Iterator { - return Array.from(this.array) - .sort((a, b) => this._comparator.compare(a, b)) - [Symbol.iterator](); - } } diff --git a/src/heap/binaryMaxHeap.ts b/src/heap/binaryMaxHeap.ts deleted file mode 100644 index deee907..0000000 --- a/src/heap/binaryMaxHeap.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BinaryHeap } from './binaryHeap'; -import { heapify } from './heapify'; - -export class BinaryMaxHeap extends BinaryHeap { - protected isAboveOrEqual(a: T, b: T): boolean { - return this._comparator.compare(a, b) >= 0; - } - - heapify(array: T[]): BinaryMaxHeap { - heapify((a, b) => this.isAboveOrEqual(a, b), array); - return new BinaryMaxHeap(this._comparator, array); - } -} diff --git a/src/heap/binaryMinHeap.ts b/src/heap/binaryMinHeap.ts deleted file mode 100644 index 18290d1..0000000 --- a/src/heap/binaryMinHeap.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BinaryHeap } from './binaryHeap'; -import { heapify } from './heapify'; - -export class BinaryMinHeap extends BinaryHeap { - protected isAboveOrEqual(a: T, b: T): boolean { - return this._comparator.compare(a, b) <= 0; - } - - heapify(array: T[]): BinaryMinHeap { - heapify((a, b) => this.isAboveOrEqual(a, b), array); - return new BinaryMinHeap(this._comparator, array); - } -} diff --git a/src/heap/heap.ts b/src/heap/heap.ts index 47147f0..4a6a8ac 100644 --- a/src/heap/heap.ts +++ b/src/heap/heap.ts @@ -1,32 +1,77 @@ +import { Sorted } from '..'; + +/** + * + */ export interface Heap extends Iterable, Sorted { + /** + * + */ clear(): void; - comparator(): Comparator; - heapify(...iterables: Iterable[]): Heap; - merge(heap: Heap): Heap; + /** + * + * @param element + */ + contains(element: T): boolean; + /** + * + * Aka extract, remove + * @param element + * + * @returns + */ + delete(element: T): boolean; + /** + * + */ + dump(): Iterable; + /** + * + * @param elements + * @returns + */ + merge(elements: Iterable): number; + /** + * + * @returns + */ peek(): T | undefined; - pop(): T | undefined; // Aka extract, delete + /** + * + * @returns + */ + pop(): T | undefined; + /** + * + * @param element + * + * @returns + */ push(element: T): number; // Aka insert, add + /** + * + * @param element + * + * @returns + */ pushPop(element: T): T; - replace(element: T): T; // Aka popPush + /** + * + * @param element + * + * @returns + */ + replace(element: T): T | undefined; // Aka popPush + /** + * + */ readonly size: number; -} - -export interface Comparator { - compare: CompareFn; -} - -export interface CompareFn { - (a: T, b: T): number; -} - -export interface Sortable { - sort: SortFn; -} - -export interface SortFn { - (compare: CompareFn): void; -} - -export interface Sorted { - comparator(): Comparator; + /** + * + * @param curElement + * @param newElement + * + * @returns + */ + update(curElement: T, newElement: T): boolean; } diff --git a/src/heap/index.ts b/src/heap/index.ts index b8ffc07..a275254 100644 --- a/src/heap/index.ts +++ b/src/heap/index.ts @@ -1,4 +1,2 @@ export * from './binaryHeap'; -export * from './binaryMaxHeap'; -export * from './binaryMinHeap'; export * from './heap'; diff --git a/src/heap/heapify.ts b/src/heap/utils.ts similarity index 52% rename from src/heap/heapify.ts rename to src/heap/utils.ts index 745ff3c..1937710 100644 --- a/src/heap/heapify.ts +++ b/src/heap/utils.ts @@ -1,25 +1,9 @@ -import { Comparator } from '.'; +import { CompareFn } from '..'; -export function maxHeapify(comparator: Comparator, array: Array): void { - heapify((a, b) => comparator.compare(a, b) >= 0, array); -} - -export function minHeapify(comparator: Comparator, array: Array): void { - heapify((a, b) => comparator.compare(a, b) <= 0, array); -} - -export function heapify(isAboveOrEqual: (a: T, b: T) => boolean, array: Array): void { - let i = Math.floor((array.length + 1) / 2) - 1; - while (i >= 0) { - sinkDown(--i, isAboveOrEqual, array); - } -} - -export function bubbleUp( - index: number, - isAboveOrEqual: (a: T, b: T) => boolean, - array: Array, -): void { +/** + * @ignore + */ +export function bubbleUp(index: number, compareFn: CompareFn, array: Array): void { const value = array[index]; // Until we reach the top of the heap @@ -29,7 +13,7 @@ export function bubbleUp( const parent = array[parentIndex]!; // If the parent is above or equal to value, the heap is in order - if (isAboveOrEqual(parent, value)) { + if (compareFn(parent, value) <= 0) { break; } @@ -39,15 +23,18 @@ export function bubbleUp( index = parentIndex; } } - -export function sinkDown( - index: number, - isAboveOrEqual: (a: T, b: T) => boolean, - array: Array, -): void { +/** + * @ignore + */ +export function heapify(compareFn: CompareFn, array: T[]): void { + for (let i = (array.length + 1) >>> 1; i > 0; sinkDown(--i, compareFn, array)) {} +} +/** + * @ignore + */ +export function sinkDown(index: number, compareFn: CompareFn, array: Array): void { const n = array.length; const value = array[index]; - do { // Compute the left child's index let childIndex = 2 * index + 1; @@ -59,12 +46,13 @@ export function sinkDown( // Decide which child to compare with let child = array[childIndex]; - if (childIndex + 1 < n && isAboveOrEqual(array[childIndex + 1]!, child)) { + if (childIndex + 1 < n && compareFn(array[childIndex + 1], child) <= 0) { child = array[++childIndex]!; } + //console.log(value, child); // If value <= child - if (isAboveOrEqual(value, child)) { + if (compareFn(value, child) <= 0) { break; } diff --git a/src/index.ts b/src/index.ts index d74efde..ac21381 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,48 @@ // export * from './graph'; -// export * from './heap'; +export * from './heap'; export * from './list'; export * from './queue'; // export * from './segmentTree'; export * from './stack'; // export * from './tree'; // export * from './trie'; + +/** + * + */ +export interface CompareFn { + /** + * + */ + (a: T, b: T): number; +} + +/** + * + */ +export interface Sortable { + /** + * + */ + sort: SortFn; +} + +/** + * + */ +export interface SortFn { + /** + * + */ + (compareFn: CompareFn): void; +} + +/** + * + */ +export interface Sorted { + /** + * + */ + comparator(): CompareFn; +} diff --git a/src/list/arrayList.ts b/src/list/arrayList.ts index 5b38b26..c3e88e6 100644 --- a/src/list/arrayList.ts +++ b/src/list/arrayList.ts @@ -1,5 +1,5 @@ import { List } from './list'; -import { batch, clamp, wrap } from '../utils'; +import { batch, clamp, wrap } from './utils'; /** * An implementation of the {@link List} interface using an array diff --git a/src/list/doublyLinkedList.ts b/src/list/doublyLinkedList.ts index 6331625..310a416 100644 --- a/src/list/doublyLinkedList.ts +++ b/src/list/doublyLinkedList.ts @@ -1,5 +1,5 @@ import { List } from './list'; -import { clamp, wrap } from '../utils'; +import { clamp, wrap } from './utils'; /** * A doubly-linked node version of the {@link LinkedNode} interface. diff --git a/src/list/linkedList.ts b/src/list/linkedList.ts index 276c6bc..c7d5ad0 100644 --- a/src/list/linkedList.ts +++ b/src/list/linkedList.ts @@ -1,5 +1,5 @@ import { List } from './list'; -import { clamp, wrap } from '../utils'; +import { clamp, wrap } from './utils'; /** * A linked node interface. diff --git a/src/utils.ts b/src/list/utils.ts similarity index 50% rename from src/utils.ts rename to src/list/utils.ts index 02eb90b..a3a3e30 100644 --- a/src/utils.ts +++ b/src/list/utils.ts @@ -18,33 +18,10 @@ export function* batch(size: number, elements: Iterable): Generator { */ export function clamp(num: number, min: number, max: number): number { return Math.min(max, Math.max(min, num)); -} /* -export function* first(num: number, iterator: Iterator): Generator { - while (num-- > 0) { - const res = iterator.next(); - if (res.done) { - break; - } - yield res.value; - } } -*/ /* -export function* iterate(iterator: Iterator): Generator { - let res = iterator.next(); - while (!res.done) { - yield res.value; - res = iterator.next(); - } -} -*/ /** * @ignore */ -/** - * @ignore - */ -/** - * @ignore - */ export function wrap(num: number, min: number, max: number): number { +export function wrap(num: number, min: number, max: number): number { return clamp(num < 0 ? max + num : num, min, max); } diff --git a/test/heap/binaryHeap.ts b/test/heap/binaryHeap.ts new file mode 100644 index 0000000..2d8f71b --- /dev/null +++ b/test/heap/binaryHeap.ts @@ -0,0 +1,433 @@ +import { expect } from 'chai'; +import { randomFill, randomInt } from 'crypto'; +import { BinaryHeap } from 'src/heap/binaryHeap'; + +describe.only('BinaryHeap unit tests', function () { + const compareFn = (a: number, b: number) => a - b; + let empty: BinaryHeap; + let filled: BinaryHeap; + const values = new Uint32Array(36); + const updatedValues = new Uint32Array(36); + + // eslint-disable-next-line + randomFill(values, (_) => {}); + // eslint-disable-next-line + randomFill(updatedValues, (_) => {}); + + beforeEach(function () { + empty = new BinaryHeap(compareFn); + filled = new BinaryHeap(compareFn, values); + }); + + describe('#clear()', function() { + it('Should clear when empty', function () { + empty.clear(); + expect(empty.size).to.equal(0); + expect(Array.from(empty.dump())).to.eql([]); + }); + it('Should clear when not empty', function () { + filled.clear(); + expect(filled.size).to.equal(0); + expect(Array.from(filled.dump())).to.eql([]); + }); + it('Should clear added value', function () { + empty.push(12); + empty.clear(); + expect(empty.size).to.equal(0); + expect(Array.from(empty.dump())).to.eql([]); + }); + it('Should clear added values', function () { + empty.push(1); + empty.push(2); + empty.push(3); + empty.clear(); + expect(empty.size).to.equal(0); + expect(Array.from(empty.dump())).to.eql([]); + }); + }); + describe('#contains()', function() { + it('Should return false when empty', function () { + expect(empty.contains(values[0])).to.equal(false); + }); + it('Should return false when not found', function () { + expect(filled.contains(-99)).to.equal(false); + }); + it('Should return true if found', function () { + for(let i = 0; i < values.length; ++i) { + expect(filled.contains(values[i])).to.equal(true); + } + }); + }); + describe('#delete()', function() { + it('Should return false when empty', function () { + expect(empty.delete(1)).to.equal(false); + expect(empty.peek()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should return false when not found', function () { + expect(filled.delete(-99)).to.equal(false); + expect(filled.size).to.equal(values.length); + }); + it('Should return true if found', function () { + for(let i = 0; i < values.length; ++i) { + expect(filled.delete(values[i])).to.equal(true); + } + }); + it('Should decrease size', function () { + for(let i = values.length - 1; i >= 0; --i) { + expect(filled.delete(values[i])).to.equal(true); + expect(filled.size).to.equal(i); + } + }); + it('Should update heap', function () { + empty.merge([20, 15, 10, 5]); + empty.delete(10); + expect(empty.peek()).to.equal(5); + expect(empty.size).to.equal(3); + empty.delete(5); + expect(empty.peek()).to.equal(15); + expect(empty.size).to.equal(2); + empty.delete(20); + expect(empty.peek()).to.equal(15); + expect(empty.size).to.equal(1); + empty.delete(15); + expect(empty.peek()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should not break heap', function () { + const vals = Array.from(values); + for (let i = vals.length - 1; i >= 0; --i) { + const heap = new BinaryHeap(compareFn, vals); + const val = vals.splice(randomInt(vals.length), 1)[0]; + expect(heap.delete(val)).to.equal(true); + expect(heap.size).to.equal(vals.length); + const sorted = Array.from(vals).sort((a, b) => compareFn(b, a)); + while(heap.size > 0) { + expect(heap.pop()).to.equal(sorted.pop()); + } + } + }); + }); + describe('#dump()', function() { + it('Should work when empty', function () { + expect(Array.from(empty.dump())).to.eql([]); + }); + it('Should work with single value', function () { + empty.push(12); + expect(Array.from(empty.dump())).to.eql([12]); + }); + it('Should return iterable on all elements', function () { + const expected = Array.from(values).sort(compareFn); + const actual = Array.from(filled.dump()).sort(compareFn); + expect(actual).to.eql(expected); + }); + }); + describe('#merge()', function() { + it('Should work when empty', function () { + expect(empty.merge([3,2,1])).to.equal(3); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(3); + }); + it('Should work when not empty', function () { + expect(filled.merge([-1, -2, -3])).to.equal(values.length + 3); + expect(filled.peek()).to.equal(-3); + expect(filled.size).to.equal(values.length + 3); + }); + it('Should not break heap', function() { + for (let i = 0; i <= values.length; ++i) { + const arr1 = Array.from(values.slice(0, i)); + for (let j = 0; j <= updatedValues.length; ++j) { + const arr2 = Array.from(updatedValues.slice(0, j)); + const heap = new BinaryHeap(compareFn, arr1); + expect(heap.size).to.equal(arr1.length); + expect(heap.merge(arr2)).to.equal(i + j); + const sorted = arr1.concat(arr2); + sorted.sort((a, b) => compareFn(b, a)); + expect(heap.size).to.equal(sorted.length); + for (let k = i + j - 1; k >= 0; --k) { + expect(heap.pop()).to.equal(sorted[k]); + expect(heap.size).to.equal(k); + } + } + } + }); + }); + describe('#peek()', function() { + it('Should return `undefined` when empty', function () { + expect(empty.peek()).to.equal(undefined); + expect(empty.peek()).to.equal(undefined); + }); + it('Should return the added value', function () { + empty.push(12); + expect(empty.peek()).to.equal(12); + }); + it('Should return the smallest value', function () { + empty.push(1); + empty.push(2); + expect(empty.peek()).to.equal(1); + }); + it('Should return the smallest values as heap grows', function () { + for (let i = 0; i < values.length; ++i) { + const vals = Array.from(values.slice(0, i+1)).sort(compareFn); + empty.push(values[i]); + expect(empty.peek()).to.equal(vals[0]); + } + }); + it('Should return the smallest values as heap shrinks', function () { + const vals = Array.from(values).sort((a, b) => compareFn(b, a)); + for (let i = values.length; i > 0; --i) { + expect(filled.peek()).to.equal(vals[i- 1]); + filled.pop(); + vals.pop(); + } + }); + }); + describe('#pop()', function() { + it('Should work when empty', function () { + expect(empty.pop()).to.equal(undefined); + expect(empty.size).to.equal(0); + expect(empty.pop()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should remove the value from the list', function () { + empty.push(12); + expect(empty.size).to.equal(1); + expect(empty.pop()).to.equal(12); + expect(empty.size).to.equal(0); + expect(empty.pop()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should return the smallest values', function () { + empty.push(1); + empty.push(2); + expect(empty.size).to.equal(2); + expect(empty.pop()).to.equal(1); + expect(empty.size).to.equal(1); + expect(empty.pop()).to.equal(2); + expect(empty.size).to.equal(0); + }); + it('Should return the smallest values as heap shrinks', function () { + const vals = Array.from(values).sort((a, b) => compareFn(b, a)); + for (let i = values.length; i > 0; vals.pop()) { + expect(filled.pop()).to.equal(vals[--i]); + expect(filled.size).to.equal(i); + } + }); + }); + describe('#push()', function() { + it('Should work when empty', function () { + expect(empty.push(1)).to.equal(1); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(1); + }); + it('Should add the value to the list', function () { + expect(empty.push(12)).to.equal(1); + expect(empty.peek()).to.equal(12); + expect(empty.size).to.equal(1); + }); + it('Should return the smallest values', function () { + expect(empty.push(1)).to.equal(1); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(1); + expect(empty.push(2)).to.equal(2); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(2); + }); + it('Should return the smallest values as heap grows', function () { + for (let i = 1; i <= values.length; ++i) { + const vals = Array.from(values.slice(0, i)).sort(compareFn); + expect(empty.push(values[i-1])).to.equal(i); + expect(empty.peek()).to.equal(vals[0]); + expect(empty.size).to.equal(i); + } + }); + it('Should not break heap', function () { + const vals: number[] = []; + for (let i = 0; i < values.length; ++i) { + const heap = new BinaryHeap(compareFn, vals); + heap.push(values[i]); + vals.push(values[i]); + const sorted = Array.from(vals).sort((a, b) => compareFn(b, a)); + for (let j = i; j >= 0; --j) { + expect(heap.pop()).to.equal(sorted[j]); + expect(heap.size).to.equal(j); + } + } + }); + }); + describe('#pushPop()', function() { + it('Should work when empty', function () { + expect(empty.pushPop(1)).to.equal(1); + expect(empty.peek()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should return the smallest value', function () { + empty.push(12); + expect(empty.pushPop(1)).to.equal(1); + expect(empty.peek()).to.equal(12); + expect(empty.size).to.equal(1); + expect(empty.pushPop(15)).to.equal(12); + expect(empty.peek()).to.equal(15); + expect(empty.size).to.equal(1); + }); + it('Should not modify heap size', function () { + const sorted = Array.from(values).sort(compareFn); + expect(filled.pushPop(-12)).to.equal(-12); + expect(filled.peek()).to.equal(sorted[0]); + expect(filled.size).to.equal(values.length); + expect(filled.pushPop(sorted[0] + 1)).to.equal(sorted[0]); + expect(filled.peek()).to.equal(sorted[0] + 1); + expect(filled.size).to.equal(values.length); + }); + it('Should always return the smallest value', function () { + const vals = Array.from(values); + for (let i = 0; i < updatedValues.length; ++i) { + vals.push(updatedValues[i]); + vals.sort((a, b) => compareFn(b, a)); + const expected = vals.pop(); + expect(filled.pushPop(updatedValues[i])).to.equal(expected); + expect(filled.peek()).to.equal(vals[vals.length - 1]); + expect(filled.size).to.equal(vals.length); + } + }); + it('Should not break heap', function () { + const vals = Array.from(values); + for (let i = 0; i < updatedValues.length; ++i) { + const heap = new BinaryHeap(compareFn, vals); + vals.push(updatedValues[i]); + vals.sort((a, b) => compareFn(b, a)); + const expected = vals.pop(); + expect(heap.pushPop(updatedValues[i])).to.equal(expected); + for (let j = vals.length - 1; j >= 0; --j) { + expect(heap.pop()).to.equal(vals[j]); + expect(heap.size).to.equal(j); + } + } + }); + }); + describe('#replace()', function() { + it('Should work when empty', function () { + expect(empty.replace(1)).to.equal(undefined); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(1); + }); + it('Should return the smallest value', function () { + empty.push(12); + expect(empty.replace(1)).to.equal(12); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(1); + expect(empty.replace(15)).to.equal(1); + expect(empty.peek()).to.equal(15); + expect(empty.size).to.equal(1); + }); + it('Should not modify heap size', function () { + const sorted = Array.from(values).sort(compareFn); + expect(filled.replace(-12)).to.equal(sorted[0]); + expect(filled.peek()).to.equal(-12); + expect(filled.size).to.equal(values.length); + expect(filled.replace(sorted[0] + 1)).to.equal(-12); + expect(filled.peek()).to.equal(sorted[0] + 1); + expect(filled.size).to.equal(values.length); + }); + it('Should always return the smallest value', function () { + const vals = Array.from(values); + for (let i = 0; i < updatedValues.length; ++i) { + expect(filled.size).to.equal(vals.length); + vals.sort((a, b) => compareFn(b, a)); + expect(filled.peek()).to.equal(vals[vals.length - 1]); + let expected = vals.pop()!; + expect(filled.replace(updatedValues[i])).to.equal(expected); + vals.push(updatedValues[i]); + } + }); + it('Should not break heap', function () { + const vals = Array.from(values).sort((a, b) => compareFn(b, a)); + for (let i = 0; i < updatedValues.length; ++i) { + const heap = new BinaryHeap(compareFn, vals); + let expected = vals.pop()!; + expect(heap.replace(updatedValues[i])).to.equal(expected); + vals.push(updatedValues[i]); + vals.sort((a, b) => compareFn(b, a)); + for (let j = vals.length - 1; j >= 0; --j) { + expect(heap.pop()).to.equal(vals[j]); + expect(heap.size).to.equal(j); + } + } + }); + }); + describe('#size()', function() { + it('Should be zero when empty', function () { + expect(empty.size).to.equal(0); + }); + it('Should be accurate', function () { + for (let i = 1; i <= values.length; ++i) { + expect(empty.push(values[i-1])).to.equal(i); + expect(empty.size).to.equal(i); + } + }); + }); + describe('#[Symbol.iterator]()', function() { + it('Should work when empty', function () { + expect(Array.from(empty)).to.eql([]); + }); + it('Should work with single value', function () { + empty.push(12); + expect(Array.from(empty)).to.eql([12]); + }); + it('Should return sorted iterable on all elements', function () { + const expected = Array.from(values).sort(compareFn); + const actual = Array.from(filled); + expect(actual).to.eql(expected); + }); + }); + describe('#update()', function() { + it('Should return false when empty', function () { + expect(empty.update(1,2)).to.equal(false); + expect(empty.peek()).to.equal(undefined); + expect(empty.size).to.equal(0); + }); + it('Should return false when not found', function () { + expect(filled.update(-99, 12)).to.equal(false); + }); + it('Should return true if found', function () { + for(let i = 0; i < values.length; ++i) { + expect(filled.update(values[i], updatedValues[i])).to.equal(true); + } + }); + it('Should not modify size', function () { + for(let i = 0; i < values.length; ++i) { + filled.update(values[i], updatedValues[i]); + expect(filled.size).to.equal(values.length); + } + }); + it('Should update heap', function () { + empty.push(5); + empty.update(5, 7); + expect(empty.peek()).to.equal(7); + expect(empty.size).to.equal(1); + empty.update(7, 2); + expect(empty.peek()).to.equal(2); + expect(empty.size).to.equal(1); + empty.push(12); + expect(empty.peek()).to.equal(2); + expect(empty.size).to.equal(2); + empty.update(12, 1); + expect(empty.peek()).to.equal(1); + expect(empty.size).to.equal(2); + }); + it('Should not break heap', function () { + const vals = Array.from(values); + for (let i = 0; i < vals.length; ++i) { + const heap = new BinaryHeap(compareFn, vals); + expect(heap.update(vals[i], updatedValues[i])).to.equal(true); + expect(heap.size).to.equal(vals.length); + vals[i] = updatedValues[i]; + const sorted = Array.from(vals).sort((a, b) => compareFn(b, a)); + for (let j = vals.length - 1; j >= 0; --j) { + expect(heap.pop()).to.equal(sorted.pop()); + expect(heap.size).to.equal(j); + } + } + }); + }); +}); \ No newline at end of file diff --git a/test/heap/utils.ts b/test/heap/utils.ts new file mode 100644 index 0000000..2e8e699 --- /dev/null +++ b/test/heap/utils.ts @@ -0,0 +1,15 @@ +import { expect } from 'chai'; +import { randomFill, randomInt } from 'crypto'; +import { bubbleUp, heapify, sinkDown } from 'src/heap/utils'; + +describe.only('Heap.utils unit tests', function () { + describe('#bubbleUp()', function() { + it('TODO'); + }); + describe('#heapify()', function() { + it('TODO'); + }); + describe('#sinkDown()', function() { + it('TODO'); + }); +}); \ No newline at end of file diff --git a/test/list/arrayList.ts b/test/list/arrayList.ts index 683cc72..639556b 100644 --- a/test/list/arrayList.ts +++ b/test/list/arrayList.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { randomFill, randomInt } from 'crypto'; import { ArrayList } from 'src/list/arrayList'; -import { clamp, wrap } from 'src/utils'; +import { clamp, wrap } from 'src/list/utils'; describe('ArrayList unit tests', function () { let empty: ArrayList; diff --git a/test/list/doublyLinkedList.ts b/test/list/doublyLinkedList.ts index ac8dc46..8596af9 100644 --- a/test/list/doublyLinkedList.ts +++ b/test/list/doublyLinkedList.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { randomFill, randomInt } from 'crypto'; import { DoublyLinkedList } from 'src/list/doublyLinkedList'; -import { clamp, wrap } from 'src/utils'; +import { clamp, wrap } from 'src/list/utils'; describe('DoublyLinkedList unit tests', function () { let empty: DoublyLinkedList; diff --git a/test/list/linkedList.ts b/test/list/linkedList.ts index 7b5b332..07e7fb7 100644 --- a/test/list/linkedList.ts +++ b/test/list/linkedList.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { randomFill, randomInt } from 'crypto'; import { LinkedList } from 'src/list/linkedList'; -import { clamp, wrap } from 'src/utils'; +import { clamp, wrap } from 'src/list/utils'; describe('LinkedList unit tests', function () { let empty: LinkedList; diff --git a/test/list/utils.ts b/test/list/utils.ts new file mode 100644 index 0000000..83c1b04 --- /dev/null +++ b/test/list/utils.ts @@ -0,0 +1,15 @@ +import { expect } from 'chai'; +import { randomFill, randomInt } from 'crypto'; +import { batch, clamp, wrap } from 'src/list/utils'; + +describe.only('List.utils unit tests', function () { + describe('#batch()', function() { + it('TODO'); + }); + describe('#clamp()', function() { + it('TODO'); + }); + describe('#wrap()', function() { + it('TODO'); + }); +}); \ No newline at end of file