sangja
Version:
JavaScript data structures library
324 lines (294 loc) • 9.17 kB
JavaScript
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;