Binary Min-Heap Priority Queue

Implement a dependency-free binary min-heap with a custom comparator for O(log n) priority queue pushes and pops.

JavascriptCli Tooling

Code

snippet
export class MinHeap {
  #items = [];
  #compare;

  constructor(compare = (left, right) => left - right) {
    if (typeof compare !== "function") {
      throw new TypeError("compare must be a function");
    }
    this.#compare = compare;
  }

  get size() {
    return this.#items.length;
  }

  get isEmpty() {
    return this.#items.length === 0;
  }

  peek() {
    return this.#items[0];
  }

  push(value) {
    this.#items.push(value);
    this.#bubbleUp(this.#items.length - 1);
    return this;
  }

  pop() {
    if (this.#items.length === 0) return undefined;
    const root = this.#items[0];
    const last = this.#items.pop();
    if (this.#items.length > 0) {
      this.#items[0] = last;
      this.#sinkDown(0);
    }
    return root;
  }

  #bubbleUp(startIndex) {
    let index = startIndex;
    while (index > 0) {
      const parentIndex = (index - 1) >> 1;
      if (this.#compare(this.#items[index], this.#items[parentIndex]) >= 0) {
        break;
      }
      const current = this.#items[index];
      this.#items[index] = this.#items[parentIndex];
      this.#items[parentIndex] = current;
      index = parentIndex;
    }
  }

  #sinkDown(startIndex) {
    let index = startIndex;
    const length = this.#items.length;
    while (true) {
      const left = index * 2 + 1;
      const right = left + 1;
      let smallest = index;
      if (
        left < length &&
        this.#compare(this.#items[left], this.#items[smallest]) < 0
      ) {
        smallest = left;
      }
      if (
        right < length &&
        this.#compare(this.#items[right], this.#items[smallest]) < 0
      ) {
        smallest = right;
      }
      if (smallest === index) break;
      const current = this.#items[index];
      this.#items[index] = this.#items[smallest];
      this.#items[smallest] = current;
      index = smallest;
    }
  }

  static from(values, compare) {
    if (values == null || typeof values[Symbol.iterator] !== "function") {
      throw new TypeError("values must be iterable");
    }
    const heap = new MinHeap(compare);
    for (const value of values) heap.push(value);
    return heap;
  }
}

const queue = new MinHeap(
  (left, right) => left.priority - right.priority || left.sequence - right.sequence,
);
const tasks = [
  { name: "send-email", priority: 2, sequence: 1 },
  { name: "rotate-logs", priority: 3, sequence: 2 },
  { name: "charge-card", priority: 1, sequence: 3 },
  { name: "warm-cache", priority: 2, sequence: 4 },
];
for (const task of tasks) queue.push(task);
while (!queue.isEmpty) {
  const task = queue.pop();
  console.log(task.priority, task.name);
}

const numbers = MinHeap.from([5, 1, 4, 2, 3]);
const sorted = [];
while (!numbers.isEmpty) sorted.push(numbers.pop());
console.log(sorted.join(","));