UNPKG

sangja

Version:

JavaScript data structures library

324 lines (294 loc) 9.17 kB
const Utils = require('./utils'); /** * @class * @memberof sangja */ class Heap { /** * Creates a new Heap. * By default, max-heap is created. * Iterable a and option object b are optional. But if both are given, a must precede b. * @constructor * @param {iterable} [a=[]] - Iterator for initialize * @param {Object} [b={}] - Option object for initialize * @param {function} [b.key=sangja.defaultKey] - Key function for each value. * Each value should be comparable with given key. * @param {function} [b.compare=sangja.defaultCompare] - Compare function.<br> * If x precede y, compare(key(x), key(y)) < 0<br> * If y precede x, compare(key(x), key(y)) > 0<br> * If the order of x and y is the same, compare(key(x), key(y)) == 0 * @param {function} [b.reverse=false] - If true, compare result is inverted. */ constructor(a = {}, b = {}) { // null, null -> iterator: [], key,compare: default // iterator, null -> iterator: ok, key,compare: default // option, null -> iterator: [], key,compare: option // iterator, option -> iterator: ok, key,compare: option let iterator = null; let key = null; let compare = null; let reverse = null; if (Utils.isIterable(a)) { iterator = a; // If a is an iterable, b will be an option object.(If not given, {}) ({ key, compare, reverse } = b); } else { iterator = []; // If a is not an iterable, a will be an option object.(If not given, {}) ({ key, compare, reverse } = a); } this._heap = [0]; this._options = { key: key || Utils.defaultKey, compare: compare || Utils.defaultCompare, reverse: reverse || false, }; this._key = this._options.key; if (this._options.reverse) { this._compare = (x, y) => this._options.compare(this._key(y), this._key(x)); } else { this._compare = (x, y) => this._options.compare(this._key(x), this._key(y)); } [...iterator].forEach(v => this.add(v)); } /** * Add value to the the heap. * @param {*} value - The value to add */ add(value) { this._heap.push(value); let now = this._heap.length - 1; let next = Math.floor(now / 2); while (now > 1 && this._compare(this._heap[next], this._heap[now]) < 0) { const tmp = this._heap[next]; this._heap[next] = this._heap[now]; this._heap[now] = tmp; now = next; next = Math.floor(now / 2); } } /** * Add values to the the heap. * @param {iterable} iterable - The iterable object that contain values to add. */ addAll(iterable) { [...iterable].forEach(v => this.add(v)); } /** * Removes the root of the heap and returns the value. * @returns {*} The value at the root of the heap. If empty, return undefined. */ pop() { if (this._heap.length === 1) { return undefined; } if (this._heap.length === 2) { return this._heap.pop(); } const value = this._heap[1]; this._heap[1] = this._heap.pop(); let now = 1; while (now < this._heap.length) { const left = now * 2; const right = now * 2 + 1; // If now < right child, now <- max(left, right) and continue if (right < this._heap.length && this._compare(this._heap[now], this._heap[right]) < 0) { let next = null; if (this._compare(this._heap[left], this._heap[right]) < 0) { next = right; } else { next = left; } const tmp = this._heap[now]; this._heap[now] = this._heap[next]; this._heap[next] = tmp; now = next; } else if (left < this._heap.length && this._compare(this._heap[now], this._heap[left]) < 0) { const tmp = this._heap[now]; this._heap[now] = this._heap[left]; this._heap[left] = tmp; now = left; } else { break; } } return value; } /** * Returns the value at the root of the heap without changing the state of the heap. * @returns {*} The value at the root of the heap. If empty, return undefined. */ peek() { if (this._heap.length === 1) { return undefined; } return this._heap[1]; } /** * Returns the value of the first element in the heap * that satisfies the given predicate.<br> * Searches matching value by bfs order. * @param {function} f - Predicate * @returns {(*|undefined)} The the first value in the heap * that satisfies the provided testing function. * When no element in the heap satisfies the provided testing function, return undefined. */ find(f) { for (let i = 1; i < this._heap.length; i += 1) { if (f(this._heap[i])) { return this._heap[i]; } } return undefined; } /** * Returns the number of elements in the heap. * @returns {number} The number of elements in the heap. */ size() { return this._heap.length - 1; } /** * Returns whether the heap is empty. * @returns {boolean} True if the heap is empty. */ isEmpty() { return this._heap.length === 1; } /** * Removed all elements in the heap. */ clear() { this._heap = [0]; } /** * Execute the given procedure f for each values. * @param {function} f - Procedure to execute */ forEach(f) { const heapForIter = new Heap(this._heap.slice(1), this._options); while (!heapForIter.isEmpty()) { f(heapForIter.pop()); } } /** * Returns a new Heap mapped with given function f. * @param {function} f - Function * @param {Object} [options=this._options] - Option object for initialize the heap.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {Heap} Heap([mapped values]) */ map(f, options) { const heap = new Heap(Utils.mergeOptions(options, this._options)); this.forEach(v => heap.add(f(v))); return heap; } /** * Returns a new Heap whose values are mapped with given function f and flattened. * @param {function} f - Function (this.value) => iterable * @param {Object} [options=this._options] - Option object for initialize the heap.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {Heap} Heap([mapped and flattened values]) */ flatMap(f, options) { const heap = new Heap(Utils.mergeOptions(options, this._options)); this.forEach(v => heap.addAll([...f(v)])); return heap; } /** * Returns a new Heap whose values are filtered with given predicate f. * @param {function} f - Predicate (this.value) => boolean * @param {Object} [options=this._options] - Option object for initialize the heap.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {Heap} Heap([filtered values]) */ filter(f, options) { const heap = new Heap(Utils.mergeOptions(options, this._options)); this.forEach((v) => { if (f(v)) { heap.add(v); } }); return heap; } /** * Return new heap with same items, but reversed option is inverted from this. * @return {Heap} Heap([reversed values]) */ reversed() { return new Heap(this, Utils.mergeOptions({ reverse: !this._options.reverse }, this._options)); } /** * If any of containing values satisfies given f, return true. * If none of values satisfy f or not contain any value, return false. * @param {function} f - Predicate * @returns {boolean} */ some(f) { for (let i = 1; i < this._heap.length; i += 1) { if (f(this._heap[i])) { return true; } } return false; } /** * If all of containing values satisfies given f or not contain any value, return true. * If any of value doesn't satisfy f, return false. * @param {function} f - Predicate * @returns {boolean} */ every(f) { for (let i = 1; i < this._heap.length; i += 1) { if (!f(this._heap[i])) { return false; } } return true; } /** * If contains given value v, return true. * @param {*} v * @returns {boolean} */ includes(v) { for (let i = 1; i < this._heap.length; i += 1) { if (this._heap[i] === v) { return true; } } return false; } * [Symbol.iterator]() { // Use other heap! const heapForIter = new Heap(this._heap.slice(1), this._options); while (!heapForIter.isEmpty()) { yield heapForIter.pop(); } } /** * Returns breadth first iterator. * @param {Function} [f] - If f is given, execute f by bfs order. * @returns {(generator|undefined)} Tree breadth first iterator. If f if given, not return. */ // eslint-disable-next-line consistent-return breadthFirst(f) { if (!f) { const that = this; return (function* _breadthFirst() { for (let i = 1; i < that._heap.length; i += 1) { yield that._heap[i]; } }()); } for (let i = 1; i < this._heap.length; i += 1) { f(this._heap[i]); } } } module.exports = Heap;