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