Path: blob/main/extensions/copilot/src/util/vs/base/common/arrays.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { findFirstIdxMonotonousOrArrLen } from './arraysFind';8import { CancellationToken } from './cancellation';9import { CancellationError } from './errors';10import { ISplice } from './sequence';1112/**13* Returns the last entry and the initial N-1 entries of the array, as a tuple of [rest, last].14*15* The array must have at least one element.16*17* @param arr The input array18* @returns A tuple of [rest, last] where rest is all but the last element and last is the last element19* @throws Error if the array is empty20*/21export function tail<T>(arr: T[]): [T[], T] {22if (arr.length === 0) {23throw new Error('Invalid tail call');24}2526return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];27}2829export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArray<T> | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {30if (one === other) {31return true;32}3334if (!one || !other) {35return false;36}3738if (one.length !== other.length) {39return false;40}4142for (let i = 0, len = one.length; i < len; i++) {43if (!itemEquals(one[i], other[i])) {44return false;45}46}4748return true;49}5051/**52* Remove the element at `index` by replacing it with the last element. This is faster than `splice`53* but changes the order of the array54*/55export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {56const last = array.length - 1;57if (index < last) {58array[index] = array[last];59}60array.pop();61}6263/**64* Performs a binary search algorithm over a sorted array.65*66* @param array The array being searched.67* @param key The value we search for.68* @param comparator A function that takes two array elements and returns zero69* if they are equal, a negative number if the first element precedes the70* second one in the sorting order, or a positive number if the second element71* precedes the first one.72* @return See {@link binarySearch2}73*/74export function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {75return binarySearch2(array.length, i => comparator(array[i], key));76}7778/**79* Performs a binary search algorithm over a sorted collection. Useful for cases80* when we need to perform a binary search over something that isn't actually an81* array, and converting data to an array would defeat the use of binary search82* in the first place.83*84* @param length The collection length.85* @param compareToKey A function that takes an index of an element in the86* collection and returns zero if the value at this index is equal to the87* search key, a negative number if the value precedes the search key in the88* sorting order, or a positive number if the search key precedes the value.89* @return A non-negative index of an element, if found. If not found, the90* result is -(n+1) (or ~n, using bitwise notation), where n is the index91* where the key should be inserted to maintain the sorting order.92*/93export function binarySearch2(length: number, compareToKey: (index: number) => number): number {94let low = 0,95high = length - 1;9697while (low <= high) {98const mid = ((low + high) / 2) | 0;99const comp = compareToKey(mid);100if (comp < 0) {101low = mid + 1;102} else if (comp > 0) {103high = mid - 1;104} else {105return mid;106}107}108return -(low + 1);109}110111type Compare<T> = (a: T, b: T) => number;112113/**114* Finds the nth smallest element in the array using quickselect algorithm.115* The data does not need to be sorted.116*117* @param nth The zero-based index of the element to find (0 = smallest, 1 = second smallest, etc.)118* @param data The unsorted array119* @param compare A comparator function that defines the sort order120* @returns The nth smallest element121* @throws TypeError if nth is >= data.length122*/123export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {124125nth = nth | 0;126127if (nth >= data.length) {128throw new TypeError('invalid index');129}130131const pivotValue = data[Math.floor(data.length * Math.random())];132const lower: T[] = [];133const higher: T[] = [];134const pivots: T[] = [];135136for (const value of data) {137const val = compare(value, pivotValue);138if (val < 0) {139lower.push(value);140} else if (val > 0) {141higher.push(value);142} else {143pivots.push(value);144}145}146147if (nth < lower.length) {148return quickSelect(nth, lower, compare);149} else if (nth < lower.length + pivots.length) {150return pivots[0];151} else {152return quickSelect(nth - (lower.length + pivots.length), higher, compare);153}154}155156export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {157const result: T[][] = [];158let currentGroup: T[] | undefined = undefined;159for (const element of data.slice(0).sort(compare)) {160if (!currentGroup || compare(currentGroup[0], element) !== 0) {161currentGroup = [element];162result.push(currentGroup);163} else {164currentGroup.push(element);165}166}167return result;168}169170/**171* Splits the given items into a list of (non-empty) groups.172* `shouldBeGrouped` is used to decide if two consecutive items should be in the same group.173* The order of the items is preserved.174*/175export function* groupAdjacentBy<T>(items: Iterable<T>, shouldBeGrouped: (item1: T, item2: T) => boolean): Iterable<T[]> {176let currentGroup: T[] | undefined;177let last: T | undefined;178for (const item of items) {179if (last !== undefined && shouldBeGrouped(last, item)) {180currentGroup!.push(item);181} else {182if (currentGroup) {183yield currentGroup;184}185currentGroup = [item];186}187last = item;188}189if (currentGroup) {190yield currentGroup;191}192}193194export function forEachAdjacent<T>(arr: T[], f: (item1: T | undefined, item2: T | undefined) => void): void {195for (let i = 0; i <= arr.length; i++) {196f(i === 0 ? undefined : arr[i - 1], i === arr.length ? undefined : arr[i]);197}198}199200export function forEachWithNeighbors<T>(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void {201for (let i = 0; i < arr.length; i++) {202f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]);203}204}205206export function concatArrays<T extends any[]>(...arrays: T): T[number][number][] {207return [].concat(...arrays);208}209210interface IMutableSplice<T> extends ISplice<T> {211readonly toInsert: T[];212deleteCount: number;213}214215/**216* Diffs two *sorted* arrays and computes the splices which apply the diff.217*/218export function sortedDiff<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): ISplice<T>[] {219const result: IMutableSplice<T>[] = [];220221function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {222if (deleteCount === 0 && toInsert.length === 0) {223return;224}225226const latest = result[result.length - 1];227228if (latest && latest.start + latest.deleteCount === start) {229latest.deleteCount += deleteCount;230latest.toInsert.push(...toInsert);231} else {232result.push({ start, deleteCount, toInsert });233}234}235236let beforeIdx = 0;237let afterIdx = 0;238239while (true) {240if (beforeIdx === before.length) {241pushSplice(beforeIdx, 0, after.slice(afterIdx));242break;243}244if (afterIdx === after.length) {245pushSplice(beforeIdx, before.length - beforeIdx, []);246break;247}248249const beforeElement = before[beforeIdx];250const afterElement = after[afterIdx];251const n = compare(beforeElement, afterElement);252if (n === 0) {253// equal254beforeIdx += 1;255afterIdx += 1;256} else if (n < 0) {257// beforeElement is smaller -> before element removed258pushSplice(beforeIdx, 1, []);259beforeIdx += 1;260} else if (n > 0) {261// beforeElement is greater -> after element added262pushSplice(beforeIdx, 0, [afterElement]);263afterIdx += 1;264}265}266267return result;268}269270/**271* Takes two *sorted* arrays and computes their delta (removed, added elements).272* Finishes in `Math.min(before.length, after.length)` steps.273*/274export function delta<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): { removed: T[]; added: T[] } {275const splices = sortedDiff(before, after, compare);276const removed: T[] = [];277const added: T[] = [];278279for (const splice of splices) {280removed.push(...before.slice(splice.start, splice.start + splice.deleteCount));281added.push(...splice.toInsert);282}283284return { removed, added };285}286287/**288* Returns the top N elements from the array.289*290* Faster than sorting the entire array when the array is a lot larger than N.291*292* @param array The unsorted array.293* @param compare A sort function for the elements.294* @param n The number of elements to return.295* @return The first n elements from array when sorted with compare.296*/297export function top<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, n: number): T[] {298if (n === 0) {299return [];300}301const result = array.slice(0, n).sort(compare);302topStep(array, compare, result, n, array.length);303return result;304}305306/**307* Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run.308*309* Returns the top N elements from the array.310*311* Faster than sorting the entire array when the array is a lot larger than N.312*313* @param array The unsorted array.314* @param compare A sort function for the elements.315* @param n The number of elements to return.316* @param batch The number of elements to examine before yielding to the event loop.317* @return The first n elements from array when sorted with compare.318*/319export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: number, batch: number, token?: CancellationToken): Promise<T[]> {320if (n === 0) {321return Promise.resolve([]);322}323324return new Promise((resolve, reject) => {325(async () => {326const o = array.length;327const result = array.slice(0, n).sort(compare);328for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {329if (i > n) {330await new Promise(resolve => setTimeout(resolve)); // any other delay function would starve I/O331}332if (token && token.isCancellationRequested) {333throw new CancellationError();334}335topStep(array, compare, result, i, m);336}337return result;338})()339.then(resolve, reject);340});341}342343function topStep<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {344for (const n = result.length; i < m; i++) {345const element = array[i];346if (compare(element, result[n - 1]) < 0) {347result.pop();348const j = findFirstIdxMonotonousOrArrLen(result, e => compare(element, e) < 0);349result.splice(j, 0, element);350}351}352}353354/**355* @returns New array with all falsy values removed. The original array IS NOT modified.356*/357export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {358return array.filter((e): e is T => !!e);359}360361/**362* Remove all falsy values from `array`. The original array IS modified.363*/364export function coalesceInPlace<T>(array: Array<T | undefined | null>): asserts array is Array<T> {365let to = 0;366for (let i = 0; i < array.length; i++) {367if (!!array[i]) {368array[to] = array[i];369to += 1;370}371}372array.length = to;373}374375/**376* @deprecated Use `Array.copyWithin` instead377*/378export function move(array: unknown[], from: number, to: number): void {379array.splice(to, 0, array.splice(from, 1)[0]);380}381382/**383* @returns false if the provided object is an array and not empty.384*/385export function isFalsyOrEmpty(obj: unknown): boolean {386return !Array.isArray(obj) || obj.length === 0;387}388389/**390* @returns True if the provided object is an array and has at least one element.391*/392export function isNonEmptyArray<T>(obj: T[] | undefined | null): obj is T[];393export function isNonEmptyArray<T>(obj: readonly T[] | undefined | null): obj is readonly T[];394export function isNonEmptyArray<T>(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] {395return Array.isArray(obj) && obj.length > 0;396}397398/**399* Removes duplicates from the given array. The optional keyFn allows to specify400* how elements are checked for equality by returning an alternate value for each.401*/402export function distinct<T>(array: ReadonlyArray<T>, keyFn: (value: T) => unknown = value => value): T[] {403const seen = new Set<any>();404405return array.filter(element => {406const key = keyFn(element);407if (seen.has(key)) {408return false;409}410seen.add(key);411return true;412});413}414415export function uniqueFilter<T, R>(keyFn: (t: T) => R): (t: T) => boolean {416const seen = new Set<R>();417418return element => {419const key = keyFn(element);420421if (seen.has(key)) {422return false;423}424425seen.add(key);426return true;427};428}429430export function commonPrefixLength<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b): number {431let result = 0;432433for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {434result++;435}436437return result;438}439440export function range(to: number): number[];441export function range(from: number, to: number): number[];442export function range(arg: number, to?: number): number[] {443let from = typeof to === 'number' ? arg : 0;444445if (typeof to === 'number') {446from = arg;447} else {448from = 0;449to = arg;450}451452const result: number[] = [];453454if (from <= to) {455for (let i = from; i < to; i++) {456result.push(i);457}458} else {459for (let i = from; i > to; i--) {460result.push(i);461}462}463464return result;465}466467export function index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T };468export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R };469export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R } {470return array.reduce((r, t) => {471r[indexer(t)] = mapper ? mapper(t) : t;472return r;473}, Object.create(null));474}475476/**477* Inserts an element into an array. Returns a function which, when478* called, will remove that element from the array.479*480* @deprecated In almost all cases, use a `Set<T>` instead.481*/482export function insert<T>(array: T[], element: T): () => void {483array.push(element);484485return () => remove(array, element);486}487488/**489* Removes an element from an array if it can be found.490*491* @deprecated In almost all cases, use a `Set<T>` instead.492*/493export function remove<T>(array: T[], element: T): T | undefined {494const index = array.indexOf(element);495if (index > -1) {496array.splice(index, 1);497498return element;499}500501return undefined;502}503504/**505* Insert `insertArr` inside `target` at `insertIndex`.506* Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array507*/508export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[]): T[] {509const before = target.slice(0, insertIndex);510const after = target.slice(insertIndex);511return before.concat(insertArr, after);512}513514/**515* Uses Fisher-Yates shuffle to shuffle the given array516*/517export function shuffle<T>(array: T[], _seed?: number): void {518let rand: () => number;519520if (typeof _seed === 'number') {521let seed = _seed;522// Seeded random number generator in JS. Modified from:523// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript524rand = () => {525const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias526return x - Math.floor(x);527};528} else {529rand = Math.random;530}531532for (let i = array.length - 1; i > 0; i -= 1) {533const j = Math.floor(rand() * (i + 1));534const temp = array[i];535array[i] = array[j];536array[j] = temp;537}538}539540/**541* Pushes an element to the start of the array, if found.542*/543export function pushToStart<T>(arr: T[], value: T): void {544const index = arr.indexOf(value);545546if (index > -1) {547arr.splice(index, 1);548arr.unshift(value);549}550}551552/**553* Pushes an element to the end of the array, if found.554*/555export function pushToEnd<T>(arr: T[], value: T): void {556const index = arr.indexOf(value);557558if (index > -1) {559arr.splice(index, 1);560arr.push(value);561}562}563564export function pushMany<T>(arr: T[], items: ReadonlyArray<T>): void {565for (const item of items) {566arr.push(item);567}568}569570export function mapArrayOrNot<T, U>(items: T | T[], fn: (_: T) => U): U | U[] {571return Array.isArray(items) ?572items.map(fn) :573fn(items);574}575576export function mapFilter<T, U>(array: ReadonlyArray<T>, fn: (t: T) => U | undefined): U[] {577const result: U[] = [];578for (const item of array) {579const mapped = fn(item);580if (mapped !== undefined) {581result.push(mapped);582}583}584return result;585}586587export function withoutDuplicates<T>(array: ReadonlyArray<T>): T[] {588const s = new Set(array);589return Array.from(s);590}591592export function asArray<T>(x: T | T[]): T[];593export function asArray<T>(x: T | readonly T[]): readonly T[];594export function asArray<T>(x: T | T[]): T[] {595return Array.isArray(x) ? x : [x];596}597598export function getRandomElement<T>(arr: T[]): T | undefined {599return arr[Math.floor(Math.random() * arr.length)];600}601602/**603* Insert the new items in the array.604* @param array The original array.605* @param start The zero-based location in the array from which to start inserting elements.606* @param newItems The items to be inserted607*/608export function insertInto<T>(array: T[], start: number, newItems: T[]): void {609const startIdx = getActualStartIndex(array, start);610const originalLength = array.length;611const newItemsLength = newItems.length;612array.length = originalLength + newItemsLength;613// Move the items after the start index, start from the end so that we don't overwrite any value.614for (let i = originalLength - 1; i >= startIdx; i--) {615array[i + newItemsLength] = array[i];616}617618for (let i = 0; i < newItemsLength; i++) {619array[i + startIdx] = newItems[i];620}621}622623/**624* Removes elements from an array and inserts new elements in their place, returning the deleted elements. Alternative to the native Array.splice method, it625* can only support limited number of items due to the maximum call stack size limit.626* @param array The original array.627* @param start The zero-based location in the array from which to start removing elements.628* @param deleteCount The number of elements to remove.629* @returns An array containing the elements that were deleted.630*/631export function splice<T>(array: T[], start: number, deleteCount: number, newItems: T[]): T[] {632const index = getActualStartIndex(array, start);633let result = array.splice(index, deleteCount);634if (result === undefined) {635// see https://bugs.webkit.org/show_bug.cgi?id=261140636result = [];637}638insertInto(array, index, newItems);639return result;640}641642/**643* Determine the actual start index (same logic as the native splice() or slice())644* If greater than the length of the array, start will be set to the length of the array. In this case, no element will be deleted but the method will behave as an adding function, adding as many element as item[n*] provided.645* If negative, it will begin that many elements from the end of the array. (In this case, the origin -1, meaning -n is the index of the nth last element, and is therefore equivalent to the index of array.length - n.) If array.length + start is less than 0, it will begin from index 0.646* @param array The target array.647* @param start The operation index.648*/649function getActualStartIndex<T>(array: T[], start: number): number {650return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length);651}652653654655/**656* When comparing two values,657* a negative number indicates that the first value is less than the second,658* a positive number indicates that the first value is greater than the second,659* and zero indicates that neither is the case.660*/661export type CompareResult = number;662663export namespace CompareResult {664export function isLessThan(result: CompareResult): boolean {665return result < 0;666}667668export function isLessThanOrEqual(result: CompareResult): boolean {669return result <= 0;670}671672export function isGreaterThan(result: CompareResult): boolean {673return result > 0;674}675676export function isNeitherLessOrGreaterThan(result: CompareResult): boolean {677return result === 0;678}679680export const greaterThan = 1;681export const lessThan = -1;682export const neitherLessOrGreaterThan = 0;683}684685/**686* A comparator `c` defines a total order `<=` on `T` as following:687* `c(a, b) <= 0` iff `a` <= `b`.688* We also have `c(a, b) == 0` iff `c(b, a) == 0`.689*/690export type Comparator<T> = (a: T, b: T) => CompareResult;691692export function compareBy<TItem, TCompareBy>(selector: (item: TItem) => TCompareBy, comparator: Comparator<TCompareBy>): Comparator<TItem> {693return (a, b) => comparator(selector(a), selector(b));694}695696export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]): Comparator<TItem> {697return (item1, item2) => {698for (const comparator of comparators) {699const result = comparator(item1, item2);700if (!CompareResult.isNeitherLessOrGreaterThan(result)) {701return result;702}703}704return CompareResult.neitherLessOrGreaterThan;705};706}707708/**709* The natural order on numbers.710*/711export const numberComparator: Comparator<number> = (a, b) => a - b;712713export const booleanComparator: Comparator<boolean> = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);714715export function reverseOrder<TItem>(comparator: Comparator<TItem>): Comparator<TItem> {716return (a, b) => -comparator(a, b);717}718719/**720* Returns a new comparator that treats `undefined` as the smallest value.721* All other values are compared using the given comparator.722*/723export function compareUndefinedSmallest<T>(comparator: Comparator<T>): Comparator<T | undefined> {724return (a, b) => {725if (a === undefined) {726return b === undefined ? CompareResult.neitherLessOrGreaterThan : CompareResult.lessThan;727} else if (b === undefined) {728return CompareResult.greaterThan;729}730731return comparator(a, b);732};733}734735export class ArrayQueue<T> {736private readonly items: readonly T[];737private firstIdx = 0;738private lastIdx: number;739740/**741* Constructs a queue that is backed by the given array. Runtime is O(1).742*/743constructor(items: readonly T[]) {744this.items = items;745this.lastIdx = this.items.length - 1;746}747748get length(): number {749return this.lastIdx - this.firstIdx + 1;750}751752/**753* Consumes elements from the beginning of the queue as long as the predicate returns true.754* If no elements were consumed, `null` is returned. Has a runtime of O(result.length).755*/756takeWhile(predicate: (value: T) => boolean): T[] | null {757// P(k) := k <= this.lastIdx && predicate(this.items[k])758// Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s)759760let startIdx = this.firstIdx;761while (startIdx < this.items.length && predicate(this.items[startIdx])) {762startIdx++;763}764const result = startIdx === this.firstIdx ? null : this.items.slice(this.firstIdx, startIdx);765this.firstIdx = startIdx;766return result;767}768769/**770* Consumes elements from the end of the queue as long as the predicate returns true.771* If no elements were consumed, `null` is returned.772* The result has the same order as the underlying array!773*/774takeFromEndWhile(predicate: (value: T) => boolean): T[] | null {775// P(k) := this.firstIdx >= k && predicate(this.items[k])776// Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx]777778let endIdx = this.lastIdx;779while (endIdx >= 0 && predicate(this.items[endIdx])) {780endIdx--;781}782const result = endIdx === this.lastIdx ? null : this.items.slice(endIdx + 1, this.lastIdx + 1);783this.lastIdx = endIdx;784return result;785}786787peek(): T | undefined {788if (this.length === 0) {789return undefined;790}791return this.items[this.firstIdx];792}793794peekLast(): T | undefined {795if (this.length === 0) {796return undefined;797}798return this.items[this.lastIdx];799}800801dequeue(): T | undefined {802const result = this.items[this.firstIdx];803this.firstIdx++;804return result;805}806807removeLast(): T | undefined {808const result = this.items[this.lastIdx];809this.lastIdx--;810return result;811}812813takeCount(count: number): T[] {814const result = this.items.slice(this.firstIdx, this.firstIdx + count);815this.firstIdx += count;816return result;817}818}819820/**821* This class is faster than an iterator and array for lazy computed data.822*/823export class CallbackIterable<T> {824public static readonly empty = new CallbackIterable<never>(_callback => { });825826constructor(827/**828* Calls the callback for every item.829* Stops when the callback returns false.830*/831public readonly iterate: (callback: (item: T) => boolean) => void832) {833}834835forEach(handler: (item: T) => void) {836this.iterate(item => { handler(item); return true; });837}838839toArray(): T[] {840const result: T[] = [];841this.iterate(item => { result.push(item); return true; });842return result;843}844845filter(predicate: (item: T) => boolean): CallbackIterable<T> {846return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true));847}848849map<TResult>(mapFn: (item: T) => TResult): CallbackIterable<TResult> {850return new CallbackIterable<TResult>(cb => this.iterate(item => cb(mapFn(item))));851}852853some(predicate: (item: T) => boolean): boolean {854let result = false;855this.iterate(item => { result = predicate(item); return !result; });856return result;857}858859findFirst(predicate: (item: T) => boolean): T | undefined {860let result: T | undefined;861this.iterate(item => {862if (predicate(item)) {863result = item;864return false;865}866return true;867});868return result;869}870871findLast(predicate: (item: T) => boolean): T | undefined {872let result: T | undefined;873this.iterate(item => {874if (predicate(item)) {875result = item;876}877return true;878});879return result;880}881882findLastMaxBy(comparator: Comparator<T>): T | undefined {883let result: T | undefined;884let first = true;885this.iterate(item => {886if (first || CompareResult.isGreaterThan(comparator(item, result!))) {887first = false;888result = item;889}890return true;891});892return result;893}894}895896/**897* Represents a re-arrangement of items in an array.898*/899export class Permutation {900constructor(private readonly _indexMap: readonly number[]) { }901902/**903* Returns a permutation that sorts the given array according to the given compare function.904*/905public static createSortPermutation<T>(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation {906const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2]));907return new Permutation(sortIndices);908}909910/**911* Returns a new array with the elements of the given array re-arranged according to this permutation.912*/913apply<T>(arr: readonly T[]): T[] {914return arr.map((_, index) => arr[this._indexMap[index]]);915}916917/**918* Returns a new permutation that undoes the re-arrangement of this permutation.919*/920inverse(): Permutation {921const inverseIndexMap = this._indexMap.slice();922for (let i = 0; i < this._indexMap.length; i++) {923inverseIndexMap[this._indexMap[i]] = i;924}925return new Permutation(inverseIndexMap);926}927}928929/**930* Asynchronous variant of `Array.find()`, returning the first element in931* the array for which the predicate returns true.932*933* This implementation does not bail early and waits for all promises to934* resolve before returning.935*/936export async function findAsync<T>(array: readonly T[], predicate: (element: T, index: number) => Promise<boolean>): Promise<T | undefined> {937const results = await Promise.all(array.map(938async (element, index) => ({ element, ok: await predicate(element, index) })939));940941return results.find(r => r.ok)?.element;942}943944export function sum(array: readonly number[]): number {945return array.reduce((acc, value) => acc + value, 0);946}947948export function sumBy<T>(array: readonly T[], selector: (value: T) => number): number {949return array.reduce((acc, value) => acc + selector(value), 0);950}951952953