Path: blob/main/src/vs/editor/common/model/prefixSumComputer.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { arrayInsert } from '../../../base/common/arrays.js';6import { toUint32 } from '../../../base/common/uint.js';78export class PrefixSumComputer {910/**11* values[i] is the value at index i12*/13private values: Uint32Array;1415/**16* prefixSum[i] = SUM(heights[j]), 0 <= j <= i17*/18private prefixSum: Uint32Array;1920/**21* prefixSum[i], 0 <= i <= prefixSumValidIndex can be trusted22*/23private readonly prefixSumValidIndex: Int32Array;2425constructor(values: Uint32Array) {26this.values = values;27this.prefixSum = new Uint32Array(values.length);28this.prefixSumValidIndex = new Int32Array(1);29this.prefixSumValidIndex[0] = -1;30}3132public getCount(): number {33return this.values.length;34}3536public insertValues(insertIndex: number, insertValues: Uint32Array): boolean {37insertIndex = toUint32(insertIndex);38const oldValues = this.values;39const oldPrefixSum = this.prefixSum;40const insertValuesLen = insertValues.length;4142if (insertValuesLen === 0) {43return false;44}4546this.values = new Uint32Array(oldValues.length + insertValuesLen);47this.values.set(oldValues.subarray(0, insertIndex), 0);48this.values.set(oldValues.subarray(insertIndex), insertIndex + insertValuesLen);49this.values.set(insertValues, insertIndex);5051if (insertIndex - 1 < this.prefixSumValidIndex[0]) {52this.prefixSumValidIndex[0] = insertIndex - 1;53}5455this.prefixSum = new Uint32Array(this.values.length);56if (this.prefixSumValidIndex[0] >= 0) {57this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex[0] + 1));58}59return true;60}6162public setValue(index: number, value: number): boolean {63index = toUint32(index);64value = toUint32(value);6566if (this.values[index] === value) {67return false;68}69this.values[index] = value;70if (index - 1 < this.prefixSumValidIndex[0]) {71this.prefixSumValidIndex[0] = index - 1;72}73return true;74}7576public removeValues(startIndex: number, count: number): boolean {77startIndex = toUint32(startIndex);78count = toUint32(count);7980const oldValues = this.values;81const oldPrefixSum = this.prefixSum;8283if (startIndex >= oldValues.length) {84return false;85}8687const maxCount = oldValues.length - startIndex;88if (count >= maxCount) {89count = maxCount;90}9192if (count === 0) {93return false;94}9596this.values = new Uint32Array(oldValues.length - count);97this.values.set(oldValues.subarray(0, startIndex), 0);98this.values.set(oldValues.subarray(startIndex + count), startIndex);99100this.prefixSum = new Uint32Array(this.values.length);101if (startIndex - 1 < this.prefixSumValidIndex[0]) {102this.prefixSumValidIndex[0] = startIndex - 1;103}104if (this.prefixSumValidIndex[0] >= 0) {105this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex[0] + 1));106}107return true;108}109110public getTotalSum(): number {111if (this.values.length === 0) {112return 0;113}114return this._getPrefixSum(this.values.length - 1);115}116117/**118* Returns the sum of the first `index + 1` many items.119* @returns `SUM(0 <= j <= index, values[j])`.120*/121public getPrefixSum(index: number): number {122if (index < 0) {123return 0;124}125126index = toUint32(index);127return this._getPrefixSum(index);128}129130private _getPrefixSum(index: number): number {131if (index <= this.prefixSumValidIndex[0]) {132return this.prefixSum[index];133}134135let startIndex = this.prefixSumValidIndex[0] + 1;136if (startIndex === 0) {137this.prefixSum[0] = this.values[0];138startIndex++;139}140141if (index >= this.values.length) {142index = this.values.length - 1;143}144145for (let i = startIndex; i <= index; i++) {146this.prefixSum[i] = this.prefixSum[i - 1] + this.values[i];147}148this.prefixSumValidIndex[0] = Math.max(this.prefixSumValidIndex[0], index);149return this.prefixSum[index];150}151152public getIndexOf(sum: number): PrefixSumIndexOfResult {153sum = Math.floor(sum);154155// Compute all sums (to get a fully valid prefixSum)156this.getTotalSum();157158let low = 0;159let high = this.values.length - 1;160let mid = 0;161let midStop = 0;162let midStart = 0;163164while (low <= high) {165mid = low + ((high - low) / 2) | 0;166167midStop = this.prefixSum[mid];168midStart = midStop - this.values[mid];169170if (sum < midStart) {171high = mid - 1;172} else if (sum >= midStop) {173low = mid + 1;174} else {175break;176}177}178179return new PrefixSumIndexOfResult(mid, sum - midStart);180}181}182183/**184* {@link getIndexOf} has an amortized runtime complexity of O(1).185*186* ({@link PrefixSumComputer.getIndexOf} is just O(log n))187*/188export class ConstantTimePrefixSumComputer {189private _values: number[];190private _isValid: boolean;191private _validEndIndex: number;192193/**194* _prefixSum[i] = SUM(values[j]), 0 <= j <= i195*/196private _prefixSum: number[];197198/**199* _indexBySum[sum] = idx => _prefixSum[idx - 1] <= sum < _prefixSum[idx]200*/201private _indexBySum: number[];202203constructor(values: number[]) {204this._values = values;205this._isValid = false;206this._validEndIndex = -1;207this._prefixSum = [];208this._indexBySum = [];209}210211/**212* @returns SUM(0 <= j < values.length, values[j])213*/214public getTotalSum(): number {215this._ensureValid();216return this._indexBySum.length;217}218219/**220* Returns the sum of the first `count` many items.221* @returns `SUM(0 <= j < count, values[j])`.222*/223public getPrefixSum(count: number): number {224this._ensureValid();225if (count === 0) {226return 0;227}228return this._prefixSum[count - 1];229}230231/**232* @returns `result`, such that `getPrefixSum(result.index) + result.remainder = sum`233*/234public getIndexOf(sum: number): PrefixSumIndexOfResult {235this._ensureValid();236const idx = this._indexBySum[sum];237const viewLinesAbove = idx > 0 ? this._prefixSum[idx - 1] : 0;238return new PrefixSumIndexOfResult(idx, sum - viewLinesAbove);239}240241public removeValues(start: number, deleteCount: number): void {242this._values.splice(start, deleteCount);243this._invalidate(start);244}245246public insertValues(insertIndex: number, insertArr: number[]): void {247this._values = arrayInsert(this._values, insertIndex, insertArr);248this._invalidate(insertIndex);249}250251private _invalidate(index: number): void {252this._isValid = false;253this._validEndIndex = Math.min(this._validEndIndex, index - 1);254}255256private _ensureValid(): void {257if (this._isValid) {258return;259}260261for (let i = this._validEndIndex + 1, len = this._values.length; i < len; i++) {262const value = this._values[i];263const sumAbove = i > 0 ? this._prefixSum[i - 1] : 0;264265this._prefixSum[i] = sumAbove + value;266for (let j = 0; j < value; j++) {267this._indexBySum[sumAbove + j] = i;268}269}270271// trim things272this._prefixSum.length = this._values.length;273this._indexBySum.length = this._prefixSum[this._prefixSum.length - 1];274275// mark as valid276this._isValid = true;277this._validEndIndex = this._values.length - 1;278}279280public setValue(index: number, value: number): void {281if (this._values[index] === value) {282// no change283return;284}285this._values[index] = value;286this._invalidate(index);287}288}289290291export class PrefixSumIndexOfResult {292_prefixSumIndexOfResultBrand: void = undefined;293294constructor(295public readonly index: number,296public readonly remainder: number297) {298this.index = index;299this.remainder = remainder;300}301}302303304