core/heap.js

// 'ESLint' configuration
/* global TypeGeneric */

/**
 * Creates heaps.
 * @template {any} TypeGeneric The generic type of the items.
 *
 * @example
 *
 * // without chaining
 * const heap = new Heap(compare);
 * heap.push(four);
 * heap.push(one);
 * heap.push(two);
 *
 * const maximum = heap.pop();
 *
 * @example
 *
 * // with chaining
 * const heap = new Heap(compare).push(four).push(one).push(two);
 *
 * const maximum = heap.pop();
 */
class Heap {

    /**
     * @template {any} TypeGeneric The generic type of the items.
     * @callback TypeHandlerCompare A handler to use to compare the two given items.
     * @param {TypeGeneric} $a The first item to compare.
     * @param {TypeGeneric} $b The second item to compare.
     * @returns {number}
     * @protected
     *
     * @memberof Heap
     */

    /**
     * Stores the compare handler.
     * @type {TypeHandlerCompare<TypeGeneric>}
     * @private
     */
    $compare;

    /**
     * Stores the items.
     * @type {Array<TypeGeneric>}
     * @private
     */
    $items;

    /**
     * Gets the size of the heap.
     * @type {number}
     * @public
     */
    get size() {

        return this.$items.length;
    }

    /**
     * Creates a new heap.
     * @param {TypeHandlerCompare<TypeGeneric>} $compare The compare handler.
     */
    constructor($compare) {

        this.$compare = $compare;

        this.$items = [];
    }

    /**
     * Shifts down an item from its given index.
     * @param {number} [$index] The index of the item to shift down.
     * @private
     */
    $shiftDown($index = 0) {

        const left = ($index * 2) + 1;
        const right = ($index * 2) + 2;

        let best = $index;

        if (left < this.$items.length
        && this.$compare(this.$items[best], this.$items[left]) > 0) {

            best = left;
        }

        if (right < this.$items.length
        && this.$compare(this.$items[best], this.$items[right]) > 0) {

            best = right;
        }

        if (best === $index) {

            return;
        }

        this.$swap($index, best);

        this.$shiftDown(best);
    }

    /**
     * Shifts up the last item.
     * @private
     */
    $shiftUp() {

        let index = this.$items.length - 1;

        while (index > 0) {

            const parent = Math.floor((index - 1) / 2);

            if (this.$compare(this.$items[index], this.$items[parent]) >= 0) {

                break;
            }

            this.$swap(index, parent);

            index = parent;
        }
    }

    /**
     * Swaps items at the two given indices.
     * @param {number} $a The index of the first item to swap.
     * @param {number} $b The index of the second item to swap.
     * @private
     */
    $swap($a, $b) {

        [this.$items[$a], this.$items[$b]] = [this.$items[$b], this.$items[$a]];
    }

    /**
     * Pops an item.
     * @returns {(TypeGeneric | undefined)}
     * @public
     */
    pop() {

        if (this.$items.length === 0) {

            return undefined;
        }

        if (this.$items.length === 1) {

            return this.$items.pop();
        }

        this.$swap(0, this.$items.length - 1);

        const best = this.$items.pop();

        this.$shiftDown();

        return best;
    }

    /**
     * Pushes an item.
     * @param {TypeGeneric} $item The item to push.
     * @returns {this}
     * @public
     */
    push($item) {

        this.$items.push($item);

        this.$shiftUp();

        return this;
    }
}

export {

    Heap
};

export default Heap;