diff --git a/package.json b/package.json index 5a5d878..2990c20 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,6 @@ "mocha": "^6.2.2" }, "dependencies": { - "@datastructures-js/heap": "^4.3.3" + "@datastructures-js/heap": "^4.3.7" } } diff --git a/src/maxPriorityQueue.d.ts b/src/maxPriorityQueue.d.ts index 8a8eae1..5c0f5bd 100644 --- a/src/maxPriorityQueue.d.ts +++ b/src/maxPriorityQueue.d.ts @@ -1,20 +1,13 @@ -import { MaxHeap, IGetCompareValue } from '@datastructures-js/heap'; +import { IGetCompareValue } from '@datastructures-js/heap'; +import { PriorityQueue } from './priorityQueue'; import { LegacyOptions } from './minPriorityQueue'; -export class MaxPriorityQueue implements Iterable { - constructor(options?: IGetCompareValue | LegacyOptions, heap?: MaxHeap); - [Symbol.iterator](): Iterator; - size(): number; - isEmpty(): boolean; - front(): T | null; - back(): T | null; +export interface MaxPriorityQueue extends PriorityQueue { enqueue(value: T): MaxPriorityQueue; push(value: T): MaxPriorityQueue; - dequeue(): T | null; - pop(): T | null; - remove(cb: (value: T) => boolean): T[]; - contains(cb: (value: T) => boolean): boolean; - toArray(): T[]; - clear(): void; - static fromArray(values: T[], getCompareValue?: IGetCompareValue): MaxPriorityQueue; } + +export const MaxPriorityQueue: { + new (options?: IGetCompareValue | LegacyOptions | null | undefined, values?: T[]): MaxPriorityQueue; + fromArray(values: T[], options?: IGetCompareValue | LegacyOptions | null | undefined): MaxPriorityQueue; +}; diff --git a/src/maxPriorityQueue.js b/src/maxPriorityQueue.js index 25471ff..5763e4b 100644 --- a/src/maxPriorityQueue.js +++ b/src/maxPriorityQueue.js @@ -3,52 +3,34 @@ * @license MIT */ -const { Heap, MaxHeap } = require('@datastructures-js/heap'); - -const getMaxCompare = (getCompareValue) => (a, b) => { - const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; - const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; - return aVal < bVal ? 1 : -1; -}; +const { PriorityQueue } = require('./priorityQueue'); /** * @class MaxPriorityQueue + * @extends PriorityQueue */ -class MaxPriorityQueue { - constructor(options, _heap) { +class MaxPriorityQueue extends PriorityQueue { + constructor(options, values) { // Handle legacy options format ({ compare: fn }) if (options && typeof options === 'object' && typeof options.compare === 'function') { - this._getCompareValue = null; - const compareFunction = (a, b) => options.compare(a, b) >= 0 ? -1 : 1; - this._heap = _heap || new Heap(compareFunction); + const compareFunction = (a, b) => options.compare(a, b) <= 0 ? -1 : 1; + super(compareFunction, values); } else { // Current format (direct compare function) const getCompareValue = options; if (getCompareValue && typeof getCompareValue !== 'function') { throw new Error('MaxPriorityQueue constructor requires a callback for object values'); } - this._heap = _heap || new MaxHeap(getCompareValue); + // Create a MaxHeap-compatible compare function + const compare = (a, b) => { + const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; + const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; + return aVal < bVal ? 1 : -1; + }; + super(compare, values); } } - /** - * Returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - front() { - return this._heap.root(); - } - - /** - * Returns an element with lowest priority in the queue - * @public - * @returns {number|string|object} - */ - back() { - return this._heap.leaf(); - } - /** * Adds a value to the queue * @public @@ -56,7 +38,8 @@ class MaxPriorityQueue { * @returns {MaxPriorityQueue} */ enqueue(value) { - return this._heap.insert(value); + super.enqueue(value); + return this; } /** @@ -68,142 +51,16 @@ class MaxPriorityQueue { push(value) { return this.enqueue(value); } - - /** - * Removes and returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - dequeue() { - return this._heap.extractRoot(); - } - - /** - * Removes and returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - pop() { - return this.dequeue(); - } - - /** - * Removes all elements that match a criteria in the callback - * @public - * @param {function} cb - * @returns {array} - */ - remove(cb) { - if (typeof cb !== 'function') { - throw new Error('MaxPriorityQueue remove expects a callback'); - } - - const removed = []; - const dequeued = []; - while (!this.isEmpty()) { - const popped = this.pop(); - if (cb(popped)) { - removed.push(popped); - } else { - dequeued.push(popped); - } - } - - dequeued.forEach((val) => this.push(val)); - return removed; - } - - /** - * Checks if the queue contains an element that matches a criteria - * @public - * @param {function} cb - * @returns {boolean} - */ - contains(cb) { - if (typeof cb !== 'function') { - throw new Error('MaxPriorityQueue contains expects a callback'); - } - - let found = false; - const dequeued = []; - while (!this.isEmpty()) { - const popped = this.pop(); - dequeued.push(popped); - if (cb(popped)) { - found = true; - break; - } - } - - dequeued.forEach((val) => this.push(val)); - return found; - } - - /** - * Returns the number of elements in the queue - * @public - * @returns {number} - */ - size() { - return this._heap.size(); - } - - /** - * Checks if the queue is empty - * @public - * @returns {boolean} - */ - isEmpty() { - return this._heap.isEmpty(); - } - - /** - * Clears the queue - * @public - */ - clear() { - this._heap.clear(); - } - - /** - * Returns a sorted list of elements from highest to lowest priority - * @public - * @returns {array} - */ - toArray() { - return this._heap.clone().sort().reverse(); - } - - /** - * Implements an iterable on the min priority queue - * @public - */ - [Symbol.iterator]() { - let size = this.size(); - return { - next: () => { - size -= 1; - return { - value: this.pop(), - done: size === -1 - }; - } - }; - } - - /** - * Creates a priority queue from an existing array - * @public - * @static - * @returns {MaxPriorityQueue} - */ - static fromArray(values, getCompareValue) { - const heap = new Heap(getMaxCompare(getCompareValue), values); - return new MaxPriorityQueue( - getCompareValue, - new MaxHeap(getCompareValue, heap).fix() - ); - } } +/** + * Creates a priority queue from an existing array + * @public + * @static + * @returns {MaxPriorityQueue} + */ +MaxPriorityQueue.fromArray = function fromArray(values, options) { + return new MaxPriorityQueue(options, values); +}; + exports.MaxPriorityQueue = MaxPriorityQueue; diff --git a/src/minPriorityQueue.d.ts b/src/minPriorityQueue.d.ts index ed6fc45..3ac4475 100644 --- a/src/minPriorityQueue.d.ts +++ b/src/minPriorityQueue.d.ts @@ -1,23 +1,16 @@ -import { MinHeap, IGetCompareValue } from '@datastructures-js/heap'; +import { IGetCompareValue } from '@datastructures-js/heap'; +import { PriorityQueue } from './priorityQueue'; export interface LegacyOptions { compare: (a: T, b: T) => number; } -export class MinPriorityQueue implements Iterable { - constructor(options?: IGetCompareValue | LegacyOptions, heap?: MinHeap); - [Symbol.iterator](): Iterator; - size(): number; - isEmpty(): boolean; - front(): T | null; - back(): T | null; +export interface MinPriorityQueue extends PriorityQueue { enqueue(value: T): MinPriorityQueue; push(value: T): MinPriorityQueue; - dequeue(): T | null; - pop(): T | null; - remove(cb: (value: T) => boolean): T[]; - contains(cb: (value: T) => boolean): boolean; - toArray(): T[]; - clear(): void; - static fromArray(values: T[], getCompareValue?: IGetCompareValue): MinPriorityQueue; } + +export const MinPriorityQueue: { + new (options?: IGetCompareValue | LegacyOptions | null | undefined, values?: T[]): MinPriorityQueue; + fromArray(values: T[], options?: IGetCompareValue | LegacyOptions | null | undefined): MinPriorityQueue; +}; diff --git a/src/minPriorityQueue.js b/src/minPriorityQueue.js index 116657c..3f7a5f8 100644 --- a/src/minPriorityQueue.js +++ b/src/minPriorityQueue.js @@ -3,52 +3,34 @@ * @license MIT */ -const { Heap, MinHeap } = require('@datastructures-js/heap'); - -const getMinCompare = (getCompareValue) => (a, b) => { - const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; - const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; - return aVal <= bVal ? -1 : 1; -}; +const { PriorityQueue } = require('./priorityQueue'); /** * @class MinPriorityQueue + * @extends PriorityQueue */ -class MinPriorityQueue { - constructor(options, _heap) { +class MinPriorityQueue extends PriorityQueue { + constructor(options, values) { // Handle legacy options format ({ compare: fn }) if (options && typeof options === 'object' && typeof options.compare === 'function') { - this._getCompareValue = null; const compareFunction = (a, b) => options.compare(a, b) <= 0 ? -1 : 1; - this._heap = _heap || new Heap(compareFunction); + super(compareFunction, values); } else { // Current format (direct compare function) const getCompareValue = options; if (getCompareValue && typeof getCompareValue !== 'function') { throw new Error('MinPriorityQueue constructor requires a callback for object values'); } - this._heap = _heap || new MinHeap(getCompareValue); + // Create a MinHeap-compatible compare function + const compare = (a, b) => { + const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; + const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; + return aVal <= bVal ? -1 : 1; + }; + super(compare, values); } } - /** - * Returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - front() { - return this._heap.root(); - } - - /** - * Returns an element with lowest priority in the queue - * @public - * @returns {number|string|object} - */ - back() { - return this._heap.leaf(); - } - /** * Adds a value to the queue * @public @@ -56,7 +38,8 @@ class MinPriorityQueue { * @returns {MinPriorityQueue} */ enqueue(value) { - return this._heap.insert(value); + super.enqueue(value); + return this; } /** @@ -68,142 +51,16 @@ class MinPriorityQueue { push(value) { return this.enqueue(value); } - - /** - * Removes and returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - dequeue() { - return this._heap.extractRoot(); - } - - /** - * Removes and returns an element with highest priority in the queue - * @public - * @returns {number|string|object} - */ - pop() { - return this.dequeue(); - } - - /** - * Removes all elements that match a criteria in the callback - * @public - * @param {function} cb - * @returns {array} - */ - remove(cb) { - if (typeof cb !== 'function') { - throw new Error('MinPriorityQueue remove expects a callback'); - } - - const removed = []; - const dequeued = []; - while (!this.isEmpty()) { - const popped = this.pop(); - if (cb(popped)) { - removed.push(popped); - } else { - dequeued.push(popped); - } - } - - dequeued.forEach((val) => this.push(val)); - return removed; - } - - /** - * Checks if the queue contains an element that matches a criteria - * @public - * @param {function} cb - * @returns {boolean} - */ - contains(cb) { - if (typeof cb !== 'function') { - throw new Error('MinPriorityQueue contains expects a callback'); - } - - let found = false; - const dequeued = []; - while (!this.isEmpty()) { - const popped = this.pop(); - dequeued.push(popped); - if (cb(popped)) { - found = true; - break; - } - } - - dequeued.forEach((val) => this.push(val)); - return found; - } - - /** - * Returns the number of elements in the queue - * @public - * @returns {number} - */ - size() { - return this._heap.size(); - } - - /** - * Checks if the queue is empty - * @public - * @returns {boolean} - */ - isEmpty() { - return this._heap.isEmpty(); - } - - /** - * Clears the queue - * @public - */ - clear() { - this._heap.clear(); - } - - /** - * Returns a sorted list of elements from highest to lowest priority - * @public - * @returns {array} - */ - toArray() { - return this._heap.clone().sort().reverse(); - } - - /** - * Implements an iterable on the min priority queue - * @public - */ - [Symbol.iterator]() { - let size = this.size(); - return { - next: () => { - size -= 1; - return { - value: this.pop(), - done: size === -1 - }; - } - }; - } - - /** - * Creates a priority queue from an existing array - * @public - * @static - * @returns {MinPriorityQueue} - */ - static fromArray(values, getCompareValue) { - const heap = new Heap(getMinCompare(getCompareValue), values); - return new MinPriorityQueue( - getCompareValue, - new MinHeap(getCompareValue, heap).fix() - ); - } } +/** + * Creates a priority queue from an existing array + * @public + * @static + * @returns {MinPriorityQueue} + */ +MinPriorityQueue.fromArray = function fromArray(values, options) { + return new MinPriorityQueue(options, values); +}; + exports.MinPriorityQueue = MinPriorityQueue; diff --git a/src/priorityQueue.d.ts b/src/priorityQueue.d.ts index e692cdf..c3a5f98 100644 --- a/src/priorityQueue.d.ts +++ b/src/priorityQueue.d.ts @@ -1,7 +1,6 @@ import { ICompare } from '@datastructures-js/heap'; -export class PriorityQueue implements Iterable { - constructor(compare: ICompare, values?: T[]); +export interface PriorityQueue extends Iterable { [Symbol.iterator](): Iterator; size(): number; isEmpty(): boolean; @@ -15,5 +14,9 @@ export class PriorityQueue implements Iterable { contains(cb: (value: T) => boolean): boolean; toArray(): T[]; clear(): void; - static fromArray(values: T[], compare: ICompare): PriorityQueue; } + +export const PriorityQueue: { + new (compare: ICompare, values?: T[]): PriorityQueue; + fromArray(values: T[], compare: ICompare): PriorityQueue; +}; diff --git a/src/priorityQueue.js b/src/priorityQueue.js index 825c282..1bc876f 100644 --- a/src/priorityQueue.js +++ b/src/priorityQueue.js @@ -12,15 +12,13 @@ class PriorityQueue { /** * Creates a priority queue * @params {function} compare + * @params {array} [values] */ - constructor(compare, _values) { + constructor(compare, values) { if (typeof compare !== 'function') { throw new Error('PriorityQueue constructor expects a compare function'); } - this._heap = new Heap(compare, _values); - if (_values) { - this._heap.fix(); - } + this._heap = new Heap(compare, values); } /** @@ -48,7 +46,8 @@ class PriorityQueue { * @returns {PriorityQueue} */ enqueue(value) { - return this._heap.insert(value); + this._heap.insert(value); + return this; } /** @@ -182,16 +181,16 @@ class PriorityQueue { } }; } - - /** - * Creates a priority queue from an existing array - * @public - * @static - * @returns {PriorityQueue} - */ - static fromArray(values, compare) { - return new PriorityQueue(compare, values); - } } +/** + * Creates a priority queue from an existing array + * @public + * @static + * @returns {PriorityQueue} + */ +PriorityQueue.fromArray = function fromArray(values, compare) { + return new PriorityQueue(compare, values); +}; + exports.PriorityQueue = PriorityQueue; diff --git a/test/PriorityQueue.test.js b/test/PriorityQueue.test.js index b8df623..6462dcb 100644 --- a/test/PriorityQueue.test.js +++ b/test/PriorityQueue.test.js @@ -143,6 +143,45 @@ describe('PriorityQueue', () => { }); }); + describe('constructor with initial values', () => { + it('should properly initialize with values (min priority)', () => { + const q = new PriorityQueue((a, b) => a - b, [3, 1, 4]); + expect(q.front()).to.equal(1); + expect(q.back()).to.equal(4); + expect(q.size()).to.equal(3); + }); + + it('should properly initialize with values (max priority)', () => { + const q = new PriorityQueue((a, b) => b - a, [3, 1, 4]); + expect(q.front()).to.equal(4); + expect(q.back()).to.equal(1); + expect(q.size()).to.equal(3); + }); + + it('should maintain priority ordering after enqueue', () => { + const q = new PriorityQueue((a, b) => a - b, [3, 1, 4]); + q.enqueue(2); + expect(q.toArray()).to.eql([1, 2, 3, 4]); + expect(q.front()).to.equal(1); + }); + + it('should handle enqueue of highest priority element correctly', () => { + const q = new PriorityQueue((a, b) => b - a, [3, 1, 4]); + q.enqueue(5); + expect(q.front()).to.equal(5); + expect(q.toArray()).to.eql([5, 4, 3, 1]); + }); + + it('should work with object values and initial array', () => { + const values = [{ id: 3 }, { id: 1 }, { id: 4 }]; + const q = new PriorityQueue((a, b) => a.id - b.id, values); + expect(q.front()).to.eql({ id: 1 }); + expect(q.back()).to.eql({ id: 4 }); + q.enqueue({ id: 2 }); + expect(q.toArray()).to.eql([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]); + }); + }); + describe('fromArray', () => { it('min PriorityQueue from array', () => { const q = PriorityQueue.fromArray(numValues, numComparator); diff --git a/test/maxPriorityQueue.test.js b/test/maxPriorityQueue.test.js index bd1d711..eb8024a 100644 --- a/test/maxPriorityQueue.test.js +++ b/test/maxPriorityQueue.test.js @@ -129,9 +129,41 @@ describe('MaxPriorityQueue', () => { }); }); + describe('constructor with initial values', () => { + it('should properly initialize with primitive values', () => { + const q = new MaxPriorityQueue(null, [3, 1, 4]); + expect(q.front()).to.equal(4); + expect(q.back()).to.equal(1); + expect(q.size()).to.equal(3); + }); + + it('should maintain priority ordering after enqueue', () => { + const q = new MaxPriorityQueue(null, [3, 1, 4]); + q.enqueue(2); + expect(q.toArray()).to.eql([4, 3, 2, 1]); + expect(q.front()).to.equal(4); + }); + + it('should handle enqueue of largest element correctly', () => { + const q = new MaxPriorityQueue(null, [3, 1, 4]); + q.enqueue(5); + expect(q.front()).to.equal(5); + expect(q.toArray()).to.eql([5, 4, 3, 1]); + }); + + it('should work with object values and compare function', () => { + const values = [{ id: 3 }, { id: 1 }, { id: 4 }]; + const q = new MaxPriorityQueue((obj) => obj.id, values); + expect(q.front()).to.eql({ id: 4 }); + expect(q.back()).to.eql({ id: 1 }); + q.enqueue({ id: 5 }); + expect(q.toArray()).to.eql([{ id: 5 }, { id: 4 }, { id: 3 }, { id: 1 }]); + }); + }); + describe('legacy compare function', () => { const values = [50, 80, 30, 90, 60, 40, 20]; - const maxQ = new MaxPriorityQueue({ compare: (a, b) => a - b }); + const maxQ = new MaxPriorityQueue({ compare: (a, b) => b - a }); it('enqueue and dequeue with legacy compare', () => { values.forEach((value) => maxQ.enqueue(value)); diff --git a/test/minPriorityQueue.test.js b/test/minPriorityQueue.test.js index e4af573..30c80e6 100644 --- a/test/minPriorityQueue.test.js +++ b/test/minPriorityQueue.test.js @@ -126,6 +126,38 @@ describe('MinPriorityQueue', () => { }); + describe('constructor with initial values', () => { + it('should properly initialize with primitive values', () => { + const q = new MinPriorityQueue(null, [3, 1, 4]); + expect(q.front()).to.equal(1); + expect(q.back()).to.equal(4); + expect(q.size()).to.equal(3); + }); + + it('should maintain priority ordering after enqueue', () => { + const q = new MinPriorityQueue(null, [3, 1, 4]); + q.enqueue(2); + expect(q.toArray()).to.eql([1, 2, 3, 4]); + expect(q.front()).to.equal(1); + }); + + it('should handle enqueue of smallest element correctly', () => { + const q = new MinPriorityQueue(null, [3, 1, 4]); + q.enqueue(0); + expect(q.front()).to.equal(0); + expect(q.toArray()).to.eql([0, 1, 3, 4]); + }); + + it('should work with object values and compare function', () => { + const values = [{ id: 3 }, { id: 1 }, { id: 4 }]; + const q = new MinPriorityQueue((obj) => obj.id, values); + expect(q.front()).to.eql({ id: 1 }); + expect(q.back()).to.eql({ id: 4 }); + q.enqueue({ id: 2 }); + expect(q.toArray()).to.eql([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]); + }); + }); + describe('legacy compare function', () => { const values = [50, 80, 30, 90, 60, 40, 20]; const minQ = new MinPriorityQueue({ compare: (a, b) => a - b });