UNPKG

@itwin/core-bentley

Version:

Bentley JavaScript core components

319 lines • 17.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Collections */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SortedArray = exports.ReadonlySortedArray = exports.DuplicatePolicy = void 0; exports.shallowClone = shallowClone; exports.lowerBound = lowerBound; /** * A [[CloneFunction]] that, given a value of type T, returns the same value. * Useful as a default argument for functions that can alternatively accept custom logic for cloning values of object type. * @param value The value to clone. * @returns the input value. * @public */ function shallowClone(value) { return value; } /** * Given a sorted array, computes the position at which the specified value should be inserted into the array so that the array remains sorted. * @param value The value whose position is to be computed. * @param list An array of U already sorted according to the comparison criterion. * @param compare The function used to compare the value with elements in `list`. * @returns an object with 'index' corresponding to the computed position and 'equal' set to true if an equivalent element already exists at that index. * @public */ function lowerBound(value, list, compare) { return lowerBoundOfEquivalent(list, (element) => compare(value, element)); } function lowerBoundOfEquivalent(list, criterion) { let low = 0; let high = list.length; while (low < high) { const mid = Math.floor((low + high) / 2); const comp = criterion(list[mid]); if (0 === comp) return { index: mid, equal: true }; else if (comp < 0) high = mid; else low = mid + 1; } return { index: low, equal: false }; } /** Describes how duplicate values are handled when inserting into a [[SortedArray]]. * A "duplicate" value is one that compares equal to a value already present in the array, per the array's comparison function. * @public */ var DuplicatePolicy; (function (DuplicatePolicy) { /** The array allows duplicate values to be inserted. All duplicate values will be adjacent in the array, but the ordering between duplicate values is unspecified. * @note In the presence of duplicate values, functions like [[SortedArray.indexOf]] and [[SortedArray.findEqual]] will return one of the values - exactly which one is unspecified. */ DuplicatePolicy[DuplicatePolicy["Allow"] = 0] = "Allow"; /** Duplicate values are forbidden - when attempting to insert a value equivalent to one already present, the already-present value is retained. */ DuplicatePolicy[DuplicatePolicy["Retain"] = 1] = "Retain"; /** Duplicate values are forbidden - when attempting to insert a value equivalent to one already present, the already-present value is replaced by the new value. * This can be useful when the value type carries additional data that is not evaluated by the comparison function. */ DuplicatePolicy[DuplicatePolicy["Replace"] = 2] = "Replace"; })(DuplicatePolicy || (exports.DuplicatePolicy = DuplicatePolicy = {})); /** * A read-only view of an array of some type T sorted according to some user-supplied criterion. * Duplicate elements may be present, though sub-types may enforce uniqueness of elements. * In the absence of duplicates, a ReadonlySortedArray<T> can behave like a Set<T> where T is an object and equality is determined * by some criterion other than object identity. * * Because the array is always sorted, querying for the presence of an element is performed using binary * search, which is more efficient than a linear search for reasonably large arrays. * * The comparison function must meet the following criteria, given 'lhs' and 'rhs' of type T: * - If lhs is equal to rhs, returns 0 * - If lhs is less than rhs, returns a negative value * - If lhs is greater than rhs, returns a positive value * - If compare(lhs, rhs) returns 0, then compare(rhs, lhs) must also return 0 * - If compare(lhs, rhs) returns a negative value, then compare(rhs, lhs) must return a positive value, and vice versa. * * Note that the array is read-only only from the perspective of its public interface. Mutation methods are defined for internal use by sub-types. * * @see [[SortedArray]] for a general-purpose mutable sorted array. * @public */ class ReadonlySortedArray { _array = []; _compare; _clone; _duplicatePolicy; /** * Construct a new ReadonlySortedArray<T>. * @param compare The function used to compare elements within the array. * @param duplicatePolicy Policy for handling attempts to insert a value when an equivalent value already exists. If the input is a boolean, then `true` indicates [[DuplicatePolicy.Allow]], and `false` indicates [[DuplicatePolicy.Retain]]. * @param clone The function invoked to clone a new element for insertion into the array. The default implementation simply returns its input. */ constructor(compare, duplicatePolicy = false, clone = shallowClone) { this._compare = compare; this._clone = clone; if (typeof duplicatePolicy === "boolean") duplicatePolicy = duplicatePolicy ? DuplicatePolicy.Allow : DuplicatePolicy.Retain; this._duplicatePolicy = duplicatePolicy; } /** The number of elements in the array */ get length() { return this._array.length; } /** Returns true if the array contains no elements. */ get isEmpty() { return 0 === this.length; } /** Returns an iterator over the contents of the array in sorted order, suitable for use in `for-of` loops. */ [Symbol.iterator]() { return this._array[Symbol.iterator](); } /** * Looks up the index of an element comparing equal to the specified value using binary search. * @param value The value to search for * @returns the index of the first equivalent element found in the array, or -1 if no such element exists. */ indexOf(value) { const bound = this.lowerBound(value); return bound.equal ? bound.index : -1; } /** * Returns true if this array contains at least one value comparing equal to the specified value. * @param value The value to search for * @returns true if an equivalent element exists in the array. */ contains(value) { return -1 !== this.indexOf(value); } /** * Looks up an element comparing equal to the specified value using binary search. * @param value The value to search for * @returns the first equivalent element found in the array, or undefined if no such element exists. */ findEqual(value) { const index = this.indexOf(value); return -1 !== index ? this._array[index] : undefined; } /** Find an element that compares as equivalent based on some criterion. If multiple elements are equivalent, the specific one returned is unspecified. * As an example, consider a `SortedArray<ModelState>` which uses `ModelState.id` as its ordering criterion. To find a model by its Id, * use `sortedArray.findEquivalent((element) => compareStrings(element.id, modelId))` where `modelId` is an [[Id64String]]. * @param criterion A function accepting an element and returning 0 if it compares as equivalent, a negative number if it compares as "less-than", or a positive value if it compares as "greater-than". * @returns The first element found that meets the criterion, or `undefined` if no elements meet the criterion. * @see [[indexOfEquivalent]]. * @public */ findEquivalent(criterion) { const index = this.indexOfEquivalent(criterion); return -1 !== index ? this._array[index] : undefined; } /** Find the index of an element that compares as equivalent based on some criterion. If multiple elements are equivalent, the specific one returned is unspecified. * As an example, consider a `SortedArray<ModelState>` which uses `ModelState.id` as its ordering criterion. To find the index of a model by its Id, * use `sortedArray.indexOfEquivalent((element) => compareStrings(element.id, modelId))` where `modelId` is an [[Id64String]]. * @param criterion A function accepting an element and returning 0 if it compares as equivalent, a negative number if the element compares as "less-than", or a positive value if the element compares as "greater-than". * @returns The index of the first element found that meets the criterion, or -1 if no elements meet the criterion. * @public */ indexOfEquivalent(criterion) { // NB: Must invert the ordering. const bound = lowerBoundOfEquivalent(this._array, (elem) => 0 - criterion(elem)); return bound.equal ? bound.index : -1; } /** * Looks up an element by its index in the array. * @param index The array index * @returns the element corresponding to that position in the array, or undefined if the supplied index exceeds the length of the array. */ get(index) { return index < this.length ? this._array[index] : undefined; } /** Apply a function to each element in the array, in sorted order. * @param func The function to be applied. */ forEach(func) { for (let i = 0; i < this.length; i++) func(this._array[i]); } /** The equivalent of [Array.slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice). */ slice(start, end) { const slice = new ReadonlySortedArray(this._compare, this._duplicatePolicy, this._clone); slice._array = this._array.slice(start, end); return slice; } /** * Computes the position at which the specified value should be inserted to maintain sorted order. * @param value The value whose position is to be computed. * @returns an object with 'index' corresponding to the computed position and 'equal' set to true if an equivalent element already exists at that index. */ lowerBound(value) { return lowerBound(value, this._array, this._compare); } /** Clears the contents of the sorted array. */ _clear() { this._array.length = 0; } /** Extracts the sorted array as a T[] and empties the contents of this ReadonlySortedArray. * @returns the contents of this ReadonlySortedArray as a T[]. */ _extractArray() { const result = this._array; this._array = []; return result; } /** * Attempts to insert a new value into the array at a position determined by the ordering. * The behavior differs based on the array's [[DuplicatePolicy]]: * If duplicates are **not** permitted, then: * - If an equivalent element already exists in the array: * - [[DuplicatePolicy.Retain]]: nothing will be inserted and the index of the existing element will be returned. * - [[DuplicatePolicy.Replace]]: the input value will overwrite the existing element at the same index and that index will be returned. * - Otherwise, the element is inserted and its index is returned. * If duplicates **are** permitted, then: * - The element will be inserted in a correct position based on the sorting criterion; * - The position of the element relative to other elements comparing as equal to it is unspecified; and * - The actual index of the newly-inserted element is returned. * If the element is to be inserted, then the supplied value will be passed to the clone function supplied to the constructor and the result will be inserted into the array. * @param value The value to insert * @param onInsert The optional callback method to call if insertion occurs with the inserted value * @returns the index in the array of the newly-inserted value, or, if duplicates are not permitted and an equivalent value already exists, the index of the equivalent value. */ _insert(value, onInsert) { const bound = this.lowerBound(value); if (bound.equal) { switch (this._duplicatePolicy) { case DuplicatePolicy.Retain: return bound.index; case DuplicatePolicy.Replace: this._array[bound.index] = this._clone(value); if (onInsert) onInsert(value); return bound.index; } } this._array.splice(bound.index, 0, this._clone(value)); if (undefined !== onInsert) onInsert(value); return bound.index; } /** * Removes the first occurrence of a value comparing equal to the specified value from the array. * @param value The value of the element to delete * @returns the index of the deleted value, or -1 if no such element exists. */ _remove(value) { const bound = this.lowerBound(value); if (bound.equal) { this._array.splice(bound.index, 1); return bound.index; } else { return -1; } } } exports.ReadonlySortedArray = ReadonlySortedArray; /** * Maintains an array of some type T in sorted order. The ordering is specified by a function supplied * by the user. * By default, only unique elements are permitted; attempting to insert a new element that compares * as equal to an element already in the array will not modify the contents of the array. * * This allows a SortedArray<T> to behave like a Set<T> where T is an object and equality is determined * by some criterion other than object identity. * * Because the array is always sorted, querying for the presence of an element is performed using binary * search, which is more efficient than a linear search for reasonably large arrays. * * The user can also specify how the SortedArray takes ownership of inserted values, e.g., by cloning them. * * The comparison function must meet the following criteria, given 'lhs' and 'rhs' of type T: * - If lhs is equal to rhs, returns 0 * - If lhs is less than rhs, returns a negative value * - If lhs is greater than rhs, returns a positive value * - If compare(lhs, rhs) returns 0, then compare(rhs, lhs) must also return 0 * - If compare(lhs, rhs) returns a negative value, then compare(rhs, lhs) must return a positive value, and vice versa. * * Modifying an element in a way that affects the comparison function will produce unpredictable results, the * most likely of which is that the array will cease to be sorted. * @public */ class SortedArray extends ReadonlySortedArray { /** * Construct a new SortedArray<T>. * @param compare The function used to compare elements within the array. * @param duplicatePolicy Policy for handling attempts to insert a value when an equivalent value already exists. If the input is a boolean, then `true` indicates [[DuplicatePolicy.Allow]], and `false` indicates [[DuplicatePolicy.Retain]]. * @param clone The function invoked to clone a new element for insertion into the array. The default implementation simply returns its input. */ constructor(compare, duplicatePolicy = false, clone = shallowClone) { super(compare, duplicatePolicy, clone); } /** Clears the contents of the sorted array. */ clear() { this._clear(); } /** Extracts the sorted array as a T[] and empties the contents of this SortedArray. * @returns the contents of this SortedArray as a T[]. */ extractArray() { return this._extractArray(); } /** * Attempts to insert a new value into the array at a position determined by the ordering. * The behavior differs based on whether or not duplicate elements are permitted. * If duplicates are **not** permitted, then: * - If an equivalent element already exists in the array, nothing will be inserted and the index of the existing element will be returned. * - Otherwise, the element is inserted and its index is returned. * If duplicates **are** permitted, then: * - The element will be inserted in a correct position based on the sorting criterion; * - The position of the element relative to other elements comparing as equal to it is unspecified; and * - The actual index of the newly-inserted element is returned. * If the element is to be inserted, then the supplied value will be passed to the clone function supplied to the constructor and the result will be inserted into the array. * @param value The value to insert * @param onInsert The optional callback method to call if insertion occurs with the inserted value * @returns the index in the array of the newly-inserted value, or, if duplicates are not permitted and an equivalent value already exists, the index of the equivalent value. */ insert(value, onInsert) { return this._insert(value, onInsert); } /** * Removes the first occurrence of a value comparing equal to the specified value from the array. * @param value The value of the element to delete * @returns the index of the deleted value, or -1 if no such element exists. */ remove(value) { return this._remove(value); } /** The equivalent of [Array.slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice). */ slice(start, end) { const slice = new SortedArray(this._compare, this._duplicatePolicy, this._clone); slice._array = this._array.slice(start, end); return slice; } } exports.SortedArray = SortedArray; //# sourceMappingURL=SortedArray.js.map