Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/common/arrays.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { findFirstIdxMonotonousOrArrLen } from './arraysFind.js';
7
import { CancellationToken } from './cancellation.js';
8
import { CancellationError } from './errors.js';
9
import { ISplice } from './sequence.js';
10
11
/**
12
* Returns the last entry and the initial N-1 entries of the array, as a tuple of [rest, last].
13
*
14
* The array must have at least one element.
15
*
16
* @param arr The input array
17
* @returns A tuple of [rest, last] where rest is all but the last element and last is the last element
18
* @throws Error if the array is empty
19
*/
20
export function tail<T>(arr: T[]): [T[], T] {
21
if (arr.length === 0) {
22
throw new Error('Invalid tail call');
23
}
24
25
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]];
26
}
27
28
export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArray<T> | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
29
if (one === other) {
30
return true;
31
}
32
33
if (!one || !other) {
34
return false;
35
}
36
37
if (one.length !== other.length) {
38
return false;
39
}
40
41
for (let i = 0, len = one.length; i < len; i++) {
42
if (!itemEquals(one[i], other[i])) {
43
return false;
44
}
45
}
46
47
return true;
48
}
49
50
/**
51
* Remove the element at `index` by replacing it with the last element. This is faster than `splice`
52
* but changes the order of the array
53
*/
54
export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {
55
const last = array.length - 1;
56
if (index < last) {
57
array[index] = array[last];
58
}
59
array.pop();
60
}
61
62
/**
63
* Performs a binary search algorithm over a sorted array.
64
*
65
* @param array The array being searched.
66
* @param key The value we search for.
67
* @param comparator A function that takes two array elements and returns zero
68
* if they are equal, a negative number if the first element precedes the
69
* second one in the sorting order, or a positive number if the second element
70
* precedes the first one.
71
* @return See {@link binarySearch2}
72
*/
73
export function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {
74
return binarySearch2(array.length, i => comparator(array[i], key));
75
}
76
77
/**
78
* Performs a binary search algorithm over a sorted collection. Useful for cases
79
* when we need to perform a binary search over something that isn't actually an
80
* array, and converting data to an array would defeat the use of binary search
81
* in the first place.
82
*
83
* @param length The collection length.
84
* @param compareToKey A function that takes an index of an element in the
85
* collection and returns zero if the value at this index is equal to the
86
* search key, a negative number if the value precedes the search key in the
87
* sorting order, or a positive number if the search key precedes the value.
88
* @return A non-negative index of an element, if found. If not found, the
89
* result is -(n+1) (or ~n, using bitwise notation), where n is the index
90
* where the key should be inserted to maintain the sorting order.
91
*/
92
export function binarySearch2(length: number, compareToKey: (index: number) => number): number {
93
let low = 0,
94
high = length - 1;
95
96
while (low <= high) {
97
const mid = ((low + high) / 2) | 0;
98
const comp = compareToKey(mid);
99
if (comp < 0) {
100
low = mid + 1;
101
} else if (comp > 0) {
102
high = mid - 1;
103
} else {
104
return mid;
105
}
106
}
107
return -(low + 1);
108
}
109
110
type Compare<T> = (a: T, b: T) => number;
111
112
113
export function quickSelect<T>(nth: number, data: T[], compare: Compare<T>): T {
114
115
nth = nth | 0;
116
117
if (nth >= data.length) {
118
throw new TypeError('invalid index');
119
}
120
121
const pivotValue = data[Math.floor(data.length * Math.random())];
122
const lower: T[] = [];
123
const higher: T[] = [];
124
const pivots: T[] = [];
125
126
for (const value of data) {
127
const val = compare(value, pivotValue);
128
if (val < 0) {
129
lower.push(value);
130
} else if (val > 0) {
131
higher.push(value);
132
} else {
133
pivots.push(value);
134
}
135
}
136
137
if (nth < lower.length) {
138
return quickSelect(nth, lower, compare);
139
} else if (nth < lower.length + pivots.length) {
140
return pivots[0];
141
} else {
142
return quickSelect(nth - (lower.length + pivots.length), higher, compare);
143
}
144
}
145
146
export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => number): T[][] {
147
const result: T[][] = [];
148
let currentGroup: T[] | undefined = undefined;
149
for (const element of data.slice(0).sort(compare)) {
150
if (!currentGroup || compare(currentGroup[0], element) !== 0) {
151
currentGroup = [element];
152
result.push(currentGroup);
153
} else {
154
currentGroup.push(element);
155
}
156
}
157
return result;
158
}
159
160
/**
161
* Splits the given items into a list of (non-empty) groups.
162
* `shouldBeGrouped` is used to decide if two consecutive items should be in the same group.
163
* The order of the items is preserved.
164
*/
165
export function* groupAdjacentBy<T>(items: Iterable<T>, shouldBeGrouped: (item1: T, item2: T) => boolean): Iterable<T[]> {
166
let currentGroup: T[] | undefined;
167
let last: T | undefined;
168
for (const item of items) {
169
if (last !== undefined && shouldBeGrouped(last, item)) {
170
currentGroup!.push(item);
171
} else {
172
if (currentGroup) {
173
yield currentGroup;
174
}
175
currentGroup = [item];
176
}
177
last = item;
178
}
179
if (currentGroup) {
180
yield currentGroup;
181
}
182
}
183
184
export function forEachAdjacent<T>(arr: T[], f: (item1: T | undefined, item2: T | undefined) => void): void {
185
for (let i = 0; i <= arr.length; i++) {
186
f(i === 0 ? undefined : arr[i - 1], i === arr.length ? undefined : arr[i]);
187
}
188
}
189
190
export function forEachWithNeighbors<T>(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void {
191
for (let i = 0; i < arr.length; i++) {
192
f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]);
193
}
194
}
195
196
export function concatArrays<TArr extends any[]>(...arrays: TArr): TArr[number][number][] {
197
return ([] as any[]).concat(...arrays);
198
}
199
200
interface IMutableSplice<T> extends ISplice<T> {
201
readonly toInsert: T[];
202
deleteCount: number;
203
}
204
205
/**
206
* Diffs two *sorted* arrays and computes the splices which apply the diff.
207
*/
208
export function sortedDiff<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): ISplice<T>[] {
209
const result: IMutableSplice<T>[] = [];
210
211
function pushSplice(start: number, deleteCount: number, toInsert: T[]): void {
212
if (deleteCount === 0 && toInsert.length === 0) {
213
return;
214
}
215
216
const latest = result[result.length - 1];
217
218
if (latest && latest.start + latest.deleteCount === start) {
219
latest.deleteCount += deleteCount;
220
latest.toInsert.push(...toInsert);
221
} else {
222
result.push({ start, deleteCount, toInsert });
223
}
224
}
225
226
let beforeIdx = 0;
227
let afterIdx = 0;
228
229
while (true) {
230
if (beforeIdx === before.length) {
231
pushSplice(beforeIdx, 0, after.slice(afterIdx));
232
break;
233
}
234
if (afterIdx === after.length) {
235
pushSplice(beforeIdx, before.length - beforeIdx, []);
236
break;
237
}
238
239
const beforeElement = before[beforeIdx];
240
const afterElement = after[afterIdx];
241
const n = compare(beforeElement, afterElement);
242
if (n === 0) {
243
// equal
244
beforeIdx += 1;
245
afterIdx += 1;
246
} else if (n < 0) {
247
// beforeElement is smaller -> before element removed
248
pushSplice(beforeIdx, 1, []);
249
beforeIdx += 1;
250
} else if (n > 0) {
251
// beforeElement is greater -> after element added
252
pushSplice(beforeIdx, 0, [afterElement]);
253
afterIdx += 1;
254
}
255
}
256
257
return result;
258
}
259
260
/**
261
* Takes two *sorted* arrays and computes their delta (removed, added elements).
262
* Finishes in `Math.min(before.length, after.length)` steps.
263
*/
264
export function delta<T>(before: ReadonlyArray<T>, after: ReadonlyArray<T>, compare: (a: T, b: T) => number): { removed: T[]; added: T[] } {
265
const splices = sortedDiff(before, after, compare);
266
const removed: T[] = [];
267
const added: T[] = [];
268
269
for (const splice of splices) {
270
removed.push(...before.slice(splice.start, splice.start + splice.deleteCount));
271
added.push(...splice.toInsert);
272
}
273
274
return { removed, added };
275
}
276
277
/**
278
* Returns the top N elements from the array.
279
*
280
* Faster than sorting the entire array when the array is a lot larger than N.
281
*
282
* @param array The unsorted array.
283
* @param compare A sort function for the elements.
284
* @param n The number of elements to return.
285
* @return The first n elements from array when sorted with compare.
286
*/
287
export function top<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, n: number): T[] {
288
if (n === 0) {
289
return [];
290
}
291
const result = array.slice(0, n).sort(compare);
292
topStep(array, compare, result, n, array.length);
293
return result;
294
}
295
296
/**
297
* Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run.
298
*
299
* Returns the top N elements from the array.
300
*
301
* Faster than sorting the entire array when the array is a lot larger than N.
302
*
303
* @param array The unsorted array.
304
* @param compare A sort function for the elements.
305
* @param n The number of elements to return.
306
* @param batch The number of elements to examine before yielding to the event loop.
307
* @return The first n elements from array when sorted with compare.
308
*/
309
export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: number, batch: number, token?: CancellationToken): Promise<T[]> {
310
if (n === 0) {
311
return Promise.resolve([]);
312
}
313
314
return new Promise((resolve, reject) => {
315
(async () => {
316
const o = array.length;
317
const result = array.slice(0, n).sort(compare);
318
for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {
319
if (i > n) {
320
await new Promise(resolve => setTimeout(resolve)); // any other delay function would starve I/O
321
}
322
if (token && token.isCancellationRequested) {
323
throw new CancellationError();
324
}
325
topStep(array, compare, result, i, m);
326
}
327
return result;
328
})()
329
.then(resolve, reject);
330
});
331
}
332
333
function topStep<T>(array: ReadonlyArray<T>, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void {
334
for (const n = result.length; i < m; i++) {
335
const element = array[i];
336
if (compare(element, result[n - 1]) < 0) {
337
result.pop();
338
const j = findFirstIdxMonotonousOrArrLen(result, e => compare(element, e) < 0);
339
result.splice(j, 0, element);
340
}
341
}
342
}
343
344
/**
345
* @returns New array with all falsy values removed. The original array IS NOT modified.
346
*/
347
export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
348
return array.filter((e): e is T => !!e);
349
}
350
351
/**
352
* Remove all falsy values from `array`. The original array IS modified.
353
*/
354
export function coalesceInPlace<T>(array: Array<T | undefined | null>): asserts array is Array<T> {
355
let to = 0;
356
for (let i = 0; i < array.length; i++) {
357
if (!!array[i]) {
358
array[to] = array[i];
359
to += 1;
360
}
361
}
362
array.length = to;
363
}
364
365
/**
366
* @deprecated Use `Array.copyWithin` instead
367
*/
368
export function move(array: unknown[], from: number, to: number): void {
369
array.splice(to, 0, array.splice(from, 1)[0]);
370
}
371
372
/**
373
* @returns false if the provided object is an array and not empty.
374
*/
375
export function isFalsyOrEmpty(obj: unknown): boolean {
376
return !Array.isArray(obj) || obj.length === 0;
377
}
378
379
/**
380
* @returns True if the provided object is an array and has at least one element.
381
*/
382
export function isNonEmptyArray<T>(obj: T[] | undefined | null): obj is T[];
383
export function isNonEmptyArray<T>(obj: readonly T[] | undefined | null): obj is readonly T[];
384
export function isNonEmptyArray<T>(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] {
385
return Array.isArray(obj) && obj.length > 0;
386
}
387
388
/**
389
* Removes duplicates from the given array. The optional keyFn allows to specify
390
* how elements are checked for equality by returning an alternate value for each.
391
*/
392
export function distinct<T>(array: ReadonlyArray<T>, keyFn: (value: T) => unknown = value => value): T[] {
393
const seen = new Set<any>();
394
395
return array.filter(element => {
396
const key = keyFn!(element);
397
if (seen.has(key)) {
398
return false;
399
}
400
seen.add(key);
401
return true;
402
});
403
}
404
405
export function uniqueFilter<T, R>(keyFn: (t: T) => R): (t: T) => boolean {
406
const seen = new Set<R>();
407
408
return element => {
409
const key = keyFn(element);
410
411
if (seen.has(key)) {
412
return false;
413
}
414
415
seen.add(key);
416
return true;
417
};
418
}
419
420
export function commonPrefixLength<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b): number {
421
let result = 0;
422
423
for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) {
424
result++;
425
}
426
427
return result;
428
}
429
430
export function range(to: number): number[];
431
export function range(from: number, to: number): number[];
432
export function range(arg: number, to?: number): number[] {
433
let from = typeof to === 'number' ? arg : 0;
434
435
if (typeof to === 'number') {
436
from = arg;
437
} else {
438
from = 0;
439
to = arg;
440
}
441
442
const result: number[] = [];
443
444
if (from <= to) {
445
for (let i = from; i < to; i++) {
446
result.push(i);
447
}
448
} else {
449
for (let i = from; i > to; i--) {
450
result.push(i);
451
}
452
}
453
454
return result;
455
}
456
457
export function index<T>(array: ReadonlyArray<T>, indexer: (t: T) => string): { [key: string]: T };
458
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R };
459
export function index<T, R>(array: ReadonlyArray<T>, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R } {
460
return array.reduce((r, t) => {
461
r[indexer(t)] = mapper ? mapper(t) : t;
462
return r;
463
}, Object.create(null));
464
}
465
466
/**
467
* Inserts an element into an array. Returns a function which, when
468
* called, will remove that element from the array.
469
*
470
* @deprecated In almost all cases, use a `Set<T>` instead.
471
*/
472
export function insert<T>(array: T[], element: T): () => void {
473
array.push(element);
474
475
return () => remove(array, element);
476
}
477
478
/**
479
* Removes an element from an array if it can be found.
480
*
481
* @deprecated In almost all cases, use a `Set<T>` instead.
482
*/
483
export function remove<T>(array: T[], element: T): T | undefined {
484
const index = array.indexOf(element);
485
if (index > -1) {
486
array.splice(index, 1);
487
488
return element;
489
}
490
491
return undefined;
492
}
493
494
/**
495
* Insert `insertArr` inside `target` at `insertIndex`.
496
* Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array
497
*/
498
export function arrayInsert<T>(target: T[], insertIndex: number, insertArr: T[]): T[] {
499
const before = target.slice(0, insertIndex);
500
const after = target.slice(insertIndex);
501
return before.concat(insertArr, after);
502
}
503
504
/**
505
* Uses Fisher-Yates shuffle to shuffle the given array
506
*/
507
export function shuffle<T>(array: T[], _seed?: number): void {
508
let rand: () => number;
509
510
if (typeof _seed === 'number') {
511
let seed = _seed;
512
// Seeded random number generator in JS. Modified from:
513
// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript
514
rand = () => {
515
const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias
516
return x - Math.floor(x);
517
};
518
} else {
519
rand = Math.random;
520
}
521
522
for (let i = array.length - 1; i > 0; i -= 1) {
523
const j = Math.floor(rand() * (i + 1));
524
const temp = array[i];
525
array[i] = array[j];
526
array[j] = temp;
527
}
528
}
529
530
/**
531
* Pushes an element to the start of the array, if found.
532
*/
533
export function pushToStart<T>(arr: T[], value: T): void {
534
const index = arr.indexOf(value);
535
536
if (index > -1) {
537
arr.splice(index, 1);
538
arr.unshift(value);
539
}
540
}
541
542
/**
543
* Pushes an element to the end of the array, if found.
544
*/
545
export function pushToEnd<T>(arr: T[], value: T): void {
546
const index = arr.indexOf(value);
547
548
if (index > -1) {
549
arr.splice(index, 1);
550
arr.push(value);
551
}
552
}
553
554
export function pushMany<T>(arr: T[], items: ReadonlyArray<T>): void {
555
for (const item of items) {
556
arr.push(item);
557
}
558
}
559
560
export function mapArrayOrNot<T, U>(items: T | T[], fn: (_: T) => U): U | U[] {
561
return Array.isArray(items) ?
562
items.map(fn) :
563
fn(items);
564
}
565
566
export function asArray<T>(x: T | T[]): T[];
567
export function asArray<T>(x: T | readonly T[]): readonly T[];
568
export function asArray<T>(x: T | T[]): T[] {
569
return Array.isArray(x) ? x : [x];
570
}
571
572
export function getRandomElement<T>(arr: T[]): T | undefined {
573
return arr[Math.floor(Math.random() * arr.length)];
574
}
575
576
/**
577
* Insert the new items in the array.
578
* @param array The original array.
579
* @param start The zero-based location in the array from which to start inserting elements.
580
* @param newItems The items to be inserted
581
*/
582
export function insertInto<T>(array: T[], start: number, newItems: T[]): void {
583
const startIdx = getActualStartIndex(array, start);
584
const originalLength = array.length;
585
const newItemsLength = newItems.length;
586
array.length = originalLength + newItemsLength;
587
// Move the items after the start index, start from the end so that we don't overwrite any value.
588
for (let i = originalLength - 1; i >= startIdx; i--) {
589
array[i + newItemsLength] = array[i];
590
}
591
592
for (let i = 0; i < newItemsLength; i++) {
593
array[i + startIdx] = newItems[i];
594
}
595
}
596
597
/**
598
* Removes elements from an array and inserts new elements in their place, returning the deleted elements. Alternative to the native Array.splice method, it
599
* can only support limited number of items due to the maximum call stack size limit.
600
* @param array The original array.
601
* @param start The zero-based location in the array from which to start removing elements.
602
* @param deleteCount The number of elements to remove.
603
* @returns An array containing the elements that were deleted.
604
*/
605
export function splice<T>(array: T[], start: number, deleteCount: number, newItems: T[]): T[] {
606
const index = getActualStartIndex(array, start);
607
let result = array.splice(index, deleteCount);
608
if (result === undefined) {
609
// see https://bugs.webkit.org/show_bug.cgi?id=261140
610
result = [];
611
}
612
insertInto(array, index, newItems);
613
return result;
614
}
615
616
/**
617
* Determine the actual start index (same logic as the native splice() or slice())
618
* 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.
619
* 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.
620
* @param array The target array.
621
* @param start The operation index.
622
*/
623
function getActualStartIndex<T>(array: T[], start: number): number {
624
return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length);
625
}
626
627
628
629
/**
630
* When comparing two values,
631
* a negative number indicates that the first value is less than the second,
632
* a positive number indicates that the first value is greater than the second,
633
* and zero indicates that neither is the case.
634
*/
635
export type CompareResult = number;
636
637
export namespace CompareResult {
638
export function isLessThan(result: CompareResult): boolean {
639
return result < 0;
640
}
641
642
export function isLessThanOrEqual(result: CompareResult): boolean {
643
return result <= 0;
644
}
645
646
export function isGreaterThan(result: CompareResult): boolean {
647
return result > 0;
648
}
649
650
export function isNeitherLessOrGreaterThan(result: CompareResult): boolean {
651
return result === 0;
652
}
653
654
export const greaterThan = 1;
655
export const lessThan = -1;
656
export const neitherLessOrGreaterThan = 0;
657
}
658
659
/**
660
* A comparator `c` defines a total order `<=` on `T` as following:
661
* `c(a, b) <= 0` iff `a` <= `b`.
662
* We also have `c(a, b) == 0` iff `c(b, a) == 0`.
663
*/
664
export type Comparator<T> = (a: T, b: T) => CompareResult;
665
666
export function compareBy<TItem, TCompareBy>(selector: (item: TItem) => TCompareBy, comparator: Comparator<TCompareBy>): Comparator<TItem> {
667
return (a, b) => comparator(selector(a), selector(b));
668
}
669
670
export function tieBreakComparators<TItem>(...comparators: Comparator<TItem>[]): Comparator<TItem> {
671
return (item1, item2) => {
672
for (const comparator of comparators) {
673
const result = comparator(item1, item2);
674
if (!CompareResult.isNeitherLessOrGreaterThan(result)) {
675
return result;
676
}
677
}
678
return CompareResult.neitherLessOrGreaterThan;
679
};
680
}
681
682
/**
683
* The natural order on numbers.
684
*/
685
export const numberComparator: Comparator<number> = (a, b) => a - b;
686
687
export const booleanComparator: Comparator<boolean> = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0);
688
689
export function reverseOrder<TItem>(comparator: Comparator<TItem>): Comparator<TItem> {
690
return (a, b) => -comparator(a, b);
691
}
692
693
/**
694
* Returns a new comparator that treats `undefined` as the smallest value.
695
* All other values are compared using the given comparator.
696
*/
697
export function compareUndefinedSmallest<T>(comparator: Comparator<T>): Comparator<T | undefined> {
698
return (a, b) => {
699
if (a === undefined) {
700
return b === undefined ? CompareResult.neitherLessOrGreaterThan : CompareResult.lessThan;
701
} else if (b === undefined) {
702
return CompareResult.greaterThan;
703
}
704
705
return comparator(a, b);
706
};
707
}
708
709
export class ArrayQueue<T> {
710
private readonly items: readonly T[];
711
private firstIdx = 0;
712
private lastIdx: number;
713
714
/**
715
* Constructs a queue that is backed by the given array. Runtime is O(1).
716
*/
717
constructor(items: readonly T[]) {
718
this.items = items;
719
this.lastIdx = this.items.length - 1;
720
}
721
722
get length(): number {
723
return this.lastIdx - this.firstIdx + 1;
724
}
725
726
/**
727
* Consumes elements from the beginning of the queue as long as the predicate returns true.
728
* If no elements were consumed, `null` is returned. Has a runtime of O(result.length).
729
*/
730
takeWhile(predicate: (value: T) => boolean): T[] | null {
731
// P(k) := k <= this.lastIdx && predicate(this.items[k])
732
// Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s)
733
734
let startIdx = this.firstIdx;
735
while (startIdx < this.items.length && predicate(this.items[startIdx])) {
736
startIdx++;
737
}
738
const result = startIdx === this.firstIdx ? null : this.items.slice(this.firstIdx, startIdx);
739
this.firstIdx = startIdx;
740
return result;
741
}
742
743
/**
744
* Consumes elements from the end of the queue as long as the predicate returns true.
745
* If no elements were consumed, `null` is returned.
746
* The result has the same order as the underlying array!
747
*/
748
takeFromEndWhile(predicate: (value: T) => boolean): T[] | null {
749
// P(k) := this.firstIdx >= k && predicate(this.items[k])
750
// Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx]
751
752
let endIdx = this.lastIdx;
753
while (endIdx >= 0 && predicate(this.items[endIdx])) {
754
endIdx--;
755
}
756
const result = endIdx === this.lastIdx ? null : this.items.slice(endIdx + 1, this.lastIdx + 1);
757
this.lastIdx = endIdx;
758
return result;
759
}
760
761
peek(): T | undefined {
762
if (this.length === 0) {
763
return undefined;
764
}
765
return this.items[this.firstIdx];
766
}
767
768
peekLast(): T | undefined {
769
if (this.length === 0) {
770
return undefined;
771
}
772
return this.items[this.lastIdx];
773
}
774
775
dequeue(): T | undefined {
776
const result = this.items[this.firstIdx];
777
this.firstIdx++;
778
return result;
779
}
780
781
removeLast(): T | undefined {
782
const result = this.items[this.lastIdx];
783
this.lastIdx--;
784
return result;
785
}
786
787
takeCount(count: number): T[] {
788
const result = this.items.slice(this.firstIdx, this.firstIdx + count);
789
this.firstIdx += count;
790
return result;
791
}
792
}
793
794
/**
795
* This class is faster than an iterator and array for lazy computed data.
796
*/
797
export class CallbackIterable<T> {
798
public static readonly empty = new CallbackIterable<never>(_callback => { });
799
800
constructor(
801
/**
802
* Calls the callback for every item.
803
* Stops when the callback returns false.
804
*/
805
public readonly iterate: (callback: (item: T) => boolean) => void
806
) {
807
}
808
809
forEach(handler: (item: T) => void) {
810
this.iterate(item => { handler(item); return true; });
811
}
812
813
toArray(): T[] {
814
const result: T[] = [];
815
this.iterate(item => { result.push(item); return true; });
816
return result;
817
}
818
819
filter(predicate: (item: T) => boolean): CallbackIterable<T> {
820
return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true));
821
}
822
823
map<TResult>(mapFn: (item: T) => TResult): CallbackIterable<TResult> {
824
return new CallbackIterable<TResult>(cb => this.iterate(item => cb(mapFn(item))));
825
}
826
827
some(predicate: (item: T) => boolean): boolean {
828
let result = false;
829
this.iterate(item => { result = predicate(item); return !result; });
830
return result;
831
}
832
833
findFirst(predicate: (item: T) => boolean): T | undefined {
834
let result: T | undefined;
835
this.iterate(item => {
836
if (predicate(item)) {
837
result = item;
838
return false;
839
}
840
return true;
841
});
842
return result;
843
}
844
845
findLast(predicate: (item: T) => boolean): T | undefined {
846
let result: T | undefined;
847
this.iterate(item => {
848
if (predicate(item)) {
849
result = item;
850
}
851
return true;
852
});
853
return result;
854
}
855
856
findLastMaxBy(comparator: Comparator<T>): T | undefined {
857
let result: T | undefined;
858
let first = true;
859
this.iterate(item => {
860
if (first || CompareResult.isGreaterThan(comparator(item, result!))) {
861
first = false;
862
result = item;
863
}
864
return true;
865
});
866
return result;
867
}
868
}
869
870
/**
871
* Represents a re-arrangement of items in an array.
872
*/
873
export class Permutation {
874
constructor(private readonly _indexMap: readonly number[]) { }
875
876
/**
877
* Returns a permutation that sorts the given array according to the given compare function.
878
*/
879
public static createSortPermutation<T>(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation {
880
const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2]));
881
return new Permutation(sortIndices);
882
}
883
884
/**
885
* Returns a new array with the elements of the given array re-arranged according to this permutation.
886
*/
887
apply<T>(arr: readonly T[]): T[] {
888
return arr.map((_, index) => arr[this._indexMap[index]]);
889
}
890
891
/**
892
* Returns a new permutation that undoes the re-arrangement of this permutation.
893
*/
894
inverse(): Permutation {
895
const inverseIndexMap = this._indexMap.slice();
896
for (let i = 0; i < this._indexMap.length; i++) {
897
inverseIndexMap[this._indexMap[i]] = i;
898
}
899
return new Permutation(inverseIndexMap);
900
}
901
}
902
903
/**
904
* Asynchronous variant of `Array.find()`, returning the first element in
905
* the array for which the predicate returns true.
906
*
907
* This implementation does not bail early and waits for all promises to
908
* resolve before returning.
909
*/
910
export async function findAsync<T>(array: readonly T[], predicate: (element: T, index: number) => Promise<boolean>): Promise<T | undefined> {
911
const results = await Promise.all(array.map(
912
async (element, index) => ({ element, ok: await predicate(element, index) })
913
));
914
915
return results.find(r => r.ok)?.element;
916
}
917
918
export function sum(array: readonly number[]): number {
919
return array.reduce((acc, value) => acc + value, 0);
920
}
921
922
export function sumBy<T>(array: readonly T[], selector: (value: T) => number): number {
923
return array.reduce((acc, value) => acc + selector(value), 0);
924
}
925
926