packageDocumentation module import { assert } from "./Assert" ; import { Id64 , Id64Array , Id64Set , Id64String } from "./Id" ; import { OrderedId64Iterable } from "./OrderedId64Iterable" ; import { SortedArray } from "./SortedArray" ; public export type CompressedId64Set = string ; see see see see see public export namespace CompressedId64Set { function isHexDigit ( ch ) { return ( ch >= 48 && ch <= 57 ) || ( ch >= 65 && ch <= 70 ) ; } function compactRange ( increment , length ) { assert ( length > 0 ) ; const inc = `+${ increment . toString ( ) }` ; if ( length <= 1 ) return inc ; const len = length . toString ( 16 ) . toUpperCase ( ) ; return `${ inc }*${ len }` ; } note see see see export function compressSet ( ids ) { return sortAndCompress ( ids ) ; } see export function sortAndCompress ( ids ) { const arr = typeof ids === "string" ? [ ids ] : Array . from ( ids ) ; OrderedId64Iterable . sortArray ( arr ) ; return compressArray ( arr ) ; } throws note note see see see export function compressArray ( ids ) { return compressIds ( ids ) ; } throws note note see see see export 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 ( ! Id64 . isValidId64 ( id ) ) continue ; curId . setFromId ( id ) ; curIncrement . setFromDifference ( curId , prevId ) ; const cmp = prevId . compare ( curId ) ; if ( 0 === cmp ) continue ; 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 ; } /** 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 implements Id64 . Uint32Pair { private static readonly _base = 0x100000000 ; private static assertUint32 ( num ) { assert ( num >= 0 ) ; assert ( num < Uint64 . _base ) ; assert ( Math . floor ( num ) === num ) ; } private assertConstraints ( ) { Uint64 . assertUint32 ( this . lower ) ; Uint64 . assertUint32 ( this . upper ) ; } constructor ( public lower = 0 , public upper = 0 ) { this . assertConstraints ( ) ; } public compare ( rhs ) { const diff = this . upper - rhs . upper ; return 0 === diff ? this . lower - rhs . lower : diff ; } public equals ( rhs ) { return 0 === this . compare ( rhs ) ; } public isLessThan ( rhs ) { return this . compare ( rhs ) < 0 ; } public isGreaterThan ( rhs ) { return this . compare ( rhs ) > 0 ; } public get isZero ( ) : boolean { return 0 === this . lower && 0 === this . upper ; } public setFromDifference ( lhs , rhs ) { 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 ; } } public 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 ( ) ; } public setFromId ( id ) { Id64 . getUint32Pair ( id , this ) ; } public copyFrom ( other ) { this . lower = other . lower ; this . upper = other . upper ; } public 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" ) ; assert ( lower . length === 8 ) ; return `${ upper }${ lower }` . toUpperCase ( ) ; } public toId64String ( ) { return 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.
     */ export function * iterator ( ids ) { if ( 0 === ids . length ) return ; if ( "+" !== ids [ 0 ] ) throw new Error ( "Invalid CompressedId64Set" ) ; let curIndex = 1 ; 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 ; value <<= 4 ; value |= ( ch >= 65 ? ch - 65 + 10 : ch - 48 ) ; value = value >>> 0 ; ++ curIndex ; } return value ; } function parseUint64 ( uint64 ) { let lower = 0 ; let upper = 0 ; const startIndex = curIndex ; const first = parseUint32 ( ) ; const nFirstDigits = curIndex - startIndex ; assert ( nFirstDigits <= 8 ) ; if ( 8 === nFirstDigits && curIndex + 1 < ids . length && isHexDigit ( ids . charCodeAt ( curIndex + 1 ) ) ) { const secondIndex = curIndex ; const second = parseUint32 ( ) ; const nSecondDigits = curIndex - secondIndex ; 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 ( ) ; } } } /** 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.
     */ export function iterable ( ids ) { return { [ Symbol . iterator ] : ( ) => iterator ( ids ) } ; } param compressedIds param out returns throws see see see export function decompressSet ( compressedIds , out ? ) { const set = out ?? new Set < string > ( ) ; for ( const id of iterable ( compressedIds ) ) set . add ( id ) ; return set ; } param compressedIds param out returns throws note see see see export function decompressArray ( compressedIds , out ? ) { const arr = out ?? [ ] ; for ( const id of iterable ( compressedIds ) ) arr . push ( id ) ; return arr ; } } see public export class OrderedId64Array extends SortedArray < Id64String > { /** Construct a new, empty array. */ public constructor ( ) { super ( ( lhs , rhs ) => OrderedId64Iterable . compare ( lhs , rhs ) ) ; } /** An iterable that iterates over the Ids in sorted order. */ public get ids ( ) : OrderedId64Iterable { return this . _array ; } /** The underlying array of Ids. */ public get array ( ) : ReadonlyArray < Id64String > { return this . _array ; } } public export class MutableCompressedId64Set implements OrderedId64Iterable { private _ids : CompressedId64Set ; private readonly _inserted = new OrderedId64Array ( ) ; private readonly _deleted = new OrderedId64Array ( ) ; /** Construct a new set, optionally initialized to contain the Ids represented by `ids`. */ public 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. */ public get ids ( ) : CompressedId64Set { this . updateIds ( ) ; return this . _ids ; } throws public add ( id ) { if ( ! Id64 . isValidId64 ( id ) ) throw new Error ( "MutableCompressedId64Set.add: invalid Id" ) ; this . _deleted . remove ( id ) ; this . _inserted . insert ( id ) ; } throws public delete ( id ) { if ( ! Id64 . isValidId64 ( id ) ) throw new Error ( "MutableCompressedId64Set.delete: invalid Id" ) ; this . _inserted . remove ( id ) ; this . _deleted . insert ( id ) ; } /** Remove all Ids from the set. */ public clear ( ) { this . _ids = "" ; this . _inserted . clear ( ) ; this . _deleted . clear ( ) ; } /** Remove all Ids from the set, then add the specified Ids. */ public 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. */ public [ 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. */ public computeUnion ( ids ) { if ( this . isEmpty ) return CompressedId64Set . compressIds ( ids ) ; else if ( OrderedId64Iterable . isEmptySet ( ids ) || this . equals ( ids ) ) return this . ids ; return CompressedId64Set . compressIds ( 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. */ public computeIntersection ( ids ) { if ( this . equals ( ids ) ) return this . ids ; else if ( this . isEmpty || OrderedId64Iterable . isEmptySet ( ids ) ) return "" ; return CompressedId64Set . compressIds ( 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. */ public computeDifference ( ids ) { if ( this . isEmpty || this . equals ( ids ) ) return "" ; return CompressedId64Set . compressIds ( OrderedId64Iterable . difference ( this , ids ) ) ; } /** Return true if this set contains no Ids. */ public get isEmpty ( ) : boolean { return OrderedId64Iterable . isEmptySet ( this . ids ) ; } note public 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 . areEqualSets ( this , other ) ; } private get _isDirty ( ) : boolean { return ! this . _inserted . isEmpty || ! this . _deleted . isEmpty ; } private updateIds ( ) { if ( ! this . _isDirty ) return ; const difference = OrderedId64Iterable . difference ( CompressedId64Set . iterable ( this . _ids ) , this . _deleted . ids ) ; const union = { [ Symbol . iterator ] : ( ) => OrderedId64Iterable . unionIterator ( difference , this . _inserted . ids ) } ; this . _ids = CompressedId64Set . compressIds ( union ) ; this . _inserted . clear ( ) ; this . _deleted . clear ( ) ; } }