mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 11:35:42 -06:00
Improve performance of deduplication of sorted arrays
This commit is contained in:
parent
d9775cd822
commit
3cb15378d7
@ -7341,24 +7341,12 @@ namespace ts {
|
||||
unionIndex?: number;
|
||||
}
|
||||
|
||||
function getTypeId(type: Type) {
|
||||
return type.id;
|
||||
}
|
||||
|
||||
function binarySearchTypes(types: Type[], type: Type): number {
|
||||
let low = 0;
|
||||
let high = types.length - 1;
|
||||
const typeId = type.id;
|
||||
while (low <= high) {
|
||||
const middle = low + ((high - low) >> 1);
|
||||
const id = types[middle].id;
|
||||
if (id === typeId) {
|
||||
return middle;
|
||||
}
|
||||
else if (id > typeId) {
|
||||
high = middle - 1;
|
||||
}
|
||||
else {
|
||||
low = middle + 1;
|
||||
}
|
||||
}
|
||||
return ~low;
|
||||
return binarySearch(types, type, getTypeId, compareValues);
|
||||
}
|
||||
|
||||
function containsType(types: Type[], type: Type): boolean {
|
||||
|
||||
@ -660,35 +660,77 @@ namespace ts {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new array with duplicate entries removed.
|
||||
* Deduplicates an array that has already been sorted.
|
||||
*/
|
||||
export function deduplicateSorted<T>(array: SortedReadonlyArray<T>, comparer: EqualityComparer<T> | Comparer<T>) {
|
||||
if (!array) return undefined;
|
||||
if (array.length === 0) return [];
|
||||
|
||||
let last = array[0];
|
||||
const deduplicated: T[] = [last];
|
||||
for (let i = 1; i < array.length; i++) {
|
||||
switch (comparer(last, array[i])) {
|
||||
// equality comparison
|
||||
case true:
|
||||
|
||||
// relational comparison
|
||||
case Comparison.LessThan:
|
||||
case Comparison.EqualTo:
|
||||
continue;
|
||||
}
|
||||
|
||||
deduplicated.push(last = array[i]);
|
||||
}
|
||||
|
||||
return deduplicated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicates an unsorted array.
|
||||
* @param equalityComparer An optional `EqualityComparer` used to determine if two values are duplicates.
|
||||
* @param comparer An optional `Comparer` used to sort entries before comparison. If supplied,
|
||||
* results are returned in the original order found in `array`.
|
||||
*/
|
||||
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer?: EqualityComparer<T>, comparer?: Comparer<T>): T[] {
|
||||
if (!array) return undefined;
|
||||
if (!comparer) return addRangeIfUnique([], array, equalityComparer);
|
||||
return deduplicateWorker(array, equalityComparer, comparer);
|
||||
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T>, comparer?: Comparer<T>): T[] {
|
||||
return !array ? undefined :
|
||||
array.length === 0 ? [] :
|
||||
array.length === 1 ? array.slice() :
|
||||
comparer ? deduplicateRelational(array, equalityComparer, comparer) :
|
||||
deduplicateEquality(array, equalityComparer);
|
||||
}
|
||||
|
||||
function deduplicateWorker<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T> = equateValues, comparer: Comparer<T>) {
|
||||
function deduplicateRelational<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T>, comparer: Comparer<T>) {
|
||||
// Perform a stable sort of the array. This ensures the first entry in a list of
|
||||
// duplicates remains the first entry in the result.
|
||||
const indices = sequence(0, array.length);
|
||||
stableSortIndices(array, indices, comparer);
|
||||
|
||||
const deduplicated: number[] = [];
|
||||
loop: for (const sourceIndex of indices) {
|
||||
for (const targetIndex of deduplicated) {
|
||||
if (equalityComparer(array[sourceIndex], array[targetIndex])) {
|
||||
continue loop;
|
||||
}
|
||||
let last = array[indices[0]];
|
||||
const deduplicated: number[] = [indices[0]];
|
||||
for (let i = 1; i < indices.length; i++) {
|
||||
const index = indices[i];
|
||||
const item = array[index];
|
||||
if (!equalityComparer(last, item)) {
|
||||
deduplicated.push(index);
|
||||
last = item;
|
||||
}
|
||||
deduplicated.push(sourceIndex);
|
||||
}
|
||||
|
||||
// return deduplicated items in original order
|
||||
return deduplicated.sort().map(i => array[i]);
|
||||
// restore original order
|
||||
deduplicated.sort();
|
||||
return deduplicated.map(i => array[i]);
|
||||
}
|
||||
|
||||
function deduplicateEquality<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T>) {
|
||||
const result: T[] = [];
|
||||
for (const item of array) {
|
||||
pushIfUnique(result, item, equalityComparer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function sortAndDeduplicate<T>(array: ReadonlyArray<T>, comparer: Comparer<T>, equalityComparer?: EqualityComparer<T>) {
|
||||
return deduplicateSorted(sort(array, comparer), equalityComparer || comparer);
|
||||
}
|
||||
|
||||
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, equalityComparer: (a: T, b: T) => boolean = equateValues): boolean {
|
||||
@ -827,15 +869,6 @@ namespace ts {
|
||||
return to;
|
||||
}
|
||||
|
||||
function addRangeIfUnique<T>(to: T[], from: ReadonlyArray<T>, equalityComparer?: EqualityComparer<T>): T[] | undefined {
|
||||
for (let i = 0; i < from.length; i++) {
|
||||
if (from[i] !== undefined) {
|
||||
pushIfUnique(to, from[i], equalityComparer);
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the value was added.
|
||||
*/
|
||||
@ -878,13 +911,20 @@ namespace ts {
|
||||
indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new sorted array.
|
||||
*/
|
||||
export function sort<T>(array: ReadonlyArray<T>, comparer: Comparer<T>) {
|
||||
return array.slice().sort(comparer) as ReadonlyArray<T> as SortedReadonlyArray<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
|
||||
*/
|
||||
export function stableSort<T>(array: ReadonlyArray<T>, comparer: Comparer<T>) {
|
||||
const indices = sequence(0, array.length);
|
||||
stableSortIndices(array, indices, comparer);
|
||||
return indices.map(i => array[i]);
|
||||
return indices.map(i => array[i]) as ReadonlyArray<T> as SortedReadonlyArray<T>;
|
||||
}
|
||||
|
||||
export function rangeEquals<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, pos: number, end: number) {
|
||||
@ -969,25 +1009,22 @@ namespace ts {
|
||||
* @param array A sorted array whose first element must be no larger than number
|
||||
* @param number The value to be searched for in the array.
|
||||
*/
|
||||
export function binarySearch<T>(array: ReadonlyArray<T>, value: T, comparer?: Comparer<T>, offset?: number): number {
|
||||
export function binarySearch<T, U>(array: ReadonlyArray<T>, value: T, keySelector: Selector<T, U>, keyComparer: Comparer<U>, offset?: number): number {
|
||||
if (!array || array.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let low = offset || 0;
|
||||
let high = array.length - 1;
|
||||
comparer = comparer !== undefined
|
||||
? comparer
|
||||
: (v1, v2) => (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0));
|
||||
|
||||
const key = keySelector(value);
|
||||
while (low <= high) {
|
||||
const middle = low + ((high - low) >> 1);
|
||||
const midValue = array[middle];
|
||||
const midKey = keySelector(array[middle]);
|
||||
|
||||
if (comparer(midValue, value) === 0) {
|
||||
if (keyComparer(midKey, key) === 0) {
|
||||
return middle;
|
||||
}
|
||||
else if (comparer(midValue, value) > 0) {
|
||||
else if (keyComparer(midKey, key) > 0) {
|
||||
high = middle - 1;
|
||||
}
|
||||
else {
|
||||
@ -2452,8 +2489,8 @@ namespace ts {
|
||||
return flatten<string>(results);
|
||||
|
||||
function visitDirectory(path: string, absolutePath: string, depth: number | undefined) {
|
||||
let { files, directories } = getFileSystemEntries(path);
|
||||
files = files.slice().sort(comparer);
|
||||
const entries = getFileSystemEntries(path);
|
||||
const files = sort(entries.files, comparer);
|
||||
|
||||
for (const current of files) {
|
||||
const name = combinePaths(path, current);
|
||||
@ -2478,7 +2515,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
directories = directories.slice().sort(comparer);
|
||||
const directories = sort(entries.directories, comparer);
|
||||
for (const current of directories) {
|
||||
const name = combinePaths(path, current);
|
||||
const absoluteName = combinePaths(absolutePath, current);
|
||||
|
||||
@ -352,7 +352,7 @@ namespace ts {
|
||||
* We assume the first line starts at position 0 and 'position' is non-negative.
|
||||
*/
|
||||
export function computeLineAndCharacterOfPosition(lineStarts: ReadonlyArray<number>, position: number): LineAndCharacter {
|
||||
let lineNumber = binarySearch(lineStarts, position);
|
||||
let lineNumber = binarySearch(lineStarts, position, identity, compareValues);
|
||||
if (lineNumber < 0) {
|
||||
// If the actual position was not found,
|
||||
// the binary search returns the 2's-complement of the next line start
|
||||
|
||||
@ -306,7 +306,7 @@ namespace ts {
|
||||
|
||||
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
|
||||
const optsList = showAllOptions ?
|
||||
optionDeclarations.slice().sort((a, b) => compareStringsCaseInsensitive(a.name, b.name)) :
|
||||
sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) :
|
||||
filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView);
|
||||
|
||||
// We want our descriptions to align at the same column in our output,
|
||||
|
||||
@ -36,6 +36,11 @@ namespace ts {
|
||||
push(...values: T[]): void;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
|
||||
" __sortedArrayBrand": any;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type EqualityComparer<T> = (a: T, b: T) => boolean;
|
||||
|
||||
|
||||
@ -321,16 +321,16 @@ namespace ts {
|
||||
return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node, includeTrivia);
|
||||
}
|
||||
|
||||
function getPos(range: Node) {
|
||||
return range.pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: it is expected that the `nodeArray` and the `node` are within the same file.
|
||||
* For example, searching for a `SourceFile` in a `SourceFile[]` wouldn't work.
|
||||
*/
|
||||
export function indexOfNode(nodeArray: ReadonlyArray<Node>, node: Node) {
|
||||
return binarySearch(nodeArray, node, compareNodePos);
|
||||
}
|
||||
|
||||
function compareNodePos({ pos: aPos }: Node, { pos: bPos}: Node) {
|
||||
return aPos < bPos ? Comparison.LessThan : bPos < aPos ? Comparison.GreaterThan : Comparison.EqualTo;
|
||||
return binarySearch(nodeArray, node, getPos, compareValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1313,7 +1313,7 @@ namespace Harness {
|
||||
export const diagnosticSummaryMarker = "__diagnosticSummary";
|
||||
export const globalErrorsMarker = "__globalErrors";
|
||||
export function *iterateErrorBaseline(inputFiles: ReadonlyArray<TestFile>, diagnostics: ReadonlyArray<ts.Diagnostic>, pretty?: boolean): IterableIterator<[string, string, number]> {
|
||||
diagnostics = diagnostics.slice().sort(ts.compareDiagnostics);
|
||||
diagnostics = ts.sort(diagnostics, ts.compareDiagnostics);
|
||||
let outputLines = "";
|
||||
// Count up all errors that were found in files other than lib.d.ts so we don't miss any
|
||||
let totalErrorsReportedInNonLibraryFiles = 0;
|
||||
|
||||
@ -203,8 +203,10 @@ namespace ts.server {
|
||||
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
|
||||
*/
|
||||
export function combineProjectOutput<T>(projects: ReadonlyArray<Project>, action: (project: Project) => ReadonlyArray<T>, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) {
|
||||
const result = flatMap(projects, action).sort(comparer);
|
||||
return projects.length > 1 ? deduplicate(result, areEqual) : result;
|
||||
const outputs = flatMap(projects, action);
|
||||
return comparer
|
||||
? sortAndDeduplicate(outputs, comparer, areEqual)
|
||||
: deduplicate(outputs, areEqual);
|
||||
}
|
||||
|
||||
export interface HostConfiguration {
|
||||
|
||||
@ -250,7 +250,7 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const insertIndex = binarySearch(array, insert, compare);
|
||||
const insertIndex = binarySearch(array, insert, identity, compare);
|
||||
if (insertIndex < 0) {
|
||||
array.splice(~insertIndex, 0, insert);
|
||||
}
|
||||
@ -266,7 +266,7 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const removeIndex = binarySearch(array, remove, compare);
|
||||
const removeIndex = binarySearch(array, remove, identity, compare);
|
||||
if (removeIndex >= 0) {
|
||||
array.splice(removeIndex, 1);
|
||||
}
|
||||
|
||||
@ -581,7 +581,7 @@ namespace ts.textChanges {
|
||||
return applyFormatting(nonformattedText, sourceFile, initialIndentation, delta, this.rulesProvider);
|
||||
}
|
||||
|
||||
private static normalize(changes: Change[]): Change[] {
|
||||
private static normalize(changes: Change[]) {
|
||||
// order changes by start position
|
||||
const normalized = stableSort(changes, (a, b) => a.range.pos - b.range.pos);
|
||||
// verify that change intervals do not overlap, except possibly at end points.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user