UNPKG

@thi.ng/heaps

Version:

Various heap implementations for arbitrary values and with customizable ordering

240 lines (239 loc) 5.3 kB
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 };