@thi.ng/heaps
Version:
Various heap implementations for arbitrary values and with customizable ordering
240 lines (239 loc) • 5.3 kB
JavaScript
import { compare } from "@thi.ng/compare/compare";
import { equiv } from "@thi.ng/equiv";
const defHeap = (values, opts) => new Heap(values, opts);
class Heap {
static parentIndex(idx) {
return idx > 0 ? idx - 1 >> 1 : -1;
}
static childIndex(idx) {
return idx >= 0 ? (idx << 1) + 1 : -1;
}
values;
compare;
equiv;
constructor(values, opts) {
opts = { compare, equiv, ...opts };
this.compare = opts.compare;
this.equiv = opts.equiv;
this.values = [];
if (values) {
this.into(values);
}
}
*[Symbol.iterator]() {
yield* this.min();
}
get length() {
return this.values.length;
}
copy() {
const h = this.empty();
h.values = this.values.slice();
return h;
}
clear() {
this.values.length = 0;
}
empty() {
return new Heap(null, { compare: this.compare });
}
peek() {
return this.values[0];
}
push(val) {
this.values.push(val);
this.percolateUp(this.values.length - 1);
return this;
}
pop() {
const vals = this.values;
const tail = vals.pop();
let res;
if (vals.length > 0) {
res = vals[0];
vals[0] = tail;
this.percolateDown(0);
} else {
res = tail;
}
return res;
}
pushPop(val, vals = this.values) {
const head = vals[0];
if (vals.length > 0 && this.compare(head, val) <= 0) {
vals[0] = val;
val = head;
this.percolateDown(0, vals);
}
return val;
}
into(vals) {
for (let v of vals) {
this.push(v);
}
return this;
}
/**
* Calls {@link Heap.pushPop} for each given value in `vals` and
* returns last result (i.e. the smallest value in heap after
* processing all `vals`).
*
* @param vals - values to insert
*/
pushPopAll(vals) {
let res;
for (let v of vals) {
res = this.pushPop(v);
}
return res;
}
replaceHead(val) {
const res = this.values[0];
this.values[0] = val;
this.percolateDown(0);
return res;
}
remove(val) {
const { values, equiv: equiv2 } = this;
for (let i = values.length; i-- > 0; ) {
if (equiv2(values[i], val)) {
this.values.splice(i, 1);
this.heapify();
return true;
}
}
return false;
}
find(val) {
const { values, equiv: equiv2 } = this;
for (let i = values.length; i-- > 0; ) {
if (equiv2(values[i], val)) {
return values[i];
}
}
}
findWith(pred) {
const values = this.values;
for (let i = values.length; i-- > 0; ) {
if (pred(values[i])) return values[i];
}
}
has(val) {
return this.find(val) !== void 0;
}
heapify(vals = this.values) {
for (let i = vals.length - 1 >> 1; i >= 0; i--) {
this.percolateDown(i, vals);
}
}
/**
* Returns the largest `n` values (or less) in heap, based on
* comparator ordering.
*
* @param n - number of values
*/
max(n = this.values.length) {
const { compare: compare2, values } = this;
const res = values.slice(0, n);
if (!n) {
return res;
}
this.heapify(res);
for (let m = values.length; n < m; n++) {
this.pushPop(values[n], res);
}
return res.sort((a, b) => compare2(b, a));
}
/**
* Returns the smallest `n` values (or less) in heap, based on
* comparator ordering.
*
* @param n - number of values
*/
min(n = this.values.length) {
const { compare: compare2, values } = this;
const res = values.slice(0, n).sort(compare2);
if (!n) {
return res;
}
let x = res[n - 1], y;
for (let i = n, m = values.length; i < m; i++) {
y = values[i];
if (compare2(y, x) < 0) {
res.splice(__binarySearch(y, res, 0, n, compare2), 0, y);
res.pop();
x = res[n - 1];
}
}
return res;
}
parent(n) {
n = Heap.parentIndex(n);
return n >= 0 ? this.values[n] : void 0;
}
children(n) {
n = Heap.childIndex(n);
const vals = this.values;
const m = vals.length;
if (n >= m) return;
if (n === m - 1) return [vals[n]];
return [vals[n], vals[n + 1]];
}
leaves() {
const vals = this.values;
if (!vals.length) {
return [];
}
return vals.slice(Heap.parentIndex(vals.length - 1) + 1);
}
percolateUp(i, vals = this.values) {
const node = vals[i];
const cmp = this.compare;
while (i > 0) {
const pi = i - 1 >> 1;
const parent = vals[pi];
if (cmp(node, parent) >= 0) {
break;
}
vals[pi] = node;
vals[i] = parent;
i = pi;
}
}
percolateDown(i, vals = this.values) {
const n = vals.length;
const node = vals[i];
const cmp = this.compare;
let child = (i << 1) + 1;
while (child < n) {
const next = child + 1;
if (next < n && cmp(vals[child], vals[next]) >= 0) {
child = next;
}
if (cmp(vals[child], node) < 0) {
vals[i] = vals[child];
} else {
break;
}
i = child;
child = (i << 1) + 1;
}
vals[i] = node;
}
}
const __binarySearch = (x, vals, lo, hi, cmp) => {
let m;
while (lo < hi) {
m = lo + hi >>> 1;
if (cmp(x, vals[m]) < 0) {
hi = m;
} else {
lo = m + 1;
}
}
return lo;
};
export {
Heap,
defHeap
};