Merge pull request #127406 from microsoft/hediet/ArrayQueue
Replaces filterSortedByKey with faster ArrayQueue.
This commit is contained in:
commit
e92da93d8a
3 changed files with 115 additions and 19 deletions
|
@ -589,15 +589,51 @@ export function maxIndex<T>(array: readonly T[], fn: (value: T) => number): numb
|
|||
return maxIdx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all elements `e` with `startKey <= keySelector(e) <= endKey`.
|
||||
* `data` must be sorted in ascending order.
|
||||
*/
|
||||
export function filterSortedByKey<T>(data: readonly T[], keySelector: (value: T) => number, startKey: number, endKey = startKey): T[] | null {
|
||||
const startIdx = findFirstInSorted(data, x => startKey <= keySelector(x));
|
||||
const endIdx = findFirstInSorted(data, x => endKey < keySelector(x));
|
||||
if (startIdx === endIdx) {
|
||||
return null;
|
||||
export class ArrayQueue<T> {
|
||||
private firstIdx = 0;
|
||||
private lastIdx = this.items.length - 1;
|
||||
|
||||
/**
|
||||
* Constructs a queue that is backed by the given array. Runtime is O(1).
|
||||
*/
|
||||
constructor(private readonly items: T[]) { }
|
||||
|
||||
get length(): number {
|
||||
return this.lastIdx - this.firstIdx + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes elements from the beginning of the queue as long as the predicate returns true.
|
||||
* If no elements were consumed, `null` is returned. Has a runtime of O(result.length).
|
||||
*/
|
||||
takeWhile(predicate: (value: T) => boolean): T[] | null {
|
||||
// P(k) := k <= this.lastIdx && predicate(this.items[k])
|
||||
// Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s)
|
||||
|
||||
let startIdx = this.firstIdx;
|
||||
while (startIdx < this.items.length && predicate(this.items[startIdx])) {
|
||||
startIdx++;
|
||||
}
|
||||
const result = startIdx === this.firstIdx ? null : this.items.slice(this.firstIdx, startIdx);
|
||||
this.firstIdx = startIdx;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes elements from the end of the queue as long as the predicate returns true.
|
||||
* If no elements were consumed, `null` is returned.
|
||||
* The result has the same order as the underlying array!
|
||||
*/
|
||||
takeFromEndWhile(predicate: (value: T) => boolean): T[] | null {
|
||||
// P(k) := this.firstIdx >= k && predicate(this.items[k])
|
||||
// Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx]
|
||||
|
||||
let endIdx = this.lastIdx;
|
||||
while (endIdx >= 0 && predicate(this.items[endIdx])) {
|
||||
endIdx--;
|
||||
}
|
||||
const result = endIdx === this.lastIdx ? null : this.items.slice(endIdx + 1, this.lastIdx + 1);
|
||||
this.lastIdx = endIdx;
|
||||
return result;
|
||||
}
|
||||
return data.slice(startIdx, endIdx);
|
||||
}
|
||||
|
|
|
@ -312,12 +312,63 @@ suite('Arrays', () => {
|
|||
assert.strictEqual(arrays.maxIndex(array, value => value === 'b' ? 5 : 0), 1);
|
||||
});
|
||||
|
||||
test('filterSortedByKey', () => {
|
||||
const array = [1, 2, 5, 5, 7, 8];
|
||||
suite('ArrayQueue', () => {
|
||||
suite('takeWhile/takeFromEndWhile', () => {
|
||||
test('TakeWhile 1', () => {
|
||||
const queue1 = new arrays.ArrayQueue([9, 8, 1, 7, 6]);
|
||||
assert.deepStrictEqual(queue1.takeWhile(x => x > 5), [9, 8]);
|
||||
assert.deepStrictEqual(queue1.takeWhile(x => x < 7), [1]);
|
||||
assert.deepStrictEqual(queue1.takeWhile(x => true), [7, 6]);
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(arrays.filterSortedByKey(array, i => i, 3, 5), [5, 5]);
|
||||
assert.deepStrictEqual(arrays.filterSortedByKey(array, i => i, 5, 5), [5, 5]);
|
||||
assert.deepStrictEqual(arrays.filterSortedByKey(array, i => i, 6, 6), null);
|
||||
assert.deepStrictEqual(arrays.filterSortedByKey(array, i => i, 8, 8), [8]);
|
||||
test('TakeWhile 1', () => {
|
||||
const queue1 = new arrays.ArrayQueue([9, 8, 1, 7, 6]);
|
||||
assert.deepStrictEqual(queue1.takeFromEndWhile(x => x > 5), [7, 6]);
|
||||
assert.deepStrictEqual(queue1.takeFromEndWhile(x => x < 2), [1]);
|
||||
assert.deepStrictEqual(queue1.takeFromEndWhile(x => true), [9, 8]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('takeWhile/takeFromEndWhile monotonous', () => {
|
||||
function testMonotonous(array: number[], predicate: (a: number) => boolean) {
|
||||
function normalize(arr: number[]): number[] | null {
|
||||
if (arr.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
const negatedPredicate = (a: number) => !predicate(a);
|
||||
|
||||
{
|
||||
const queue1 = new arrays.ArrayQueue(array);
|
||||
assert.deepStrictEqual(queue1.takeWhile(predicate), normalize(array.filter(predicate)));
|
||||
assert.deepStrictEqual(queue1.length, array.length - array.filter(predicate).length);
|
||||
assert.deepStrictEqual(queue1.takeWhile(() => true), normalize(array.filter(negatedPredicate)));
|
||||
}
|
||||
{
|
||||
const queue3 = new arrays.ArrayQueue(array);
|
||||
assert.deepStrictEqual(queue3.takeFromEndWhile(negatedPredicate), normalize(array.filter(negatedPredicate)));
|
||||
assert.deepStrictEqual(queue3.length, array.length - array.filter(negatedPredicate).length);
|
||||
assert.deepStrictEqual(queue3.takeFromEndWhile(() => true), normalize(array.filter(predicate)));
|
||||
}
|
||||
}
|
||||
|
||||
const array = [1, 1, 1, 2, 5, 5, 7, 8, 8];
|
||||
|
||||
test('TakeWhile 1', () => testMonotonous(array, value => value <= 1));
|
||||
test('TakeWhile 2', () => testMonotonous(array, value => value < 5));
|
||||
test('TakeWhile 3', () => testMonotonous(array, value => value <= 5));
|
||||
test('TakeWhile 4', () => testMonotonous(array, value => true));
|
||||
test('TakeWhile 5', () => testMonotonous(array, value => false));
|
||||
|
||||
const array2 = [1, 1, 1, 2, 5, 5, 7, 8, 8, 9, 9, 9, 9, 10, 10];
|
||||
|
||||
test('TakeWhile 6', () => testMonotonous(array2, value => value < 10));
|
||||
test('TakeWhile 7', () => testMonotonous(array2, value => value < 7));
|
||||
test('TakeWhile 8', () => testMonotonous(array2, value => value < 5));
|
||||
|
||||
test('TakeWhile Empty', () => testMonotonous([], value => value <= 5));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ import { TextChange } from 'vs/editor/common/model/textChange';
|
|||
import { Constants } from 'vs/base/common/uint';
|
||||
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
|
||||
import { listenStream } from 'vs/base/common/stream';
|
||||
import { filterSortedByKey } from 'vs/base/common/arrays';
|
||||
import { ArrayQueue } from 'vs/base/common/arrays';
|
||||
|
||||
function createTextBufferBuilder() {
|
||||
return new PieceTreeTextBufferBuilder();
|
||||
|
@ -1470,17 +1470,23 @@ export class TextModel extends Disposable implements model.ITextModel {
|
|||
0,
|
||||
this._versionId
|
||||
));
|
||||
|
||||
|
||||
const injectedTextInEditedRange = LineInjectedText.fromDecorations(decorationsWithInjectedTextInEditedRange);
|
||||
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);
|
||||
|
||||
for (let j = editingLinesCnt; j >= 0; j--) {
|
||||
const editLineNumber = startLineNumber + j;
|
||||
const currentEditLineNumber = currentEditStartLineNumber + j;
|
||||
|
||||
injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber > currentEditLineNumber);
|
||||
const decorationsInCurrentLine = injectedTextInEditedRangeQueue.takeFromEndWhile(r => r.lineNumber === currentEditLineNumber);
|
||||
|
||||
rawContentChanges.push(
|
||||
new ModelRawLineChanged(
|
||||
editLineNumber,
|
||||
this.getLineContent(currentEditLineNumber),
|
||||
filterSortedByKey(injectedTextInEditedRange, i => i.lineNumber, currentEditLineNumber)
|
||||
decorationsInCurrentLine
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1491,6 +1497,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
|||
}
|
||||
|
||||
if (editingLinesCnt < insertingLinesCnt) {
|
||||
const injectedTextInEditedRangeQueue = new ArrayQueue(injectedTextInEditedRange);
|
||||
// Must insert some lines
|
||||
const spliceLineNumber = startLineNumber + editingLinesCnt;
|
||||
const cnt = insertingLinesCnt - editingLinesCnt;
|
||||
|
@ -1500,7 +1507,9 @@ export class TextModel extends Disposable implements model.ITextModel {
|
|||
for (let i = 0; i < cnt; i++) {
|
||||
let lineNumber = fromLineNumber + i;
|
||||
newLines[i] = this.getLineContent(lineNumber);
|
||||
injectedTexts[i] = filterSortedByKey(injectedTextInEditedRange, i => i.lineNumber, lineNumber);
|
||||
|
||||
injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber < lineNumber);
|
||||
injectedTexts[i] = injectedTextInEditedRangeQueue.takeWhile(r => r.lineNumber === lineNumber);
|
||||
}
|
||||
|
||||
rawContentChanges.push(
|
||||
|
|
Loading…
Reference in a new issue