Heap test cases added

This commit is contained in:
Revone 2023-08-18 13:40:08 +08:00
parent 4f1aefd505
commit 5db5561999
9 changed files with 312 additions and 142 deletions

View file

@ -6,9 +6,35 @@
* @license MIT License
*/
import {PriorityQueue} from '../priority-queue';
import type {HeapItem, HeapOptions} from '../types';
import type {HeapOptions} from '../types';
export abstract class Heap<T> {
export class HeapItem<T = number> {
private _priority: number;
get priority(): number {
return this._priority;
}
set priority(value: number) {
this._priority = value;
}
private _val: T | null;
get val(): T | null {
return this._val;
}
set val(value: T | null) {
this._val = value;
}
constructor(priority: number = NaN, val: T | null = null) {
this._val = val;
this._priority = priority;
}
}
export abstract class Heap<T = number> {
/**
* The function is a constructor for a class that initializes a priority callback function based on the
* options provided.
@ -36,12 +62,12 @@ export abstract class Heap<T> {
this._pq = v;
}
protected _priorityCb: (element: T) => number;
protected _priorityCb: (val: T) => number;
get priorityCb() {
return this._priorityCb;
}
protected set priorityCb(v: (element: T) => number) {
protected set priorityCb(v: (val: T) => number) {
this._priorityCb = v;
}
@ -84,7 +110,7 @@ export abstract class Heap<T> {
/**
* The `peek` function returns the top item in the priority queue without removing it.
* @returns The `peek()` method is returning either a `HeapItem<T>` object or `null`.Returns an element with the highest priority in the queue
* @returns The `peek()` method is returning either a `HeapItem<T>` object or `null`.Returns an val with the highest priority in the queue
*/
peek(): HeapItem<T> | null {
return this._pq.peek();
@ -92,25 +118,25 @@ export abstract class Heap<T> {
/**
* The `peekLast` function returns the last item in the heap.
* @returns The method `peekLast()` returns either a `HeapItem<T>` object or `null`.Returns an element with the lowest priority in the queue
* @returns The method `peekLast()` returns either a `HeapItem<T>` object or `null`.Returns an val with the lowest priority in the queue
*/
peekLast(): HeapItem<T> | null {
return this._pq.leaf();
}
/**
* The `add` function adds an element to a priority queue with an optional priority value.
* @param {T} element - The `element` parameter represents the value that you want to add to the heap. It can be of any
* The `add` function adds an val to a priority queue with an optional priority value.
* @param {T} val - The `val` parameter represents the value that you want to add to the heap. It can be of any
* type.
* @param {number} [priority] - The `priority` parameter is an optional number that represents the priority of the
* element being added to the heap. If the `element` parameter is a number, then the `priority` parameter is set to
* the value of `element`. If the `element` parameter is not a number, then the
* val being added to the heap. If the `val` parameter is a number, then the `priority` parameter is set to
* the value of `val`. If the `val` parameter is not a number, then the
* @returns The `add` method returns the instance of the `Heap` class.
* @throws {Error} if priority is not a valid number
*/
add(element: T, priority?: number): Heap<T> {
if (typeof element === 'number') {
priority = element;
add(val: T, priority?: number): Heap<T> {
if (typeof val === 'number') {
priority = val;
} else {
if (priority === undefined) {
throw new Error('.add expects a numeric priority');
@ -121,20 +147,20 @@ export abstract class Heap<T> {
throw new Error('.add expects a numeric priority');
}
if (Number.isNaN(+priority) && Number.isNaN(this._priorityCb(element))) {
if (Number.isNaN(+priority) && Number.isNaN(this._priorityCb(val))) {
throw new Error(
'.add expects a numeric priority '
+ 'or a constructor callback that returns a number'
);
}
const _priority = !Number.isNaN(+priority) ? priority : this._priorityCb(element);
this._pq.add({priority: _priority, element});
const _priority = !Number.isNaN(+priority) ? priority : this._priorityCb(val);
this._pq.add(new HeapItem<T>(_priority, val));
return this;
}
/**
* The `poll` function returns the top item from a priority queue or null if the queue is empty.Removes and returns an element with the highest priority in the queue
* The `poll` function returns the top item from a priority queue or null if the queue is empty.Removes and returns an val with the highest priority in the queue
* @returns either a HeapItem<T> object or null.
*/
poll(): HeapItem<T> | null {
@ -145,9 +171,24 @@ export abstract class Heap<T> {
return top;
}
/**
* The function checks if a given node or value exists in the priority queue.
* @param {T | HeapItem<T>} node - The parameter `node` can be of type `T` or `HeapItem<T>`.
* @returns a boolean value.
*/
has(node:T | HeapItem<T>): boolean {
if (node instanceof HeapItem<T>) {
return this.getPq().getNodes().includes(node);
} else {
return this.getPq().getNodes().findIndex(item => {
return item.val === node;
}) !== -1;
}
}
/**
* The `toArray` function returns an array of `HeapItem<T>` objects.
* @returns An array of HeapItem<T> objects.Returns a sorted list of elements
* @returns An array of HeapItem<T> objects.Returns a sorted list of vals
*/
toArray(): HeapItem<T>[] {
return this._pq.toArray();

View file

@ -6,15 +6,15 @@
* @license MIT License
*/
import {Heap} from './heap';
import {Heap, HeapItem} from './heap';
import {PriorityQueue} from '../priority-queue';
import type {HeapItem, HeapOptions} from '../types';
import type {HeapOptions} from '../types';
/**
* @class MaxHeap
* @extends Heap
*/
export class MaxHeap<T> extends Heap<T> {
export class MaxHeap<T = number> extends Heap<T> {
protected _pq: PriorityQueue<HeapItem<T>>;
/**

View file

@ -6,15 +6,15 @@
* @license MIT License
*/
import {Heap} from './heap';
import {Heap, HeapItem} from './heap';
import {PriorityQueue} from '../priority-queue';
import type {HeapItem, HeapOptions} from '../types';
import type {HeapOptions} from '../types';
/**
* @class MinHeap
* @extends Heap
*/
export class MinHeap<T> extends Heap<T> {
export class MinHeap<T = number> extends Heap<T> {
protected _pq: PriorityQueue<HeapItem<T>>;
/**

View file

@ -8,7 +8,19 @@
import type {PriorityQueueComparator, PriorityQueueDFSOrderPattern, PriorityQueueOptions} from '../types';
export class PriorityQueue<T = number> {
protected _nodes: T[] = [];
get nodes(): T[] {
return this._nodes;
}
protected set nodes(value: T[]) {
this._nodes = value;
}
get size(): number {
return this.nodes.length;
}
/**
* The constructor initializes a priority queue with the given options, including an array of nodes and a comparator
* function.
@ -25,43 +37,6 @@ export class PriorityQueue<T = number> {
}
}
protected _nodes: T[] = [];
get nodes(): T[] {
return this._nodes;
}
protected set nodes(value: T[]) {
this._nodes = value;
}
get size(): number {
return this.nodes.length;
}
/**
* The `heapify` function creates a new PriorityQueue instance and fixes the heap property.
* @param options - The "options" parameter is an object that contains the configuration options for the PriorityQueue.
* It can include properties such as "comparator" which specifies the comparison function used to order the elements in
* the priority queue, and "initialValues" which is an array of initial values to be added to the priority
* @returns a new instance of the PriorityQueue class after performing the heapify operation on it.
*/
static heapify<T>(options: PriorityQueueOptions<T>) {
const heap = new PriorityQueue(options);
heap._fix();
return heap;
}
/**
* The function checks if a priority queue is valid by creating a new priority queue with a fix option and then calling
* the isValid method.
* @param options - An object containing options for creating a priority queue. The options object should have the
* following properties:
* @returns the result of calling the `isValid()` method on a new instance of the `PriorityQueue` class.
*/
static isPriorityQueueified<T>(options: Omit<PriorityQueueOptions<T>, 'isFix'>) {
return new PriorityQueue({...options, isFix: true}).isValid();
}
/**
* Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters.
@ -80,6 +55,16 @@ export class PriorityQueue<T = number> {
this._heapifyUp(this.size - 1);
}
/**
* The "has" function checks if a given node is present in the list of nodes.
* @param {T} node - The parameter `node` is of type `T`, which means it can be any type. It represents the node that
* we want to check if it exists in the `nodes` array.
* @returns a boolean value indicating whether the given node is included in the array of nodes.
*/
has(node: T): boolean {
return this.nodes.includes(node);
}
/**
* The `peek` function returns the first element of the `nodes` array if it exists, otherwise it returns `null`.
* @returns The `peek()` function is returning the first element (`T`) of the `nodes` array if the `size` is not zero.
@ -149,82 +134,7 @@ export class PriorityQueue<T = number> {
return new PriorityQueue<T>({nodes: this.nodes, comparator: this._comparator});
}
// --- start additional methods ---
/**
* The `isValid` function recursively checks if a binary tree satisfies a certain condition.
* @returns The function `isValid()` returns a boolean value.
*/
isValid(): boolean {
const isValidRecursive = (parentIndex: number): boolean => {
let isValidLeft = true;
let isValidRight = true;
if (this._getLeft(parentIndex) !== -1) {
const leftChildIndex = (parentIndex * 2) + 1;
if (!this._compare(parentIndex, leftChildIndex)) return false;
isValidLeft = isValidRecursive(leftChildIndex);
}
if (this._getRight(parentIndex) !== -1) {
const rightChildIndex = (parentIndex * 2) + 2;
if (!this._compare(parentIndex, rightChildIndex)) return false;
isValidRight = isValidRecursive(rightChildIndex);
}
return isValidLeft && isValidRight;
};
return isValidRecursive(0);
}
/**
* The function sorts the elements in a data structure and returns them in ascending order.
* @returns The `sort()` function is returning an array of type `T[]`.
*/
sort(): T[] {
const visitedNode: T[] = [];
while (this.size !== 0) {
const top = this.poll();
if (top) visitedNode.push(top);
}
return visitedNode;
}
/**
* The DFS function performs a depth-first search traversal on a binary tree and returns an array of visited nodes
* based on the specified traversal order.
* @param {PriorityQueueDFSOrderPattern} dfsMode - The dfsMode parameter is a string that specifies the order in which
* the nodes should be visited during the Depth-First Search (DFS) traversal. It can have one of the following values:
* @returns an array of type `(T | null)[]`.
*/
DFS(dfsMode: PriorityQueueDFSOrderPattern): (T | null)[] {
const visitedNode: (T | null)[] = [];
const traverse = (cur: number) => {
const leftChildIndex = this._getLeft(cur);
const rightChildIndex = this._getRight(cur);
switch (dfsMode) {
case 'in':
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
visitedNode.push(this.nodes[cur] ?? null);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
break;
case 'pre':
visitedNode.push(this.nodes[cur] ?? null);
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
break;
case 'post':
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
visitedNode.push(this.nodes[cur] ?? null);
break;
}
};
this._isValidIndex(0) && traverse(0);
return visitedNode;
}
protected readonly _comparator: PriorityQueueComparator<T> = (a: T, b: T) => {
const aKey = a as unknown as number, bKey = b as unknown as number;
@ -345,5 +255,105 @@ export class PriorityQueue<T = number> {
for (let i = Math.floor(this.size / 2); i > -1; i--) this._heapifyDown(i);
}
// --- start additional methods ---
/**
* The `isValid` function recursively checks if a binary tree satisfies a certain condition.
* @returns The function `isValid()` returns a boolean value.
*/
isValid(): boolean {
const isValidRecursive = (parentIndex: number): boolean => {
let isValidLeft = true;
let isValidRight = true;
if (this._getLeft(parentIndex) !== -1) {
const leftChildIndex = (parentIndex * 2) + 1;
if (!this._compare(parentIndex, leftChildIndex)) return false;
isValidLeft = isValidRecursive(leftChildIndex);
}
if (this._getRight(parentIndex) !== -1) {
const rightChildIndex = (parentIndex * 2) + 2;
if (!this._compare(parentIndex, rightChildIndex)) return false;
isValidRight = isValidRecursive(rightChildIndex);
}
return isValidLeft && isValidRight;
};
return isValidRecursive(0);
}
/**
* The function sorts the elements in a data structure and returns them in ascending order.
* @returns The `sort()` function is returning an array of type `T[]`.
*/
sort(): T[] {
const visitedNode: T[] = [];
while (this.size !== 0) {
const top = this.poll();
if (top) visitedNode.push(top);
}
return visitedNode;
}
/**
* The DFS function performs a depth-first search traversal on a binary tree and returns an array of visited nodes
* based on the specified traversal order.
* @param {PriorityQueueDFSOrderPattern} dfsMode - The dfsMode parameter is a string that specifies the order in which
* the nodes should be visited during the Depth-First Search (DFS) traversal. It can have one of the following values:
* @returns an array of type `(T | null)[]`.
*/
DFS(dfsMode: PriorityQueueDFSOrderPattern): (T | null)[] {
const visitedNode: (T | null)[] = [];
const traverse = (cur: number) => {
const leftChildIndex = this._getLeft(cur);
const rightChildIndex = this._getRight(cur);
switch (dfsMode) {
case 'in':
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
visitedNode.push(this.nodes[cur] ?? null);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
break;
case 'pre':
visitedNode.push(this.nodes[cur] ?? null);
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
break;
case 'post':
this._isValidIndex(leftChildIndex) && traverse(leftChildIndex);
this._isValidIndex(rightChildIndex) && traverse(rightChildIndex);
visitedNode.push(this.nodes[cur] ?? null);
break;
}
};
this._isValidIndex(0) && traverse(0);
return visitedNode;
}
/**
* The `heapify` function creates a new PriorityQueue instance and fixes the heap property.
* @param options - The "options" parameter is an object that contains the configuration options for the PriorityQueue.
* It can include properties such as "comparator" which specifies the comparison function used to order the elements in
* the priority queue, and "initialValues" which is an array of initial values to be added to the priority
* @returns a new instance of the PriorityQueue class after performing the heapify operation on it.
*/
static heapify<T>(options: PriorityQueueOptions<T>) {
const heap = new PriorityQueue(options);
heap._fix();
return heap;
}
/**
* The function checks if a priority queue is valid by creating a new priority queue with a fix option and then calling
* the isValid method.
* @param options - An object containing options for creating a priority queue. The options object should have the
* following properties:
* @returns the result of calling the `isValid()` method on a new instance of the `PriorityQueue` class.
*/
static isPriorityQueueified<T>(options: Omit<PriorityQueueOptions<T>, 'isFix'>) {
return new PriorityQueue({...options, isFix: true}).isValid();
}
// --- end additional methods ---
}

View file

@ -1,8 +1,5 @@
export interface HeapOptions<T> {
priority?: (element: T) => number;
}
export interface HeapItem<T> {
priority: number;
element: T | null;
// TODO there is an idea that support chaining which is for conveniently using the data structure
// isChaining? : boolean
}

View file

@ -1,3 +1,2 @@
export * from './abstract-graph';
export * from './directed-graph.test';
export * from './undirected-graph';

View file

@ -0,0 +1,5 @@
import {DirectedEdge, DirectedGraph, DirectedVertex} from '../../../../src';
describe('UndirectedGraph Test1', () => {
});

View file

@ -0,0 +1,117 @@
import {HeapItem, MaxHeap, MinHeap} from '../../../../src';
describe('Heap Test1', () => {
it('should numeric Min Heap operations be proper', function () {
const minNumHeap = new MinHeap<number>();
expect(minNumHeap).toBeInstanceOf(MinHeap);
minNumHeap.add(1);
expect(minNumHeap.has(1)).toBe(true);
minNumHeap.add(6);
expect(minNumHeap.has(2)).toBe(false);
expect(minNumHeap.has(6)).toBe(true);
minNumHeap.add(2);
expect(minNumHeap.has(2)).toBe(true);
minNumHeap.add(0);
expect(minNumHeap.has(0)).toBe(true);
minNumHeap.add(5);
expect(minNumHeap.has(5)).toBe(true);
minNumHeap.add(9);
expect(minNumHeap.has(9)).toBe(true);
expect(minNumHeap.size).toBe(6);
const poll1 = minNumHeap.poll();
expect(poll1).toBeInstanceOf(HeapItem<number>)
poll1 instanceof HeapItem && expect(poll1.val).toBe(0);
const poll2 = minNumHeap.poll();
expect(poll2).toBeInstanceOf(HeapItem<number>)
poll2 instanceof HeapItem && expect(poll2.val).toBe(1);
const peek1 = minNumHeap.peek();
expect(peek1).toBeInstanceOf(HeapItem<number>)
peek1 instanceof HeapItem && expect(peek1.val).toBe(2);
const heapArray = minNumHeap.toArray();
expect(heapArray).toBeInstanceOf(Array<HeapItem>);
expect(heapArray.map(item => item.priority)).toEqual([2, 5, 9, 6]);
expect(minNumHeap.size).toBe(4);
});
it('should object Max Heap operations be proper', function () {
const maxHeap = new MaxHeap<{ keyA: string }>();
const myObj1 = {keyA: 'a1'}, myObj6 = {keyA: 'a6'}, myObj5 = {keyA: 'a5'}, myObj2 = {keyA: 'a2'},
myObj0 = {keyA: 'a0'}, myObj9 = {keyA: 'a9'};
maxHeap.add(myObj1, 1);
expect(maxHeap.has(myObj1)).toBe(true);
expect(maxHeap.has(myObj9)).toBe(false);
maxHeap.add(myObj6, 6);
expect(maxHeap.has(myObj6)).toBe(true);
maxHeap.add(myObj5, 5);
expect(maxHeap.has(myObj5)).toBe(true);
maxHeap.add(myObj2, 2);
expect(maxHeap.has(myObj2)).toBe(true);
expect(maxHeap.has(myObj6)).toBe(true);
maxHeap.add(myObj0, 0);
expect(maxHeap.has(myObj0)).toBe(true);
expect(maxHeap.has(myObj9)).toBe(false);
maxHeap.add(myObj9, 9);
expect(maxHeap.has(myObj9)).toBe(true);
const peek9 = maxHeap.peek();
peek9 && peek9.val && expect(peek9.val.keyA).toBe('a9');
const heapToArr = maxHeap.toArray();
expect(heapToArr.map(item => item.val?.keyA)).toEqual(['a9', 'a2', 'a6', 'a1', 'a0', 'a5']);
const values = ['a9', 'a6', 'a5', 'a2', 'a1', 'a0'];
let i = 0;
while (maxHeap.size > 0) {
const polled = maxHeap.poll();
expect(polled).toBeInstanceOf(HeapItem<{ keyA: string }>);
polled && expect(polled.val).toHaveProperty('keyA');
polled && polled.val && expect(polled.val.keyA).toBe(values[i]);
i++;
}
});
it('should object Min Heap operations be proper', function () {
class MyObject {
keyA: string;
constructor(keyA: string) {
this.keyA = keyA;
}
}
const minObjHeap = new MinHeap<MyObject>();
const obj1 = new MyObject('a1'), obj6 = new MyObject('a6'), obj2 = new MyObject('a2'),
obj0 = new MyObject('a0');
minObjHeap.add(obj1, 1);
expect(minObjHeap.has(obj1)).toBe(true);
expect(minObjHeap.has(obj6)).toBe(false);
minObjHeap.add(obj6, 6);
expect(minObjHeap.has(obj6)).toBe(true);
minObjHeap.add(obj2, 2);
expect(minObjHeap.has(obj2)).toBe(true);
minObjHeap.add(obj0, 0);
expect(minObjHeap.has(obj0)).toBe(true);
const peek = minObjHeap.peek();
peek && peek.val && expect(peek.val.keyA).toBe('a0');
const heapToArr = minObjHeap.toArray();
expect(heapToArr.map(item => item.val?.keyA)).toEqual(['a0', 'a1', 'a2', 'a6']);
const values = ['a0', 'a1', 'a2', 'a6'];
let i = 0;
while (minObjHeap.size > 0) {
const polled = minObjHeap.poll();
expect(polled).toBeInstanceOf(HeapItem<MyObject>);
polled && expect(polled.val).toBeInstanceOf(MyObject);
polled && polled.val && expect(polled.val.keyA).toBe(values[i]);
i++;
}
});
});

View file

@ -0,0 +1 @@
export * from './heap.test';