@itwin/core-bentley
Version:
Bentley JavaScript core components
432 lines • 20.2 kB
JavaScript
"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 Ids
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MutableCompressedId64Set = exports.OrderedId64Array = exports.CompressedId64Set = void 0;
const Assert_1 = require("./Assert");
const Id_1 = require("./Id");
const OrderedId64Iterable_1 = require("./OrderedId64Iterable");
const SortedArray_1 = require("./SortedArray");
/** A compact string representation of an [[Id64Set]]. Such a representation is useful when serializing potentially very large
* sets of Ids.
* @see [[CompressedId64Set.iterable]] to efficiently iterate the Ids represented by a compact string.
* @see [[CompressedId64Set.compressSet]] and [[CompressedId64Set.compressArray]] to produce a compact string from a collection of Ids.
* @see [[CompressedId64Set.decompressSet]] and [[CompressedId64Set.decompressArray]] to produce a collection of Ids from a compact string.
* @see [[OrderedId64Iterable]] for a generic representation of an ordered set of Ids (compressed or otherwise).
* @see [[MutableCompressedId64Set]] for a mutable version.
* @public
*/
var CompressedId64Set;
(function (CompressedId64Set) {
function isHexDigit(ch) {
// ascii values:
// '0' = 48
// '9' = 57
// 'a' = 65
// 'f' = 70
return (ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 70);
}
function compactRange(increment, length) {
(0, Assert_1.assert)(length > 0);
const inc = `+${increment.toString()}`;
if (length <= 1)
return inc;
const len = length.toString(16).toUpperCase();
return `${inc}*${len}`;
}
/** Given a set of [[Id64String]]s, produce a compact string representation. Useful when serializing potentially large sets of Ids.
* @note Invalid Ids are ignored.
* @see [[CompressedId64Set.sortAndCompress]] to compress any unordered collection of Ids.
* @see [[CompressedId64Set.compressArray]] to perform the same operation on an [[Id64Array]].
* @see [[CompressedId64Set.decompressSet]] to perform the inverse operation.
*/
function compressSet(ids) {
return sortAndCompress(ids);
}
CompressedId64Set.compressSet = compressSet;
/** Create a sorted array from `ids`, then return a compact string representation of those Ids.
* @see [[CompressedId64Set.compressIds]] if `ids` is known to already be sorted.
*/
function sortAndCompress(ids) {
// `string` is an Iterable<string>. In that case assume caller passed a single Id64String.
const arr = typeof ids === "string" ? [ids] : Array.from(ids);
OrderedId64Iterable_1.OrderedId64Iterable.sortArray(arr);
return compressArray(arr);
}
CompressedId64Set.sortAndCompress = sortAndCompress;
/** Give a **numerically-ordered** array of [[Id64String]]s, produce a compact string representation. Useful when serializing potentially large sets of Ids.
* Duplicate Ids are included only once in the string representation.
* @throws Error if two consecutive Ids `x` and `y` exist such that the numerical value of `x` is greater than that of `y` - i.e., the array is not properly sorted.
* @note The array must be sorted according to the 64-bit numerical value of each Id.
* @note Invalid Ids are ignored.
* @see [[CompressedId64Set.decompressArray]] to perform the inverse operation.
* @see [[OrderedId64Iterable.sortArray]] to ensure the Ids are properly sorted.
* @see [[CompressedId64Set.sortAndCompress]] to compress any unordered collection of Ids.
*/
function compressArray(ids) {
return compressIds(ids);
}
CompressedId64Set.compressArray = compressArray;
/** Give a **numerically-ordered** collection of [[Id64String]]s, produce a compact string representation. Useful when serializing potentially large sets of Ids.
* Duplicate Ids are included only once in the string representation.
* @throws Error if two consecutive Ids `x` and `y` exist such that the numerical value of `x` is greater than that of `y` - i.e., the collection is not properly sorted.
* @note The collection must be sorted according to the 64-bit numerical value of each Id.
* @note Invalid Ids are ignored.
* @see [[CompressedId64Set.iterable]] to perform the inverse operation.
* @see [[OrderedId64Iterable.sortArray]] or [[OrderedId64Iterable.compare]] to ensure the Ids are properly sorted.
* @see [[CompressedId64Set.sortAndCompress]] to compress any unordered collection of Ids.
*/
function compressIds(ids) {
if ("string" === typeof ids)
return ids;
let str = "";
const prevId = new Uint64();
const rangeIncrement = new Uint64();
let rangeLen = 0;
const curId = new Uint64();
const curIncrement = new Uint64();
for (const id of ids) {
if (!Id_1.Id64.isValidId64(id))
continue; // ignore garbage and invalid Ids ("0")
curId.setFromId(id);
curIncrement.setFromDifference(curId, prevId);
const cmp = prevId.compare(curId);
if (0 === cmp)
continue; // ignore duplicates
else if (cmp > 0)
throw new Error("CompressedId64Set.compressArray requires a sorted array as input");
prevId.copyFrom(curId);
if (0 === rangeLen) {
rangeIncrement.copyFrom(curIncrement);
rangeLen = 1;
}
else if (curIncrement.equals(rangeIncrement)) {
++rangeLen;
}
else {
str += compactRange(rangeIncrement, rangeLen);
rangeIncrement.copyFrom(curIncrement);
rangeLen = 1;
}
}
if (0 < rangeLen)
str += compactRange(rangeIncrement, rangeLen);
return str;
}
CompressedId64Set.compressIds = compressIds;
/** This exists strictly for the purposes of compressed sets of 64-bit Ids, to avoid the overhead of BigInt for handling 64-bit integers. */
class Uint64 {
lower;
upper;
static _base = 0x100000000;
static assertUint32(num) {
(0, Assert_1.assert)(num >= 0);
(0, Assert_1.assert)(num < Uint64._base);
(0, Assert_1.assert)(Math.floor(num) === num);
}
assertConstraints() {
Uint64.assertUint32(this.lower);
Uint64.assertUint32(this.upper);
}
constructor(lower = 0, upper = 0) {
this.lower = lower;
this.upper = upper;
this.assertConstraints();
}
compare(rhs) {
const diff = this.upper - rhs.upper;
return 0 === diff ? this.lower - rhs.lower : diff;
}
equals(rhs) { return 0 === this.compare(rhs); }
isLessThan(rhs) { return this.compare(rhs) < 0; }
isGreaterThan(rhs) { return this.compare(rhs) > 0; }
get isZero() { return 0 === this.lower && 0 === this.upper; }
setFromDifference(lhs, rhs) {
(0, Assert_1.assert)(!rhs.isGreaterThan(lhs));
this.lower = lhs.lower - rhs.lower;
this.upper = lhs.upper - rhs.upper;
if (this.lower < 0) {
this.lower += Uint64._base;
this.upper -= 1;
}
}
add(rhs) {
let lower = rhs.lower;
let upper = rhs.upper;
if (lower + this.lower >= Uint64._base) {
lower -= Uint64._base;
upper += 1;
}
this.lower += lower;
this.upper += upper;
this.assertConstraints();
}
setFromId(id) {
Id_1.Id64.getUint32Pair(id, this);
}
copyFrom(other) {
this.lower = other.lower;
this.upper = other.upper;
}
toString() {
if (0 === this.upper)
return this.lower.toString(16).toUpperCase();
const upper = this.upper.toString(16);
const lower = this.lower.toString(16).padStart(8, "0");
(0, Assert_1.assert)(lower.length === 8);
return `${upper}${lower}`.toUpperCase();
}
toId64String() {
return Id_1.Id64.fromUint32Pair(this.lower, this.upper);
}
}
/** Supplies an iterator over the [[Id64String]]s in a [[CompressedId64Set]].
* The Ids are iterated in ascending order based on their unsigned 64-bit integer values.
*/
function* iterator(ids) {
if (0 === ids.length)
return; // empty set.
if ("+" !== ids[0])
throw new Error("Invalid CompressedId64Set");
let curIndex = 1; // skip the leading '+'
const curId = new Uint64();
function parseUint32() {
let value = 0;
let nChars = 0;
while (curIndex < ids.length && nChars < 8) {
++nChars;
const ch = ids.charCodeAt(curIndex);
if (!isHexDigit(ch))
break; // not a hex digit in [0..9] or [A..F]
value <<= 4;
value |= (ch >= 65 ? ch - 65 + 10 : ch - 48); // ch - 'A' + 10 or ch - '0'
value = value >>> 0; // restore unsignedness because silly javascript.
++curIndex;
}
return value;
}
function parseUint64(uint64) {
let lower = 0;
let upper = 0;
// Read up to the first 8 digits.
const startIndex = curIndex;
const first = parseUint32();
const nFirstDigits = curIndex - startIndex;
(0, Assert_1.assert)(nFirstDigits <= 8);
if (8 === nFirstDigits && curIndex + 1 < ids.length && isHexDigit(ids.charCodeAt(curIndex + 1))) {
// We've got up to 8 more digits remaining
const secondIndex = curIndex;
const second = parseUint32();
// Transfer excess digits from upper to lower.
const nSecondDigits = curIndex - secondIndex;
(0, Assert_1.assert)(nSecondDigits > 0 && nSecondDigits <= 8);
const nDigitsToTransfer = 8 - nSecondDigits;
upper = first >>> (4 * nDigitsToTransfer);
const transfer = first - ((upper << (4 * nDigitsToTransfer)) >>> 0);
lower = (second | ((transfer << (4 * nSecondDigits)) >>> 0)) >>> 0;
}
else {
lower = first;
}
uint64.lower = lower;
uint64.upper = upper;
}
const increment = new Uint64();
while (curIndex < ids.length) {
let multiplier = 1;
parseUint64(increment);
if (increment.isZero)
throw new Error("Invalid CompressedId64Set");
if (curIndex < ids.length) {
switch (ids[curIndex++]) {
case "*":
multiplier = parseUint32();
if (0 === multiplier)
throw new Error("Invalid CompressedId64Set");
if (curIndex !== ids.length && ids[curIndex++] !== "+")
return;
break;
case "+":
break;
default:
throw new Error("Invalid CompressedId64Set");
}
}
for (let i = 0; i < multiplier; i++) {
curId.add(increment);
yield curId.toId64String();
}
}
}
CompressedId64Set.iterator = iterator;
/** Supplies an iterable over the [[Id64String]]s in a [[CompressedId64Set]].
* The Ids are iterated in ascending order based on their unsigned 64-bit integer values.
*/
function iterable(ids) {
return {
[Symbol.iterator]: () => iterator(ids),
};
}
CompressedId64Set.iterable = iterable;
/** Decompress the compact string representation of an [[Id64Set]] into an [[Id64Set]].
* @param compressedIds The compact string representation.
* @param out If supplied, the Ids will be inserted into this set rather than allocating and returning a new set.
* @returns The set containing the decompressed Ids.
* @throws Error if `compressedIds` is not a well-formed [[CompressedId64Set]].
* @see [[CompressedId64Set.compressSet]] to perform the inverse operation.
* @see [[CompressedId64Set.decompressArray]] to decompress as an [[Id64Array]] instead.
* @see [[CompressedId64Set.iterable]] to efficiently iterate the Ids.
*/
function decompressSet(compressedIds, out) {
const set = out ?? new Set();
for (const id of iterable(compressedIds))
set.add(id);
return set;
}
CompressedId64Set.decompressSet = decompressSet;
/** Decompress the compact string representation of an [[Id64Set]] into an [[Id64Array]].
* @param compressedIds The compact string representation.
* @param out If supplied, the Ids will be appended to this array rather than allocating and returning a new array.
* @returns The array containing the decompressed Ids.
* @throws Error if `compressedIds` is not a well-formed [[CompressedId64Set]].
* @note The Ids are decompressed and appended to the array in ascending order based on their 64-bit numerical values.
* @see [[CompressedId64Set.compressArray]] to perform the inverse operation.
* @see [[CompressedId64Set.decompressSet]] to decompress as an [[Id64Set]] instead.
* @see [[CompressedId64Set.iterable]] to efficiently iterate the Ids.
*/
function decompressArray(compressedIds, out) {
const arr = out ?? [];
for (const id of iterable(compressedIds))
arr.push(id);
return arr;
}
CompressedId64Set.decompressArray = decompressArray;
})(CompressedId64Set || (exports.CompressedId64Set = CompressedId64Set = {}));
/** A [[SortedArray]] of unique [[Id64String]]s sorted in ascending order by the 64-bit unsigned integer values of the Ids.
* @see [[CompressedId64Set]] for an immutable compact string representation.
* @public
*/
class OrderedId64Array extends SortedArray_1.SortedArray {
/** Construct a new, empty array. */
constructor() {
super((lhs, rhs) => OrderedId64Iterable_1.OrderedId64Iterable.compare(lhs, rhs));
}
/** An iterable that iterates over the Ids in sorted order. */
get ids() { return this._array; }
/** The underlying array of Ids. */
get array() { return this._array; }
}
exports.OrderedId64Array = OrderedId64Array;
/** A mutable set of valid [[Id64String]]s sorted in ascending order by the 64-bit unsigned integer value of the Ids.
* Internally the set of Ids is maintained as a [[CompressedId64Set]] string representation.
* Insertions and removals are buffered until the string representation needs to be recomputed. The string representation is recomputed by every public method except [[add]] and [[delete]] -
* therefore, if multiple removals and/or insertions are required, it is most efficient to perform them all before invoking other methods.
* @public
*/
class MutableCompressedId64Set {
_ids;
_inserted = new OrderedId64Array();
_deleted = new OrderedId64Array();
/** Construct a new set, optionally initialized to contain the Ids represented by `ids`. */
constructor(ids) {
this._ids = ids ?? "";
}
/** Obtain the compact string representation of the contents of this set. If any insertions or removals are pending, they will be applied and the string recomputed. */
get ids() {
this.updateIds();
return this._ids;
}
/** Add the specified Id to the set.
* @throws Error if `id` is not a valid [[Id64String]].
*/
add(id) {
if (!Id_1.Id64.isValidId64(id))
throw new Error("MutableCompressedId64Set.add: invalid Id");
this._deleted.remove(id);
this._inserted.insert(id);
}
/** Remove the specified Id from the set.
* @throws Error if `id` is not a valid [[Id64String]].
*/
delete(id) {
if (!Id_1.Id64.isValidId64(id))
throw new Error("MutableCompressedId64Set.delete: invalid Id");
this._inserted.remove(id);
this._deleted.insert(id);
}
/** Remove all Ids from the set. */
clear() {
this._ids = "";
this._inserted.clear();
this._deleted.clear();
}
/** Remove all Ids from the set, then add the specified Ids. */
reset(ids) {
this.clear();
this._ids = ids ?? "";
}
/** Obtain an iterator over the Ids in this set. The Ids are returned in ascending order based on their unsigned 64-bit integer values. */
[Symbol.iterator]() {
return CompressedId64Set.iterator(this.ids);
}
/** Compute a compact string representation of the union of this and another set of Ids - i.e., those Ids present in either this and/or the other set. */
computeUnion(ids) {
if (this.isEmpty)
return CompressedId64Set.compressIds(ids);
else if (OrderedId64Iterable_1.OrderedId64Iterable.isEmptySet(ids) || this.equals(ids))
return this.ids;
return CompressedId64Set.compressIds(OrderedId64Iterable_1.OrderedId64Iterable.union(this, ids));
}
/** Compute a compact string representation of the intersection of this and another set of Ids - i.e., those Ids present in both this and the other set. */
computeIntersection(ids) {
if (this.equals(ids))
return this.ids;
else if (this.isEmpty || OrderedId64Iterable_1.OrderedId64Iterable.isEmptySet(ids))
return "";
return CompressedId64Set.compressIds(OrderedId64Iterable_1.OrderedId64Iterable.intersection(this, ids));
}
/** Compute a compact string representation of the difference between this and another set - i.e., those Ids present in this but not in the other set. */
computeDifference(ids) {
if (this.isEmpty || this.equals(ids))
return "";
return CompressedId64Set.compressIds(OrderedId64Iterable_1.OrderedId64Iterable.difference(this, ids));
}
/** Return true if this set contains no Ids. */
get isEmpty() {
return OrderedId64Iterable_1.OrderedId64Iterable.isEmptySet(this.ids);
}
/** Return true if the set of Ids represented by `other` is identical to those in this set.
* @note This considers only the **distinct** Ids in `other` - duplicates are ignored.
*/
equals(other) {
if (other instanceof MutableCompressedId64Set) {
if (other === this)
return true;
if (typeof other !== "string")
other = other.ids;
}
if (typeof other === "string")
return other === this.ids;
this.updateIds();
return OrderedId64Iterable_1.OrderedId64Iterable.areEqualSets(this, other);
}
get _isDirty() {
return !this._inserted.isEmpty || !this._deleted.isEmpty;
}
updateIds() {
if (!this._isDirty)
return;
const difference = OrderedId64Iterable_1.OrderedId64Iterable.difference(CompressedId64Set.iterable(this._ids), this._deleted.ids);
const union = { [Symbol.iterator]: () => OrderedId64Iterable_1.OrderedId64Iterable.unionIterator(difference, this._inserted.ids) };
this._ids = CompressedId64Set.compressIds(union);
this._inserted.clear();
this._deleted.clear();
}
}
exports.MutableCompressedId64Set = MutableCompressedId64Set;
//# sourceMappingURL=CompressedId64Set.js.map