// @file heap.js
// @ingroup utility
// Binary heap.
// @date 05/22/2023
function Heap(compareFn) {
this._base = [];
this._comp = compareFn;
}
Object.defineProperty(Heap.prototype, "size", {
get: function() {
return this._base.length;
}
});
Object.defineProperty(Heap.prototype, "front", {
get: function() {
if (this._base.length === 0)
throw Error("Data access in empty heap");
return this._base[0];
}
});
Heap.prototype.clear = function() {
this._base = [];
};
Heap.prototype.push = function(value) {
let n = this._base.length;
let a = this._base;
heap_swim(a, 0, n, value, this._comp);
return this;
};
Heap.prototype.pop = function() {
let n = this._base.length;
if (n === 0)
throw Error("Pop in empty heap");
let a = this._base;
let t = a[0];
heap_sink(a, 0, n-1, a[n-1], this._comp);
a.pop();
return t;
};
Heap.prototype.make = function (iterable) {
this._base = Array.from(iterable);
let n = this._base.length;
let a = this._base;
for (let i = n>>1; i > 0; i--) {
heap_sink(a, i-1, n, a[i-1], this._comp);
}
return this;
};
Heap.prototype.sorted = function () {
let n = this._base.length;
let a = this._base.slice();
for (let i = n-1; i > 0; i--) {
let t = a[i]; a[i] = a[0];
heap_sink(a, 0, i, t, this._comp);
}
return a;
};
Heap.prototype.toString = function () {
return `Heap {${this._base.join(", ")}}`;
};
// Private.
function heap_swim(base, h, i, key, comp) {
let hole = i;
while (i > h) {
i = (i-1)>>1; // Parent.
if (comp(key, base[i]) > 0)
base[hole] = base[i];
else
break;
hole = i;
}
base[hole] = key;
}
function heap_sink(base, i, n, key, comp) {
let head = i;
let hole = i;
for (;;) {
i = (i+1)<<1; // Right child.
if (i > n)
break;
if (i === n || comp(base[i], base[i-1]) < 0)
i--;
base[hole] = base[i];
hole = i;
}
heap_swim(base, head, hole, key, comp);
}
// Exports.
module.exports = Heap;
// END
// @file heap_test.js
// @ingroup testing
// Test Heap module.
// const Heap = require("heap");
// const colorize = require("colorize");
const colorize = (str, color) => str;
// Utility.
function report(condition, message) {
console.log(message.padEnd(30, ".")
+ (condition
? colorize("Pass", "green")
: colorize("Fail", "red")));
}
function iota(n, start = 0, step = 1) {
return Array.from({length: n}, (_, i) => i*step + start);
}
function shuffled(a) {
a = a.slice();
for (let n = a.length, i = 1; i < n; i++) {
let j = Math.random() * (i+1) | 0;
let temp = a[i]; a[i] = a[j]; a[j] = temp;
}
return a;
}
function arrayEquals(a1, a2) {
if (a1.length !== a2.length)
return false;
return a1.every((v, i) => v === a2[i]);
}
const maxHeapCompare = (a, b) => a - b;
const minHeapCompare = (a, b) => b - a;
// Test.
function testConstructor() {
console.log(colorize("constructor", "blue"));
let heap = new Heap(maxHeapCompare);
report(heap.size === 0, "size");
let except = false;
try {
heap.front;
} catch (_) {
except = true;
}
report(except, "exception (front)");
except = false;
try {
heap.pop();
} catch (_) {
except = true;
}
report(except, "exception (pop)");
}
function testPushMax() {
console.log(colorize("push(max)", "blue"));
let keys = iota(10);
let heap = new Heap(maxHeapCompare);
let okay = true;
for (const x of keys) {
heap.push(x);
if (heap.front !== x) {
okay = false;
}
}
report(heap.size === keys.length, "size");
report(okay, "order");
heap.clear();
okay = true;
for (const x of keys.reverse()) {
heap.push(x);
if (heap.front !== 9) {
okay = false;
}
}
report(heap.size === keys.length, "size (rev)");
report(okay, "order (rev)");
}
function testPushMin() {
console.log(colorize("push(min)", "blue"));
let keys = iota(10);
let heap = new Heap(minHeapCompare);
let okay = true;
for (const x of keys) {
heap.push(x);
if (heap.front !== 0) {
okay = false;
}
}
report(heap.size === keys.length, "size");
report(okay, "order");
heap.clear();
okay = true;
for (const x of keys.reverse()) {
heap.push(x);
if (heap.front !== x) {
okay = false;
}
}
report(heap.size === keys.length, "size (rev)");
report(okay, "order (rev)");
}
function testPop() {
console.log(colorize("pop", "blue"));
let keys = shuffled(iota(10));
let heap = new Heap(minHeapCompare);
for (const x of keys) {
heap.push(x);
}
let sorted = [];
for (; heap.size !== 0; heap.pop()) {
sorted.push(heap.front);
}
report(sorted.length === keys.length, "size");
keys.sort(maxHeapCompare);
report(arrayEquals(sorted, keys), "order");
}
function testMake() {
console.log(colorize("make", "blue"));
let keys = shuffled(iota(10));
let heap = new Heap(minHeapCompare);
heap.make(keys);
report(heap.size === keys.length, "size");
let sorted = [];
for (; heap.size !== 0; heap.pop()) {
sorted.push(heap.front);
}
keys.sort(maxHeapCompare);
report(arrayEquals(sorted, keys), "order");
}
function testSort() {
console.log(colorize("sort", "blue"));
let keys = shuffled(iota(10));
let heap = new Heap(maxHeapCompare);
heap.make(keys);
let sorted = heap.sorted();
report(sorted.length === keys.length, "size");
keys.sort(maxHeapCompare);
report(arrayEquals(sorted, keys), "order");
}
function testClear() {
console.log(colorize("clear", "blue"));
let keys = iota(10);
let heap = new Heap(maxHeapCompare);
heap.make(keys);
heap.clear();
report(heap.size === 0, "size");
}
// Entry.
testConstructor();
testPushMax();
testPushMin();
testPop();
testMake();
testSort();
testClear();