@rimbu/sorted
Version:
Immutable SortedMap and SortedSet implementations for TypeScript
946 lines • 41.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SortedBuilder = exports.SortedNonEmptyBase = exports.SortedEmpty = void 0;
exports.leafDeleteMin = leafDeleteMin;
exports.leafDeleteMax = leafDeleteMax;
exports.leafMutateSplitRight = leafMutateSplitRight;
exports.leafMutateGiveToLeft = leafMutateGiveToLeft;
exports.leafMutateGiveToRight = leafMutateGiveToRight;
exports.leafMutateGetFromLeft = leafMutateGetFromLeft;
exports.leafMutateGetFromRight = leafMutateGetFromRight;
exports.leafMutateJoinLeft = leafMutateJoinLeft;
exports.leafMutateJoinRight = leafMutateJoinRight;
exports.innerDeleteMin = innerDeleteMin;
exports.innerDeleteMax = innerDeleteMax;
exports.innerMutateSplitRight = innerMutateSplitRight;
exports.innerMutateGiveToLeft = innerMutateGiveToLeft;
exports.innerMutateGiveToRight = innerMutateGiveToRight;
exports.innerMutateGetFromLeft = innerMutateGetFromLeft;
exports.innerMutateGetFromRight = innerMutateGetFromRight;
exports.innerMutateJoinLeft = innerMutateJoinLeft;
exports.innerMutateJoinRight = innerMutateJoinRight;
exports.innerNormalizeDownsizeChild = innerNormalizeDownsizeChild;
exports.innerNormalizeIncreaseChild = innerNormalizeIncreaseChild;
exports.innerGetSubIndex = innerGetSubIndex;
exports.innerGetAtIndex = innerGetAtIndex;
exports.innerTakeInternal = innerTakeInternal;
exports.innerDropInternal = innerDropInternal;
exports.innerStreamSliceIndex = innerStreamSliceIndex;
var tslib_1 = require("tslib");
var base_1 = require("@rimbu/base");
var map_custom_1 = require("@rimbu/collection-types/map-custom");
var common_1 = require("@rimbu/common");
var stream_1 = require("@rimbu/stream");
var index_cjs_1 = require("./index.cjs");
/**
* Base implementation used for empty sorted collections.<br/>
* <br/>
* Provides the index‑based operations used by `SortedMap` / `SortedSet`
* instances when they are empty and always returns the given fallback value.
*/
var SortedEmpty = /** @class */ (function (_super) {
tslib_1.__extends(SortedEmpty, _super);
function SortedEmpty() {
return _super !== null && _super.apply(this, arguments) || this;
}
SortedEmpty.prototype.min = function (otherwise) {
return (0, common_1.OptLazy)(otherwise);
};
SortedEmpty.prototype.max = function (otherwise) {
return (0, common_1.OptLazy)(otherwise);
};
SortedEmpty.prototype.getAtIndex = function (index, otherwise) {
return (0, common_1.OptLazy)(otherwise);
};
SortedEmpty.prototype.take = function () {
return this;
};
SortedEmpty.prototype.drop = function () {
return this;
};
SortedEmpty.prototype.sliceIndex = function () {
return this;
};
return SortedEmpty;
}(map_custom_1.EmptyBase));
exports.SortedEmpty = SortedEmpty;
/**
* Abstract base class for non‑empty sorted collections.<br/>
* <br/>
* It exposes the common index‑based operations and structural mutation
* helpers shared by the sorted map and set node implementations.
* @typeparam E - the stored entry type
* @typeparam TS - the concrete non‑empty node type
*/
var SortedNonEmptyBase = /** @class */ (function (_super) {
tslib_1.__extends(SortedNonEmptyBase, _super);
function SortedNonEmptyBase() {
return _super !== null && _super.apply(this, arguments) || this;
}
Object.defineProperty(SortedNonEmptyBase.prototype, "mutateEntries", {
get: function () {
return this.entries;
},
enumerable: false,
configurable: true
});
return SortedNonEmptyBase;
}(map_custom_1.NonEmptyBase));
exports.SortedNonEmptyBase = SortedNonEmptyBase;
/**
* Removes and returns the minimum entry from the given leaf node while
* returning the updated leaf.
* @param source - the leaf node to operate on
*/
function leafDeleteMin(source) {
return [source.entries[0], source.copy(base_1.Arr.tail(source.entries))];
}
/**
* Removes and returns the maximum entry from the given leaf node while
* returning the updated leaf.
* @param source - the leaf node to operate on
*/
function leafDeleteMax(source) {
return [base_1.Arr.last(source.entries), source.copy(base_1.Arr.init(source.entries))];
}
/**
* Splits the given leaf node into two nodes and returns the promoted
* separator entry together with the newly created right node.<br/>
* <br/>
* The split position can be customised through the optional `index`.
* @param source - the leaf node to split
* @param index - (optional) the split index, defaults to the middle entry
*/
function leafMutateSplitRight(source, index) {
if (index === void 0) { index = source.entries.length >>> 1; }
var rightEntries = source.mutateEntries.splice(index);
var rightNode = source.copy(rightEntries);
var upEntry = rightEntries.shift();
return [upEntry, rightNode];
}
/**
* Moves the given `toLeft` entry from `source` to the left sibling and
* returns the separator entry that should be stored in the parent node.
* @param source - the leaf that donates an entry to the left sibling
* @param left - the left sibling leaf to receive the entry
* @param toLeft - the entry that must end up in the left sibling
*/
function leafMutateGiveToLeft(source, left, toLeft) {
var toUp = source.mutateEntries.shift();
var newLeft = left.copy(base_1.Arr.append(left.entries, toLeft));
return [toUp, newLeft];
}
/**
* Moves the given `toRight` entry from `source` to the right sibling and
* returns the separator entry that should be stored in the parent node.
* @param source - the leaf that donates an entry to the right sibling
* @param right - the right sibling leaf to receive the entry
* @param toRight - the entry that must end up in the right sibling
*/
function leafMutateGiveToRight(source, right, toRight) {
var toUp = source.mutateEntries.pop();
var newRight = right.copy(base_1.Arr.prepend(right.entries, toRight));
return [toUp, newRight];
}
/**
* Pulls an entry from the left sibling into `source` and returns the
* separator entry that should be stored in the parent node.
* @param source - the leaf receiving an entry
* @param left - the left sibling leaf to donate an entry
* @param toMe - the entry that must end up in `source`
*/
function leafMutateGetFromLeft(source, left, toMe) {
var toUp = base_1.Arr.last(left.entries);
var newLeft = left.copy(base_1.Arr.init(left.entries));
source.mutateEntries.unshift(toMe);
return [toUp, newLeft];
}
/**
* Pulls an entry from the right sibling into `source` and returns the
* separator entry that should be stored in the parent node.
* @param source - the leaf receiving an entry
* @param right - the right sibling leaf to donate an entry
* @param toMe - the entry that must end up in `source`
*/
function leafMutateGetFromRight(source, right, toMe) {
var toUp = right.entries[0];
var newRight = right.copy(base_1.Arr.tail(right.entries));
source.mutateEntries.push(toMe);
return [toUp, newRight];
}
/**
* Joins the given left sibling leaf and `source` into a single leaf by
* inserting the given separator `entry` between them.
* @param source - the right leaf that will absorb the left sibling
* @param left - the left sibling leaf to merge
* @param entry - the separator entry from the parent
*/
function leafMutateJoinLeft(source, left, entry) {
source.mutateEntries.unshift(entry);
source.entries = left.entries.concat(source.entries);
}
/**
* Joins the given right sibling leaf and `source` into a single leaf by
* inserting the given separator `entry` between them.
* @param source - the left leaf that will absorb the right sibling
* @param right - the right sibling leaf to merge
* @param entry - the separator entry from the parent
*/
function leafMutateJoinRight(source, right, entry) {
source.mutateEntries.push(entry);
source.entries = source.entries.concat(right.entries);
}
/**
* Removes and returns the minimum entry from the left‑most child of the
* given inner node and returns the updated node.
* @param source - the inner node to operate on
*/
function innerDeleteMin(source) {
var _a = tslib_1.__read(source.children[0].deleteMin(), 2), min = _a[0], newFirst = _a[1];
var newSelf = source.normalizeIncreaseChild(0, newFirst, source.size - 1);
return [min, newSelf];
}
/**
* Removes and returns the maximum entry from the right‑most child of the
* given inner node and returns the updated node.
* @param source - the inner node to operate on
*/
function innerDeleteMax(source) {
var lastChildIndex = source.children.length - 1;
var _a = tslib_1.__read(source.children[lastChildIndex].deleteMax(), 2), max = _a[0], newLast = _a[1];
var newSelf = source.normalizeIncreaseChild(lastChildIndex, newLast, source.size - 1);
return [max, newSelf];
}
/**
* Splits the given inner node into two nodes and returns the promoted
* separator entry together with the new right node.<br/>
* <br/>
* The split position can be customised through the optional `index`.
* @param source - the inner node to split
* @param index - (optional) the split index, defaults to the middle entry
*/
function innerMutateSplitRight(source, index) {
if (index === void 0) { index = source.entries.length >>> 1; }
var size = source.size;
var rightEntries = source.mutateEntries.splice(index - 1);
var rightChildren = source.mutateChildren.splice(index);
source.size = source.children.reduce(function (r, c) { return r + c.size; }, source.entries.length);
var rightSize = size - source.size - 1;
var upEntry = rightEntries.shift();
var rightNode = source.copy(rightEntries, rightChildren, rightSize);
return [upEntry, rightNode];
}
/**
* Moves the given `toLeft` entry and its child from `source` to the left
* sibling and returns the separator entry that should be stored in the
* parent node.
* @param source - the inner node donating an entry to the left sibling
* @param left - the left sibling node to receive the entry and child
* @param toLeft - the entry that must end up in the left sibling
*/
function innerMutateGiveToLeft(source, left, toLeft) {
var toUp = source.mutateEntries.shift();
var toLeftChild = source.mutateChildren.shift();
source.size -= toLeftChild.size + 1;
var newLeft = left.copy(base_1.Arr.append(left.entries, toLeft), base_1.Arr.append(left.children, toLeftChild), left.size + toLeftChild.size + 1);
return [toUp, newLeft];
}
/**
* Moves the given `toRight` entry and its child from `source` to the right
* sibling and returns the separator entry that should be stored in the
* parent node.
* @param source - the inner node donating an entry to the right sibling
* @param right - the right sibling node to receive the entry and child
* @param toRight - the entry that must end up in the right sibling
*/
function innerMutateGiveToRight(source, right, toRight) {
var toUp = source.mutateEntries.pop();
var toRightChild = source.mutateChildren.pop();
source.size -= toRightChild.size + 1;
var newRight = right.copy(base_1.Arr.prepend(right.entries, toRight), base_1.Arr.prepend(right.children, toRightChild), right.size + toRightChild.size + 1);
return [toUp, newRight];
}
/**
* Pulls an entry and child from the left sibling into `source` and returns
* the separator entry that should be stored in the parent node.
* @param source - the inner node receiving the entry and child
* @param left - the left sibling node to donate the entry and child
* @param toMe - the entry that must end up in `source`
*/
function innerMutateGetFromLeft(source, left, toMe) {
var toUp = base_1.Arr.last(left.entries);
var toMeChild = base_1.Arr.last(left.children);
var leftShrink = toMeChild.size + 1;
var newLeft = left.copy(base_1.Arr.init(left.entries), base_1.Arr.init(left.children), left.size - leftShrink);
source.mutateEntries.unshift(toMe);
source.mutateChildren.unshift(toMeChild);
source.size += leftShrink;
return [toUp, newLeft];
}
/**
* Pulls an entry and child from the right sibling into `source` and returns
* the separator entry that should be stored in the parent node.
* @param source - the inner node receiving the entry and child
* @param right - the right sibling node to donate the entry and child
* @param toMe - the entry that must end up in `source`
*/
function innerMutateGetFromRight(source, right, toMe) {
var toUp = right.entries[0];
var toMeChild = right.children[0];
var rightShrink = toMeChild.size + 1;
var newLeft = right.copy(base_1.Arr.tail(right.entries), base_1.Arr.tail(right.children), right.size - rightShrink);
source.mutateEntries.push(toMe);
source.mutateChildren.push(toMeChild);
source.size += rightShrink;
return [toUp, newLeft];
}
/**
* Joins the given left sibling inner node and `source` into a single node by
* inserting the given separator `entry` between them.
* @param source - the right inner node that will absorb the left sibling
* @param left - the left sibling node to merge
* @param entry - the separator entry from the parent
*/
function innerMutateJoinLeft(source, left, entry) {
source.mutateEntries.unshift(entry);
source.entries = left.entries.concat(source.entries);
source.children = left.children.concat(source.children);
source.size += left.size + 1;
}
/**
* Joins the given right sibling inner node and `source` into a single node by
* inserting the given separator `entry` between them.
* @param source - the left inner node that will absorb the right sibling
* @param right - the right sibling node to merge
* @param entry - the separator entry from the parent
*/
function innerMutateJoinRight(source, right, entry) {
source.mutateEntries.push(entry);
source.entries = source.entries.concat(right.entries);
source.children = source.children.concat(right.children);
source.size += right.size + 1;
}
/**
* Normalises the given child after it has decreased in size so that it
* satisfies the minimum entry constraint of the B‑tree.<br/>
* <br/>
* Depending on the neighbouring children this may rotate entries between
* nodes or perform a split.
* @param source - the inner node that contains the child
* @param childIndex - the index of the child within `source`
* @param newChild - the updated child node
* @param newSize - the new total size for `source`
*/
function innerNormalizeDownsizeChild(source, childIndex, newChild, newSize) {
// try to shift
var leftChild = source.children[childIndex - 1];
var rightChild = source.children[childIndex + 1];
if ((undefined === leftChild ||
leftChild.entries.length >= source.context.maxEntries) &&
(undefined === rightChild ||
rightChild.entries.length >= source.context.maxEntries)) {
// cannot shift
var _a = tslib_1.__read(newChild.mutateSplitRight(), 2), upEntry = _a[0], rightChild_1 = _a[1];
var newEntries_1 = base_1.Arr.insert(source.entries, childIndex, upEntry);
var newChildren_1 = base_1.Arr.splice(source.children, childIndex, 1, newChild, rightChild_1);
return source.copy(newEntries_1, newChildren_1, newSize);
}
if (undefined !== leftChild &&
(undefined === rightChild ||
rightChild.entries.length >= leftChild.entries.length)) {
// shiftleft
var _b = tslib_1.__read(newChild.mutateGiveToLeft(leftChild, source.entries[childIndex - 1]), 2), newSep_1 = _b[0], newLeft = _b[1];
var newEntries_2 = base_1.Arr.update(source.entries, childIndex - 1, newSep_1);
var newChildren_2 = base_1.Arr.splice(source.children, childIndex - 1, 2, newLeft, newChild);
return source.copy(newEntries_2, newChildren_2, newSize);
}
// shiftright
var _c = tslib_1.__read(newChild.mutateGiveToRight(rightChild, source.entries[childIndex]), 2), newSep = _c[0], newRight = _c[1];
var newEntries = base_1.Arr.update(source.entries, childIndex, newSep);
var newChildren = base_1.Arr.splice(source.children, childIndex, 2, newChild, newRight);
return source.copy(newEntries, newChildren, newSize);
}
/**
* Normalises the given child after it has increased in size so that it
* satisfies the maximum entry constraint of the B‑tree.<br/>
* <br/>
* Depending on the neighbouring children this may rotate entries between
* nodes or merge nodes together.
* @param source - the inner node that contains the child
* @param childIndex - the index of the child within `source`
* @param newChild - the updated child node
* @param newSize - the new total size for `source`
*/
function innerNormalizeIncreaseChild(source, childIndex, newChild, newSize) {
if (newChild.entries.length >= source.context.minEntries) {
var newChildren_3 = base_1.Arr.update(source.children, childIndex, newChild);
return source.copy(undefined, newChildren_3, newSize);
}
// try to shift
var leftChild = source.children[childIndex - 1];
var rightChild = source.children[childIndex + 1];
if ((undefined === leftChild ||
leftChild.entries.length <= source.context.minEntries) &&
(undefined === rightChild ||
rightChild.entries.length <= source.context.minEntries)) {
// cannot shift
if (undefined !== leftChild) {
newChild.mutateJoinLeft(leftChild, source.entries[childIndex - 1]);
var newEntries_3 = base_1.Arr.splice(source.entries, childIndex - 1, 1);
var newChildren_4 = base_1.Arr.splice(source.children, childIndex - 1, 2, newChild);
return source.copy(newEntries_3, newChildren_4, newSize);
}
newChild.mutateJoinRight(rightChild, source.entries[childIndex]);
var newEntries_4 = base_1.Arr.splice(source.entries, childIndex, 1);
var newChildren_5 = base_1.Arr.splice(source.children, childIndex, 2, newChild);
return source.copy(newEntries_4, newChildren_5, newSize);
}
if (undefined !== leftChild &&
(undefined === rightChild ||
rightChild.entries.length <= leftChild.entries.length)) {
// get from left
var _a = tslib_1.__read(newChild.mutateGetFromLeft(leftChild, source.entries[childIndex - 1]), 2), newSep_2 = _a[0], newLeft = _a[1];
var newEntries_5 = base_1.Arr.update(source.entries, childIndex - 1, newSep_2);
var newChildren_6 = base_1.Arr.splice(source.children, childIndex - 1, 2, newLeft, newChild);
return source.copy(newEntries_5, newChildren_6, newSize);
}
// get from right
var _b = tslib_1.__read(newChild.mutateGetFromRight(rightChild, source.entries[childIndex]), 2), newSep = _b[0], newRight = _b[1];
var newEntries = base_1.Arr.update(source.entries, childIndex, newSep);
var newChildren = base_1.Arr.splice(source.children, childIndex, 2, newChild, newRight);
return source.copy(newEntries, newChildren, newSize);
}
/**
* Returns the index of the element in the sources element array, or a tuple with the child index and the index within the child
* @param source the collection to operate on
* @param index the index to find
*/
function innerGetSubIndex(source, index) {
var elemIndex = -1;
var i = index;
while (true) {
if (elemIndex >= 0) {
if (i === 0)
return elemIndex;
i--;
}
else {
var childIndex = index_cjs_1.SortedIndex.next(elemIndex);
var child = source.children[childIndex];
if (i < child.size)
return [childIndex, i];
i -= child.size;
}
elemIndex = index_cjs_1.SortedIndex.next(elemIndex);
}
}
/**
* Returns the entry at the given `index` from the B‑tree represented by the
* given inner node, or the provided fallback value if the index is out of
* bounds.<br/>
* <br/>
* Negative indices are interpreted from the end of the collection.
* @param source - the inner node to read from
* @param index - the (possibly negative) index to look up
* @param otherwise - (default: undefined) fallback value when out of bounds
*/
function innerGetAtIndex(source, index, otherwise) {
if (index >= source.size || -index > source.size)
return (0, common_1.OptLazy)(otherwise);
if (index < 0)
return innerGetAtIndex(source, source.size + index, otherwise);
var subIndex = innerGetSubIndex(source, index);
if (Array.isArray(subIndex))
return source.children[subIndex[0]].getAtIndex(subIndex[1], otherwise);
return source.entries[subIndex];
}
/**
* Returns a new inner node containing only the first `amount` of entries
* and children of the given `source` node.<br/>
* <br/>
* The amount must be between `1` and `source.size - 1`.
* @param source - the inner node to operate on
* @param amount - the amount of entries to keep
*/
function innerTakeInternal(source, amount) {
if (amount <= 0 || amount >= source.size)
base_1.RimbuError.throwInvalidStateError();
var indexResult = innerGetSubIndex(source, amount - 1);
if (Array.isArray(indexResult)) {
var _a = tslib_1.__read(indexResult, 2), childIndex = _a[0], inChildIndex = _a[1];
var untilChild = childIndex;
var entries_1 = source.entries.slice(0, untilChild);
var children_1 = source.children.slice(0, untilChild);
var takeAmount = inChildIndex + 1;
var lastChild = source.children[childIndex].takeInternal(takeAmount);
children_1.push(lastChild);
var result = source.context.inner(entries_1, children_1, amount);
if (childIndex === 0)
return result;
while (base_1.Arr.last(result.children).entries.length < source.context.minEntries) {
result = result.normalizeIncreaseChild(result.entries.length, base_1.Arr.last(result.children), amount);
}
return result;
}
var elemIndex = indexResult;
var entries = source.entries.slice(0, elemIndex);
var children = source.children.slice(0, elemIndex + 1);
return source.context
.inner(entries, children, amount - 1)
.addInternal(source.entries[elemIndex]);
}
/**
* Returns a new inner node containing all entries and children of the given
* `source` node except for the first `amount` entries.<br/>
* <br/>
* The amount must be between `1` and `source.size - 1`.
* @param source - the inner node to operate on
* @param amount - the amount of entries to drop
*/
function innerDropInternal(source, amount) {
if (amount <= 0 || amount >= source.size)
base_1.RimbuError.throwInvalidStateError();
var newSize = source.size - amount;
var indexResult = innerGetSubIndex(source, amount);
if (Array.isArray(indexResult)) {
var _a = tslib_1.__read(indexResult, 2), childIndex = _a[0], inChildIndex = _a[1];
var fromChild = childIndex;
var entries_2 = source.entries.slice(fromChild);
var children_2 = source.children.slice(fromChild + 1);
var dropAmount = inChildIndex;
var firstChild = source.children[childIndex].dropInternal(dropAmount);
children_2.unshift(firstChild);
var result = source.context.inner(entries_2, children_2, newSize);
if (childIndex === source.entries.length)
return result;
while (result.children[0].entries.length < source.context.minEntries) {
result = result.normalizeIncreaseChild(0, result.children[0], newSize);
}
return result;
}
var elemIndex = indexResult;
var entries = source.entries.slice(elemIndex + 1);
var children = source.children.slice(elemIndex + 1);
return source.context
.inner(entries, children, newSize - 1)
.addInternal(source.entries[elemIndex]);
}
/**
* Returns a `Stream` of entries of the given inner node limited to the
* provided index `range`.<br/>
* <br/>
* When `reversed` is true, the stream iterates the selected range in
* reverse order.
* @param source - the inner node to stream from
* @param range - the index range to include
* @param reversed - (default: false) when true reverses the stream order
*/
function innerStreamSliceIndex(source, range, reversed) {
if (reversed === void 0) { reversed = false; }
var result = common_1.IndexRange.getIndicesFor(range, source.size);
if (result === 'all')
return source.stream({ reversed: reversed });
if (result === 'empty')
return stream_1.Stream.empty();
var _a = tslib_1.__read(result, 2), startIndex = _a[0], endIndex = _a[1];
var startSubIndex = innerGetSubIndex(source, startIndex);
var endSubIndex = innerGetSubIndex(source, endIndex);
var startElemIndex = 0;
var inStartElemIndex = 0;
if (Array.isArray(startSubIndex)) {
startElemIndex = index_cjs_1.SortedIndex.prev(startSubIndex[0]);
inStartElemIndex = startSubIndex[1];
}
else {
startElemIndex = startSubIndex;
}
var endElemIndex = 0;
var inEndElemIndex = 0;
if (Array.isArray(endSubIndex)) {
endElemIndex = index_cjs_1.SortedIndex.prev(endSubIndex[0]);
inEndElemIndex = endSubIndex[1];
}
else {
endElemIndex = endSubIndex;
}
if (startElemIndex === endElemIndex) {
if (startElemIndex >= 0)
return stream_1.Stream.of(source.entries[startElemIndex]);
return source.children[index_cjs_1.SortedIndex.next(startElemIndex)].streamSliceIndex({
start: inStartElemIndex,
end: inEndElemIndex,
}, { reversed: reversed });
}
var indices = reversed
? stream_1.Stream.unfold(endElemIndex, function (i, _, stop) {
return index_cjs_1.SortedIndex.compare(i, startElemIndex) <= 0 ? stop : index_cjs_1.SortedIndex.prev(i);
})
: stream_1.Stream.unfold(startElemIndex, function (i, _, stop) {
return index_cjs_1.SortedIndex.compare(i, endElemIndex) >= 0 ? stop : index_cjs_1.SortedIndex.next(i);
});
return indices.flatMap(function (index) {
if (index >= 0)
return stream_1.Stream.of(source.entries[index]);
var childIndex = index_cjs_1.SortedIndex.next(index);
var child = source.children[childIndex];
if (index === startElemIndex) {
return child.streamSliceIndex({ start: inStartElemIndex }, { reversed: reversed });
}
if (index === endElemIndex) {
return child.streamSliceIndex({ end: inEndElemIndex }, { reversed: reversed });
}
return child.stream({ reversed: reversed });
});
}
/**
* Abstract base class for mutable sorted builders used by the sorted map
* and set implementations.<br/>
* <br/>
* It encapsulates the shared tree‑based logic for computing `min`, `max`,
* index‑based access and traversal while allowing concrete builders to plug
* in their own entry and child representations.
* @typeparam E - the entry type stored in the builder
*/
var SortedBuilder = /** @class */ (function () {
function SortedBuilder() {
this._lock = 0;
}
/**
* Throws an error when the builder is mutated while it is being iterated,
* for example from within {@link SortedBuilder.forEach}.
*/
SortedBuilder.prototype.checkLock = function () {
if (this._lock)
base_1.RimbuError.throwModifiedBuilderWhileLoopingOverItError();
};
Object.defineProperty(SortedBuilder.prototype, "entries", {
get: function () {
this.prepareMutate();
return this._entries;
},
set: function (value) {
this.prepareMutate();
this.source = undefined;
this._entries = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(SortedBuilder.prototype, "hasChildren", {
get: function () {
return undefined !== this._children && this._children.length > 0;
},
enumerable: false,
configurable: true
});
Object.defineProperty(SortedBuilder.prototype, "isEmpty", {
get: function () {
return this.size === 0;
},
enumerable: false,
configurable: true
});
/**
* Returns the minimum entry of the builder, or the given fallback value
* if the builder is empty.
* @param otherwise - (default: undefined) fallback value when empty
*/
SortedBuilder.prototype.min = function (otherwise) {
if (undefined !== this.source)
return this.source.min(otherwise);
if (this.size === 0)
return (0, common_1.OptLazy)(otherwise);
if (this.hasChildren)
return this.children[0].min(otherwise);
return this.entries[0];
};
/**
* Returns the maximum entry of the builder, or the given fallback value
* if the builder is empty.
* @param otherwise - (default: undefined) fallback value when empty
*/
SortedBuilder.prototype.max = function (otherwise) {
if (undefined !== this.source)
return this.source.max(otherwise);
if (this.size === 0)
return (0, common_1.OptLazy)(otherwise);
if (this.hasChildren)
return base_1.Arr.last(this.children).max(otherwise);
else
return base_1.Arr.last(this.entries);
};
/**
* Returns the entry at the given `index`, or the provided fallback value
* when the index is out of bounds.<br/>
* <br/>
* Negative indices are interpreted from the end of the builder.
* @param index - the (possibly negative) index to look up
* @param otherwise - (default: undefined) fallback value when out of bounds
*/
SortedBuilder.prototype.getAtIndex = function (index, otherwise) {
if (undefined !== this.source) {
return this.source.getAtIndex(index, otherwise);
}
if (index >= this.size || -index > this.size) {
return (0, common_1.OptLazy)(otherwise);
}
if (index < 0) {
return this.getAtIndex(this.size + index, otherwise);
}
if (!this.hasChildren)
return this.entries[index];
var elemIndex = -1;
var i = index;
while (true) {
if (elemIndex >= 0) {
if (i === 0)
return this.entries[elemIndex];
i--;
}
else {
var childIndex = index_cjs_1.SortedIndex.next(elemIndex);
var child = this.children[childIndex];
if (i < child.size)
return child.getAtIndex(i, otherwise);
i -= child.size;
}
elemIndex = index_cjs_1.SortedIndex.next(elemIndex);
}
};
/**
* Calls the given function `f` for every entry in the builder in key
* sort‑order, passing in the entry, its zero‑based index and a `halt`
* function that can be used to stop iteration early.
* @param f - the callback function to invoke for each entry
* @param options - (optional) traversal options including a custom state
*/
SortedBuilder.prototype.forEach = function (f, options) {
if (options === void 0) { options = {}; }
var _a = options.state, state = _a === void 0 ? (0, common_1.TraverseState)() : _a;
if (state.halted || this.isEmpty)
return;
this._lock++;
if (undefined !== this.source) {
this.source.forEach(f, { state: state });
}
else {
if (!this.hasChildren) {
base_1.Arr.forEach(this.entries, f, state);
}
else {
var i = -1;
var entryLength = this.entries.length;
var halt = state.halt;
while (!state.halted && i < entryLength) {
if (i >= 0)
f(this.entries[i], state.nextIndex(), halt);
else {
var childIndex = index_cjs_1.SortedIndex.next(i);
this.children[childIndex].forEach(f, { state: state });
}
i = index_cjs_1.SortedIndex.next(i);
}
}
}
this._lock--;
};
/**
* Restores the builder invariants after mutations by splitting the root
* node when it grows beyond the configured maximum block size.
*/
SortedBuilder.prototype.normalize = function () {
if (this.entries.length === 0) {
if (!this.hasChildren)
return;
var firstChild = this.children[0];
this.entries = firstChild.entries;
this.children = firstChild.children;
this.size = firstChild.size;
return;
}
if (this.entries.length <= this.context.maxEntries)
return;
var index = this.entries.length >>> 1;
var leftEntries = this.entries;
var rightEntries = leftEntries.splice(index);
var upEntry = rightEntries.shift();
if (!this.hasChildren) {
var leftNode = this.createNew(undefined, leftEntries, undefined, leftEntries.length);
var rightNode = this.createNew(undefined, rightEntries, undefined, rightEntries.length);
this.entries = [upEntry];
this.children = [leftNode, rightNode];
}
else {
var leftChildren = this.children;
var rightChildren = leftChildren.splice(index + 1);
var leftSize = leftChildren.reduce(function (r, c) { return r + c.size; }, leftEntries.length);
var rightSize = this.size - leftSize - 1;
var leftNode = this.createNew(undefined, leftEntries, leftChildren, leftSize);
var rightNode = this.createNew(undefined, rightEntries, rightChildren, rightSize);
this.entries = [upEntry];
this.children = [leftNode, rightNode];
}
};
// check whether child at given `childIndex` has gotten too small
// get from neighbouring children if it is too small so child increases
SortedBuilder.prototype.normalizeChildIncrease = function (childIndex) {
var child = this.children[childIndex];
if (child.entries.length >= this.context.minEntries)
return;
var leftChild = this.children[childIndex - 1];
var rightChild = this.children[childIndex + 1];
if ((undefined === leftChild ||
leftChild.entries.length <= this.context.minEntries) &&
(undefined === rightChild ||
rightChild.entries.length <= this.context.minEntries)) {
// cannot shift
if (undefined !== leftChild) {
// join left
leftChild.source = undefined;
var _a = tslib_1.__read(this.entries.splice(childIndex - 1, 1), 1), down_1 = _a[0];
leftChild.entries.push(down_1);
leftChild.entries = leftChild.entries.concat(child.entries);
leftChild.size += child.size + 1;
if (leftChild.hasChildren) {
leftChild.children = leftChild.children.concat(child.children);
}
this.children.splice(childIndex, 1);
return;
}
// join right
rightChild.source = undefined;
child.source = undefined;
var _b = tslib_1.__read(this.entries.splice(childIndex, 1), 1), down = _b[0];
rightChild.entries.unshift(down);
rightChild.entries = child.entries.concat(rightChild.entries);
rightChild.size += child.size + 1;
if (rightChild.hasChildren) {
rightChild.children = child.children.concat(rightChild.children);
}
this.children.splice(childIndex, 1);
return;
}
if (undefined !== leftChild &&
(undefined === rightChild ||
rightChild.entries.length < leftChild.entries.length)) {
// get from left
child.source = undefined;
leftChild.source = undefined;
child.entries.unshift(this.entries[childIndex - 1]);
child.size++;
this.entries[childIndex - 1] = leftChild.entries.pop();
leftChild.size--;
if (leftChild.hasChildren) {
var shiftChild = leftChild.children.pop();
child.children.unshift(shiftChild);
leftChild.size -= shiftChild.size;
child.size += shiftChild.size;
}
return;
}
// get from right
child.source = undefined;
rightChild.source = undefined;
child.entries.push(this.entries[childIndex]);
child.size++;
this.entries[childIndex] = rightChild.entries.shift();
rightChild.size--;
if (rightChild.hasChildren) {
var shiftChild = rightChild.children.shift();
child.children.push(shiftChild);
rightChild.size -= shiftChild.size;
child.size += shiftChild.size;
}
};
// check whether child at given `childIndex` has gotten too large
// shift to neighbouring children if it is too large so child decreases
SortedBuilder.prototype.normalizeChildDecrease = function (childIndex) {
var child = this.children[childIndex];
if (child.entries.length <= this.context.maxEntries)
return;
var leftChild = this.children[childIndex - 1];
var rightChild = this.children[childIndex + 1];
if ((undefined === leftChild ||
leftChild.entries.length >= this.context.maxEntries) &&
(undefined === rightChild ||
rightChild.entries.length >= this.context.maxEntries)) {
// need to split child
child.source = undefined;
var index = (child.entries.length >>> 1) + 1;
var preSize = child.size;
var rightEntries = child.entries.splice(index);
var upEntry = child.entries.pop();
var rightChildren = undefined;
if (child.hasChildren) {
rightChildren = child.children.splice(index);
child.size = child.children.reduce(function (r, c) { return r + c.size; }, child.entries.length);
}
else {
child.size = child.entries.length;
}
var rightSize = preSize - child.size - 1;
var rightNode = this.createNew(undefined, rightEntries, rightChildren, rightSize);
this.entries.splice(childIndex, 0, upEntry);
this.children.splice(childIndex + 1, 0, rightNode);
return;
}
if (undefined !== leftChild &&
(undefined === rightChild ||
rightChild.entries.length >= leftChild.entries.length)) {
// shiftleft
leftChild.source = undefined;
child.source = undefined;
leftChild.entries.push(this.entries[childIndex - 1]);
leftChild.size++;
this.entries[childIndex - 1] = child.entries.shift();
child.size--;
if (child.hasChildren) {
var shiftChild = child.children.shift();
leftChild.children.push(shiftChild);
leftChild.size += shiftChild.size;
child.size -= shiftChild.size;
}
return;
}
rightChild.source = undefined;
child.source = undefined;
rightChild.entries.unshift(this.entries[childIndex]);
rightChild.size++;
this.entries[childIndex] = child.entries.pop();
child.size--;
if (child.hasChildren) {
var shiftChild = child.children.pop();
rightChild.children.unshift(shiftChild);
rightChild.size += shiftChild.size;
child.size -= shiftChild.size;
}
};
/**
* Removes and returns the minimum entry from the builder while keeping
* the internal B‑tree structure valid.
*/
SortedBuilder.prototype.deleteMin = function () {
this.size--;
if (!this.hasChildren)
return this.entries.shift();
var result = this.children[0].deleteMin();
this.normalizeChildIncrease(0);
return result;
};
/**
* Removes and returns the maximum entry from the builder while keeping
* the internal B‑tree structure valid.
*/
SortedBuilder.prototype.deleteMax = function () {
this.size--;
if (!this.hasChildren)
return this.entries.pop();
var lastChildIndex = this.children.length - 1;
var result = this.children[lastChildIndex].deleteMax();
this.normalizeChildIncrease(lastChildIndex);
return result;
};
return SortedBuilder;
}());
exports.SortedBuilder = SortedBuilder;
//# sourceMappingURL=base.cjs.map