UNPKG

42.5 kBPlain TextView Raw
1// Copyright (c) Jupyter Development Team.
2// Distributed under the terms of the Modified BSD License.
3/*-----------------------------------------------------------------------------
4| Copyright (c) 2014-2017, PhosphorJS Contributors
5|
6| Distributed under the terms of the BSD 3-Clause License.
7|
8| The full license is in the file LICENSE, distributed with this software.
9|----------------------------------------------------------------------------*/
10
11/**
12 * The namespace for array-specific algorithms.
13 */
14export namespace ArrayExt {
15 /**
16 * Find the index of the first occurrence of a value in an array.
17 *
18 * @param array - The array-like object to search.
19 *
20 * @param value - The value to locate in the array. Values are
21 * compared using strict `===` equality.
22 *
23 * @param start - The index of the first element in the range to be
24 * searched, inclusive. The default value is `0`. Negative values
25 * are taken as an offset from the end of the array.
26 *
27 * @param stop - The index of the last element in the range to be
28 * searched, inclusive. The default value is `-1`. Negative values
29 * are taken as an offset from the end of the array.
30 *
31 * @returns The index of the first occurrence of the value, or `-1`
32 * if the value is not found.
33 *
34 * #### Notes
35 * If `stop < start` the search will wrap at the end of the array.
36 *
37 * #### Complexity
38 * Linear.
39 *
40 * #### Undefined Behavior
41 * A `start` or `stop` which is non-integral.
42 *
43 * #### Example
44 * ```typescript
45 * import { ArrayExt } from '@lumino/algorithm';
46 *
47 * let data = ['one', 'two', 'three', 'four', 'one'];
48 * ArrayExt.firstIndexOf(data, 'red'); // -1
49 * ArrayExt.firstIndexOf(data, 'one'); // 0
50 * ArrayExt.firstIndexOf(data, 'one', 1); // 4
51 * ArrayExt.firstIndexOf(data, 'two', 2); // -1
52 * ArrayExt.firstIndexOf(data, 'two', 2, 1); // 1
53 * ```
54 */
55 export function firstIndexOf<T>(
56 array: ArrayLike<T>,
57 value: T,
58 start = 0,
59 stop = -1
60 ): number {
61 let n = array.length;
62 if (n === 0) {
63 return -1;
64 }
65 if (start < 0) {
66 start = Math.max(0, start + n);
67 } else {
68 start = Math.min(start, n - 1);
69 }
70 if (stop < 0) {
71 stop = Math.max(0, stop + n);
72 } else {
73 stop = Math.min(stop, n - 1);
74 }
75 let span: number;
76 if (stop < start) {
77 span = stop + 1 + (n - start);
78 } else {
79 span = stop - start + 1;
80 }
81 for (let i = 0; i < span; ++i) {
82 let j = (start + i) % n;
83 if (array[j] === value) {
84 return j;
85 }
86 }
87 return -1;
88 }
89
90 /**
91 * Find the index of the last occurrence of a value in an array.
92 *
93 * @param array - The array-like object to search.
94 *
95 * @param value - The value to locate in the array. Values are
96 * compared using strict `===` equality.
97 *
98 * @param start - The index of the first element in the range to be
99 * searched, inclusive. The default value is `-1`. Negative values
100 * are taken as an offset from the end of the array.
101 *
102 * @param stop - The index of the last element in the range to be
103 * searched, inclusive. The default value is `0`. Negative values
104 * are taken as an offset from the end of the array.
105 *
106 * @returns The index of the last occurrence of the value, or `-1`
107 * if the value is not found.
108 *
109 * #### Notes
110 * If `start < stop` the search will wrap at the front of the array.
111 *
112 * #### Complexity
113 * Linear.
114 *
115 * #### Undefined Behavior
116 * A `start` or `stop` which is non-integral.
117 *
118 * #### Example
119 * ```typescript
120 * import { ArrayExt } from '@lumino/algorithm';
121 *
122 * let data = ['one', 'two', 'three', 'four', 'one'];
123 * ArrayExt.lastIndexOf(data, 'red'); // -1
124 * ArrayExt.lastIndexOf(data, 'one'); // 4
125 * ArrayExt.lastIndexOf(data, 'one', 1); // 0
126 * ArrayExt.lastIndexOf(data, 'two', 0); // -1
127 * ArrayExt.lastIndexOf(data, 'two', 0, 1); // 1
128 * ```
129 */
130 export function lastIndexOf<T>(
131 array: ArrayLike<T>,
132 value: T,
133 start = -1,
134 stop = 0
135 ): number {
136 let n = array.length;
137 if (n === 0) {
138 return -1;
139 }
140 if (start < 0) {
141 start = Math.max(0, start + n);
142 } else {
143 start = Math.min(start, n - 1);
144 }
145 if (stop < 0) {
146 stop = Math.max(0, stop + n);
147 } else {
148 stop = Math.min(stop, n - 1);
149 }
150 let span: number;
151 if (start < stop) {
152 span = start + 1 + (n - stop);
153 } else {
154 span = start - stop + 1;
155 }
156 for (let i = 0; i < span; ++i) {
157 let j = (start - i + n) % n;
158 if (array[j] === value) {
159 return j;
160 }
161 }
162 return -1;
163 }
164
165 /**
166 * Find the index of the first value which matches a predicate.
167 *
168 * @param array - The array-like object to search.
169 *
170 * @param fn - The predicate function to apply to the values.
171 *
172 * @param start - The index of the first element in the range to be
173 * searched, inclusive. The default value is `0`. Negative values
174 * are taken as an offset from the end of the array.
175 *
176 * @param stop - The index of the last element in the range to be
177 * searched, inclusive. The default value is `-1`. Negative values
178 * are taken as an offset from the end of the array.
179 *
180 * @returns The index of the first matching value, or `-1` if no
181 * matching value is found.
182 *
183 * #### Notes
184 * If `stop < start` the search will wrap at the end of the array.
185 *
186 * #### Complexity
187 * Linear.
188 *
189 * #### Undefined Behavior
190 * A `start` or `stop` which is non-integral.
191 *
192 * Modifying the length of the array while searching.
193 *
194 * #### Example
195 * ```typescript
196 * import { ArrayExt } from '@lumino/algorithm';
197 *
198 * function isEven(value: number): boolean {
199 * return value % 2 === 0;
200 * }
201 *
202 * let data = [1, 2, 3, 4, 3, 2, 1];
203 * ArrayExt.findFirstIndex(data, isEven); // 1
204 * ArrayExt.findFirstIndex(data, isEven, 4); // 5
205 * ArrayExt.findFirstIndex(data, isEven, 6); // -1
206 * ArrayExt.findFirstIndex(data, isEven, 6, 5); // 1
207 * ```
208 */
209 export function findFirstIndex<T>(
210 array: ArrayLike<T>,
211 fn: (value: T, index: number) => boolean,
212 start = 0,
213 stop = -1
214 ): number {
215 let n = array.length;
216 if (n === 0) {
217 return -1;
218 }
219 if (start < 0) {
220 start = Math.max(0, start + n);
221 } else {
222 start = Math.min(start, n - 1);
223 }
224 if (stop < 0) {
225 stop = Math.max(0, stop + n);
226 } else {
227 stop = Math.min(stop, n - 1);
228 }
229 let span: number;
230 if (stop < start) {
231 span = stop + 1 + (n - start);
232 } else {
233 span = stop - start + 1;
234 }
235 for (let i = 0; i < span; ++i) {
236 let j = (start + i) % n;
237 if (fn(array[j], j)) {
238 return j;
239 }
240 }
241 return -1;
242 }
243
244 /**
245 * Find the index of the last value which matches a predicate.
246 *
247 * @param object - The array-like object to search.
248 *
249 * @param fn - The predicate function to apply to the values.
250 *
251 * @param start - The index of the first element in the range to be
252 * searched, inclusive. The default value is `-1`. Negative values
253 * are taken as an offset from the end of the array.
254 *
255 * @param stop - The index of the last element in the range to be
256 * searched, inclusive. The default value is `0`. Negative values
257 * are taken as an offset from the end of the array.
258 *
259 * @returns The index of the last matching value, or `-1` if no
260 * matching value is found.
261 *
262 * #### Notes
263 * If `start < stop` the search will wrap at the front of the array.
264 *
265 * #### Complexity
266 * Linear.
267 *
268 * #### Undefined Behavior
269 * A `start` or `stop` which is non-integral.
270 *
271 * Modifying the length of the array while searching.
272 *
273 * #### Example
274 * ```typescript
275 * import { ArrayExt } from '@lumino/algorithm';
276 *
277 * function isEven(value: number): boolean {
278 * return value % 2 === 0;
279 * }
280 *
281 * let data = [1, 2, 3, 4, 3, 2, 1];
282 * ArrayExt.findLastIndex(data, isEven); // 5
283 * ArrayExt.findLastIndex(data, isEven, 4); // 3
284 * ArrayExt.findLastIndex(data, isEven, 0); // -1
285 * ArrayExt.findLastIndex(data, isEven, 0, 1); // 5
286 * ```
287 */
288 export function findLastIndex<T>(
289 array: ArrayLike<T>,
290 fn: (value: T, index: number) => boolean,
291 start = -1,
292 stop = 0
293 ): number {
294 let n = array.length;
295 if (n === 0) {
296 return -1;
297 }
298 if (start < 0) {
299 start = Math.max(0, start + n);
300 } else {
301 start = Math.min(start, n - 1);
302 }
303 if (stop < 0) {
304 stop = Math.max(0, stop + n);
305 } else {
306 stop = Math.min(stop, n - 1);
307 }
308 let d: number;
309 if (start < stop) {
310 d = start + 1 + (n - stop);
311 } else {
312 d = start - stop + 1;
313 }
314 for (let i = 0; i < d; ++i) {
315 let j = (start - i + n) % n;
316 if (fn(array[j], j)) {
317 return j;
318 }
319 }
320 return -1;
321 }
322
323 /**
324 * Find the first value which matches a predicate.
325 *
326 * @param array - The array-like object to search.
327 *
328 * @param fn - The predicate function to apply to the values.
329 *
330 * @param start - The index of the first element in the range to be
331 * searched, inclusive. The default value is `0`. Negative values
332 * are taken as an offset from the end of the array.
333 *
334 * @param stop - The index of the last element in the range to be
335 * searched, inclusive. The default value is `-1`. Negative values
336 * are taken as an offset from the end of the array.
337 *
338 * @returns The first matching value, or `undefined` if no matching
339 * value is found.
340 *
341 * #### Notes
342 * If `stop < start` the search will wrap at the end of the array.
343 *
344 * #### Complexity
345 * Linear.
346 *
347 * #### Undefined Behavior
348 * A `start` or `stop` which is non-integral.
349 *
350 * Modifying the length of the array while searching.
351 *
352 * #### Example
353 * ```typescript
354 * import { ArrayExt } from '@lumino/algorithm';
355 *
356 * function isEven(value: number): boolean {
357 * return value % 2 === 0;
358 * }
359 *
360 * let data = [1, 2, 3, 4, 3, 2, 1];
361 * ArrayExt.findFirstValue(data, isEven); // 2
362 * ArrayExt.findFirstValue(data, isEven, 2); // 4
363 * ArrayExt.findFirstValue(data, isEven, 6); // undefined
364 * ArrayExt.findFirstValue(data, isEven, 6, 5); // 2
365 * ```
366 */
367 export function findFirstValue<T>(
368 array: ArrayLike<T>,
369 fn: (value: T, index: number) => boolean,
370 start = 0,
371 stop = -1
372 ): T | undefined {
373 let index = findFirstIndex(array, fn, start, stop);
374 return index !== -1 ? array[index] : undefined;
375 }
376
377 /**
378 * Find the last value which matches a predicate.
379 *
380 * @param object - The array-like object to search.
381 *
382 * @param fn - The predicate function to apply to the values.
383 *
384 * @param start - The index of the first element in the range to be
385 * searched, inclusive. The default value is `-1`. Negative values
386 * are taken as an offset from the end of the array.
387 *
388 * @param stop - The index of the last element in the range to be
389 * searched, inclusive. The default value is `0`. Negative values
390 * are taken as an offset from the end of the array.
391 *
392 * @returns The last matching value, or `undefined` if no matching
393 * value is found.
394 *
395 * #### Notes
396 * If `start < stop` the search will wrap at the front of the array.
397 *
398 * #### Complexity
399 * Linear.
400 *
401 * #### Undefined Behavior
402 * A `start` or `stop` which is non-integral.
403 *
404 * Modifying the length of the array while searching.
405 *
406 * #### Example
407 * ```typescript
408 * import { ArrayExt } from '@lumino/algorithm';
409 *
410 * function isEven(value: number): boolean {
411 * return value % 2 === 0;
412 * }
413 *
414 * let data = [1, 2, 3, 4, 3, 2, 1];
415 * ArrayExt.findLastValue(data, isEven); // 2
416 * ArrayExt.findLastValue(data, isEven, 4); // 4
417 * ArrayExt.findLastValue(data, isEven, 0); // undefined
418 * ArrayExt.findLastValue(data, isEven, 0, 1); // 2
419 * ```
420 */
421 export function findLastValue<T>(
422 array: ArrayLike<T>,
423 fn: (value: T, index: number) => boolean,
424 start = -1,
425 stop = 0
426 ): T | undefined {
427 let index = findLastIndex(array, fn, start, stop);
428 return index !== -1 ? array[index] : undefined;
429 }
430
431 /**
432 * Find the index of the first element which compares `>=` to a value.
433 *
434 * @param array - The sorted array-like object to search.
435 *
436 * @param value - The value to locate in the array.
437 *
438 * @param fn - The 3-way comparison function to apply to the values.
439 * It should return `< 0` if an element is less than a value, `0` if
440 * an element is equal to a value, or `> 0` if an element is greater
441 * than a value.
442 *
443 * @param start - The index of the first element in the range to be
444 * searched, inclusive. The default value is `0`. Negative values
445 * are taken as an offset from the end of the array.
446 *
447 * @param stop - The index of the last element in the range to be
448 * searched, inclusive. The default value is `-1`. Negative values
449 * are taken as an offset from the end of the array.
450 *
451 * @returns The index of the first element which compares `>=` to the
452 * value, or `length` if there is no such element. If the computed
453 * index for `stop` is less than `start`, then the computed index
454 * for `start` is returned.
455 *
456 * #### Notes
457 * The array must already be sorted in ascending order according to
458 * the comparison function.
459 *
460 * #### Complexity
461 * Logarithmic.
462 *
463 * #### Undefined Behavior
464 * Searching a range which is not sorted in ascending order.
465 *
466 * A `start` or `stop` which is non-integral.
467 *
468 * Modifying the length of the array while searching.
469 *
470 * #### Example
471 * ```typescript
472 * import { ArrayExt } from '@lumino/algorithm';
473 *
474 * function numberCmp(a: number, b: number): number {
475 * return a - b;
476 * }
477 *
478 * let data = [0, 3, 4, 7, 7, 9];
479 * ArrayExt.lowerBound(data, 0, numberCmp); // 0
480 * ArrayExt.lowerBound(data, 6, numberCmp); // 3
481 * ArrayExt.lowerBound(data, 7, numberCmp); // 3
482 * ArrayExt.lowerBound(data, -1, numberCmp); // 0
483 * ArrayExt.lowerBound(data, 10, numberCmp); // 6
484 * ```
485 */
486 export function lowerBound<T, U>(
487 array: ArrayLike<T>,
488 value: U,
489 fn: (element: T, value: U) => number,
490 start = 0,
491 stop = -1
492 ): number {
493 let n = array.length;
494 if (n === 0) {
495 return 0;
496 }
497 if (start < 0) {
498 start = Math.max(0, start + n);
499 } else {
500 start = Math.min(start, n - 1);
501 }
502 if (stop < 0) {
503 stop = Math.max(0, stop + n);
504 } else {
505 stop = Math.min(stop, n - 1);
506 }
507 let begin = start;
508 let span = stop - start + 1;
509 while (span > 0) {
510 let half = span >> 1;
511 let middle = begin + half;
512 if (fn(array[middle], value) < 0) {
513 begin = middle + 1;
514 span -= half + 1;
515 } else {
516 span = half;
517 }
518 }
519 return begin;
520 }
521
522 /**
523 * Find the index of the first element which compares `>` than a value.
524 *
525 * @param array - The sorted array-like object to search.
526 *
527 * @param value - The value to locate in the array.
528 *
529 * @param fn - The 3-way comparison function to apply to the values.
530 * It should return `< 0` if an element is less than a value, `0` if
531 * an element is equal to a value, or `> 0` if an element is greater
532 * than a value.
533 *
534 * @param start - The index of the first element in the range to be
535 * searched, inclusive. The default value is `0`. Negative values
536 * are taken as an offset from the end of the array.
537 *
538 * @param stop - The index of the last element in the range to be
539 * searched, inclusive. The default value is `-1`. Negative values
540 * are taken as an offset from the end of the array.
541 *
542 * @returns The index of the first element which compares `>` than the
543 * value, or `length` if there is no such element. If the computed
544 * index for `stop` is less than `start`, then the computed index
545 * for `start` is returned.
546 *
547 * #### Notes
548 * The array must already be sorted in ascending order according to
549 * the comparison function.
550 *
551 * #### Complexity
552 * Logarithmic.
553 *
554 * #### Undefined Behavior
555 * Searching a range which is not sorted in ascending order.
556 *
557 * A `start` or `stop` which is non-integral.
558 *
559 * Modifying the length of the array while searching.
560 *
561 * #### Example
562 * ```typescript
563 * import { ArrayExt } from '@lumino/algorithm';
564 *
565 * function numberCmp(a: number, b: number): number {
566 * return a - b;
567 * }
568 *
569 * let data = [0, 3, 4, 7, 7, 9];
570 * ArrayExt.upperBound(data, 0, numberCmp); // 1
571 * ArrayExt.upperBound(data, 6, numberCmp); // 3
572 * ArrayExt.upperBound(data, 7, numberCmp); // 5
573 * ArrayExt.upperBound(data, -1, numberCmp); // 0
574 * ArrayExt.upperBound(data, 10, numberCmp); // 6
575 * ```
576 */
577 export function upperBound<T, U>(
578 array: ArrayLike<T>,
579 value: U,
580 fn: (element: T, value: U) => number,
581 start = 0,
582 stop = -1
583 ): number {
584 let n = array.length;
585 if (n === 0) {
586 return 0;
587 }
588 if (start < 0) {
589 start = Math.max(0, start + n);
590 } else {
591 start = Math.min(start, n - 1);
592 }
593 if (stop < 0) {
594 stop = Math.max(0, stop + n);
595 } else {
596 stop = Math.min(stop, n - 1);
597 }
598 let begin = start;
599 let span = stop - start + 1;
600 while (span > 0) {
601 let half = span >> 1;
602 let middle = begin + half;
603 if (fn(array[middle], value) > 0) {
604 span = half;
605 } else {
606 begin = middle + 1;
607 span -= half + 1;
608 }
609 }
610 return begin;
611 }
612
613 /**
614 * Test whether two arrays are shallowly equal.
615 *
616 * @param a - The first array-like object to compare.
617 *
618 * @param b - The second array-like object to compare.
619 *
620 * @param fn - The comparison function to apply to the elements. It
621 * should return `true` if the elements are "equal". The default
622 * compares elements using strict `===` equality.
623 *
624 * @returns Whether the two arrays are shallowly equal.
625 *
626 * #### Complexity
627 * Linear.
628 *
629 * #### Undefined Behavior
630 * Modifying the length of the arrays while comparing.
631 *
632 * #### Example
633 * ```typescript
634 * import { ArrayExt } from '@lumino/algorithm';
635 *
636 * let d1 = [0, 3, 4, 7, 7, 9];
637 * let d2 = [0, 3, 4, 7, 7, 9];
638 * let d3 = [42];
639 * ArrayExt.shallowEqual(d1, d2); // true
640 * ArrayExt.shallowEqual(d2, d3); // false
641 * ```
642 */
643 export function shallowEqual<T>(
644 a: ArrayLike<T>,
645 b: ArrayLike<T>,
646 fn?: (a: T, b: T) => boolean
647 ): boolean {
648 // Check for object identity first.
649 if (a === b) {
650 return true;
651 }
652
653 // Bail early if the lengths are different.
654 if (a.length !== b.length) {
655 return false;
656 }
657
658 // Compare each element for equality.
659 for (let i = 0, n = a.length; i < n; ++i) {
660 if (fn ? !fn(a[i], b[i]) : a[i] !== b[i]) {
661 return false;
662 }
663 }
664
665 // The array are shallowly equal.
666 return true;
667 }
668
669 /**
670 * Create a slice of an array subject to an optional step.
671 *
672 * @param array - The array-like object of interest.
673 *
674 * @param options - The options for configuring the slice.
675 *
676 * @returns A new array with the specified values.
677 *
678 * @throws An exception if the slice `step` is `0`.
679 *
680 * #### Complexity
681 * Linear.
682 *
683 * #### Undefined Behavior
684 * A `start`, `stop`, or `step` which is non-integral.
685 *
686 * #### Example
687 * ```typescript
688 * import { ArrayExt } from '@lumino/algorithm';
689 *
690 * let data = [0, 3, 4, 7, 7, 9];
691 * ArrayExt.slice(data); // [0, 3, 4, 7, 7, 9]
692 * ArrayExt.slice(data, { start: 2 }); // [4, 7, 7, 9]
693 * ArrayExt.slice(data, { start: 0, stop: 4 }); // [0, 3, 4, 7]
694 * ArrayExt.slice(data, { step: 2 }); // [0, 4, 7]
695 * ArrayExt.slice(data, { step: -1 }); // [9, 7, 7, 4, 3, 0]
696 * ```
697 */
698 export function slice<T>(
699 array: ArrayLike<T>,
700 options: slice.IOptions = {}
701 ): T[] {
702 // Extract the options.
703 let { start, stop, step } = options;
704
705 // Set up the `step` value.
706 if (step === undefined) {
707 step = 1;
708 }
709
710 // Validate the step size.
711 if (step === 0) {
712 throw new Error('Slice `step` cannot be zero.');
713 }
714
715 // Look up the length of the array.
716 let n = array.length;
717
718 // Set up the `start` value.
719 if (start === undefined) {
720 start = step < 0 ? n - 1 : 0;
721 } else if (start < 0) {
722 start = Math.max(start + n, step < 0 ? -1 : 0);
723 } else if (start >= n) {
724 start = step < 0 ? n - 1 : n;
725 }
726
727 // Set up the `stop` value.
728 if (stop === undefined) {
729 stop = step < 0 ? -1 : n;
730 } else if (stop < 0) {
731 stop = Math.max(stop + n, step < 0 ? -1 : 0);
732 } else if (stop >= n) {
733 stop = step < 0 ? n - 1 : n;
734 }
735
736 // Compute the slice length.
737 let length;
738 if ((step < 0 && stop >= start) || (step > 0 && start >= stop)) {
739 length = 0;
740 } else if (step < 0) {
741 length = Math.floor((stop - start + 1) / step + 1);
742 } else {
743 length = Math.floor((stop - start - 1) / step + 1);
744 }
745
746 // Compute the sliced result.
747 let result: T[] = [];
748 for (let i = 0; i < length; ++i) {
749 result[i] = array[start + i * step];
750 }
751
752 // Return the result.
753 return result;
754 }
755
756 /**
757 * The namespace for the `slice` function statics.
758 */
759 export namespace slice {
760 /**
761 * The options for the `slice` function.
762 */
763 export interface IOptions {
764 /**
765 * The starting index of the slice, inclusive.
766 *
767 * Negative values are taken as an offset from the end
768 * of the array.
769 *
770 * The default is `0` if `step > 0` else `n - 1`.
771 */
772 start?: number;
773
774 /**
775 * The stopping index of the slice, exclusive.
776 *
777 * Negative values are taken as an offset from the end
778 * of the array.
779 *
780 * The default is `n` if `step > 0` else `-n - 1`.
781 */
782 stop?: number;
783
784 /**
785 * The step value for the slice.
786 *
787 * This must not be `0`.
788 *
789 * The default is `1`.
790 */
791 step?: number;
792 }
793 }
794
795 /**
796 * An array-like object which supports item assignment.
797 */
798 export type MutableArrayLike<T> = {
799 readonly length: number;
800 [index: number]: T;
801 };
802
803 /**
804 * Move an element in an array from one index to another.
805 *
806 * @param array - The mutable array-like object of interest.
807 *
808 * @param fromIndex - The index of the element to move. Negative
809 * values are taken as an offset from the end of the array.
810 *
811 * @param toIndex - The target index of the element. Negative
812 * values are taken as an offset from the end of the array.
813 *
814 * #### Complexity
815 * Linear.
816 *
817 * #### Undefined Behavior
818 * A `fromIndex` or `toIndex` which is non-integral.
819 *
820 * #### Example
821 * ```typescript
822 * import { ArrayExt } from from '@lumino/algorithm';
823 *
824 * let data = [0, 1, 2, 3, 4];
825 * ArrayExt.move(data, 1, 2); // [0, 2, 1, 3, 4]
826 * ArrayExt.move(data, 4, 2); // [0, 2, 4, 1, 3]
827 * ```
828 */
829 export function move<T>(
830 array: MutableArrayLike<T>,
831 fromIndex: number,
832 toIndex: number
833 ): void {
834 let n = array.length;
835 if (n <= 1) {
836 return;
837 }
838 if (fromIndex < 0) {
839 fromIndex = Math.max(0, fromIndex + n);
840 } else {
841 fromIndex = Math.min(fromIndex, n - 1);
842 }
843 if (toIndex < 0) {
844 toIndex = Math.max(0, toIndex + n);
845 } else {
846 toIndex = Math.min(toIndex, n - 1);
847 }
848 if (fromIndex === toIndex) {
849 return;
850 }
851 let value = array[fromIndex];
852 let d = fromIndex < toIndex ? 1 : -1;
853 for (let i = fromIndex; i !== toIndex; i += d) {
854 array[i] = array[i + d];
855 }
856 array[toIndex] = value;
857 }
858
859 /**
860 * Reverse an array in-place.
861 *
862 * @param array - The mutable array-like object of interest.
863 *
864 * @param start - The index of the first element in the range to be
865 * reversed, inclusive. The default value is `0`. Negative values
866 * are taken as an offset from the end of the array.
867 *
868 * @param stop - The index of the last element in the range to be
869 * reversed, inclusive. The default value is `-1`. Negative values
870 * are taken as an offset from the end of the array.
871 *
872 * #### Complexity
873 * Linear.
874 *
875 * #### Undefined Behavior
876 * A `start` or `stop` index which is non-integral.
877 *
878 * #### Example
879 * ```typescript
880 * import { ArrayExt } from '@lumino/algorithm';
881 *
882 * let data = [0, 1, 2, 3, 4];
883 * ArrayExt.reverse(data, 1, 3); // [0, 3, 2, 1, 4]
884 * ArrayExt.reverse(data, 3); // [0, 3, 2, 4, 1]
885 * ArrayExt.reverse(data); // [1, 4, 2, 3, 0]
886 * ```
887 */
888 export function reverse<T>(
889 array: MutableArrayLike<T>,
890 start = 0,
891 stop = -1
892 ): void {
893 let n = array.length;
894 if (n <= 1) {
895 return;
896 }
897 if (start < 0) {
898 start = Math.max(0, start + n);
899 } else {
900 start = Math.min(start, n - 1);
901 }
902 if (stop < 0) {
903 stop = Math.max(0, stop + n);
904 } else {
905 stop = Math.min(stop, n - 1);
906 }
907 while (start < stop) {
908 let a = array[start];
909 let b = array[stop];
910 array[start++] = b;
911 array[stop--] = a;
912 }
913 }
914
915 /**
916 * Rotate the elements of an array in-place.
917 *
918 * @param array - The mutable array-like object of interest.
919 *
920 * @param delta - The amount of rotation to apply to the elements. A
921 * positive value will rotate the elements to the left. A negative
922 * value will rotate the elements to the right.
923 *
924 * @param start - The index of the first element in the range to be
925 * rotated, inclusive. The default value is `0`. Negative values
926 * are taken as an offset from the end of the array.
927 *
928 * @param stop - The index of the last element in the range to be
929 * rotated, inclusive. The default value is `-1`. Negative values
930 * are taken as an offset from the end of the array.
931 *
932 * #### Complexity
933 * Linear.
934 *
935 * #### Undefined Behavior
936 * A `delta`, `start`, or `stop` which is non-integral.
937 *
938 * #### Example
939 * ```typescript
940 * import { ArrayExt } from '@lumino/algorithm';
941 *
942 * let data = [0, 1, 2, 3, 4];
943 * ArrayExt.rotate(data, 2); // [2, 3, 4, 0, 1]
944 * ArrayExt.rotate(data, -2); // [0, 1, 2, 3, 4]
945 * ArrayExt.rotate(data, 10); // [0, 1, 2, 3, 4]
946 * ArrayExt.rotate(data, 9); // [4, 0, 1, 2, 3]
947 * ArrayExt.rotate(data, 2, 1, 3); // [4, 2, 0, 1, 3]
948 * ```
949 */
950 export function rotate<T>(
951 array: MutableArrayLike<T>,
952 delta: number,
953 start = 0,
954 stop = -1
955 ): void {
956 let n = array.length;
957 if (n <= 1) {
958 return;
959 }
960 if (start < 0) {
961 start = Math.max(0, start + n);
962 } else {
963 start = Math.min(start, n - 1);
964 }
965 if (stop < 0) {
966 stop = Math.max(0, stop + n);
967 } else {
968 stop = Math.min(stop, n - 1);
969 }
970 if (start >= stop) {
971 return;
972 }
973 let length = stop - start + 1;
974 if (delta > 0) {
975 delta = delta % length;
976 } else if (delta < 0) {
977 delta = ((delta % length) + length) % length;
978 }
979 if (delta === 0) {
980 return;
981 }
982 let pivot = start + delta;
983 reverse(array, start, pivot - 1);
984 reverse(array, pivot, stop);
985 reverse(array, start, stop);
986 }
987
988 /**
989 * Fill an array with a static value.
990 *
991 * @param array - The mutable array-like object to fill.
992 *
993 * @param value - The static value to use to fill the array.
994 *
995 * @param start - The index of the first element in the range to be
996 * filled, inclusive. The default value is `0`. Negative values
997 * are taken as an offset from the end of the array.
998 *
999 * @param stop - The index of the last element in the range to be
1000 * filled, inclusive. The default value is `-1`. Negative values
1001 * are taken as an offset from the end of the array.
1002 *
1003 * #### Notes
1004 * If `stop < start` the fill will wrap at the end of the array.
1005 *
1006 * #### Complexity
1007 * Linear.
1008 *
1009 * #### Undefined Behavior
1010 * A `start` or `stop` which is non-integral.
1011 *
1012 * #### Example
1013 * ```typescript
1014 * import { ArrayExt } from '@lumino/algorithm';
1015 *
1016 * let data = ['one', 'two', 'three', 'four'];
1017 * ArrayExt.fill(data, 'r'); // ['r', 'r', 'r', 'r']
1018 * ArrayExt.fill(data, 'g', 1); // ['r', 'g', 'g', 'g']
1019 * ArrayExt.fill(data, 'b', 2, 3); // ['r', 'g', 'b', 'b']
1020 * ArrayExt.fill(data, 'z', 3, 1); // ['z', 'z', 'b', 'z']
1021 * ```
1022 */
1023 export function fill<T>(
1024 array: MutableArrayLike<T>,
1025 value: T,
1026 start = 0,
1027 stop = -1
1028 ): void {
1029 let n = array.length;
1030 if (n === 0) {
1031 return;
1032 }
1033 if (start < 0) {
1034 start = Math.max(0, start + n);
1035 } else {
1036 start = Math.min(start, n - 1);
1037 }
1038 if (stop < 0) {
1039 stop = Math.max(0, stop + n);
1040 } else {
1041 stop = Math.min(stop, n - 1);
1042 }
1043 let span: number;
1044 if (stop < start) {
1045 span = stop + 1 + (n - start);
1046 } else {
1047 span = stop - start + 1;
1048 }
1049 for (let i = 0; i < span; ++i) {
1050 array[(start + i) % n] = value;
1051 }
1052 }
1053
1054 /**
1055 * Insert a value into an array at a specific index.
1056 *
1057 * @param array - The array of interest.
1058 *
1059 * @param index - The index at which to insert the value. Negative
1060 * values are taken as an offset from the end of the array.
1061 *
1062 * @param value - The value to set at the specified index.
1063 *
1064 * #### Complexity
1065 * Linear.
1066 *
1067 * #### Undefined Behavior
1068 * An `index` which is non-integral.
1069 *
1070 * #### Example
1071 * ```typescript
1072 * import { ArrayExt } from '@lumino/algorithm';
1073 *
1074 * let data = [0, 1, 2];
1075 * ArrayExt.insert(data, 0, -1); // [-1, 0, 1, 2]
1076 * ArrayExt.insert(data, 2, 12); // [-1, 0, 12, 1, 2]
1077 * ArrayExt.insert(data, -1, 7); // [-1, 0, 12, 1, 7, 2]
1078 * ArrayExt.insert(data, 6, 19); // [-1, 0, 12, 1, 7, 2, 19]
1079 * ```
1080 */
1081 export function insert<T>(array: Array<T>, index: number, value: T): void {
1082 let n = array.length;
1083 if (index < 0) {
1084 index = Math.max(0, index + n);
1085 } else {
1086 index = Math.min(index, n);
1087 }
1088 for (let i = n; i > index; --i) {
1089 array[i] = array[i - 1];
1090 }
1091 array[index] = value;
1092 }
1093
1094 /**
1095 * Remove and return a value at a specific index in an array.
1096 *
1097 * @param array - The array of interest.
1098 *
1099 * @param index - The index of the value to remove. Negative values
1100 * are taken as an offset from the end of the array.
1101 *
1102 * @returns The value at the specified index, or `undefined` if the
1103 * index is out of range.
1104 *
1105 * #### Complexity
1106 * Linear.
1107 *
1108 * #### Undefined Behavior
1109 * An `index` which is non-integral.
1110 *
1111 * #### Example
1112 * ```typescript
1113 * import { ArrayExt } from '@lumino/algorithm';
1114 *
1115 * let data = [0, 12, 23, 39, 14, 12, 75];
1116 * ArrayExt.removeAt(data, 2); // 23
1117 * ArrayExt.removeAt(data, -2); // 12
1118 * ArrayExt.removeAt(data, 10); // undefined;
1119 * ```
1120 */
1121 export function removeAt<T>(array: Array<T>, index: number): T | undefined {
1122 let n = array.length;
1123 if (index < 0) {
1124 index += n;
1125 }
1126 if (index < 0 || index >= n) {
1127 return undefined;
1128 }
1129 let value = array[index];
1130 for (let i = index + 1; i < n; ++i) {
1131 array[i - 1] = array[i];
1132 }
1133 array.length = n - 1;
1134 return value;
1135 }
1136
1137 /**
1138 * Remove the first occurrence of a value from an array.
1139 *
1140 * @param array - The array of interest.
1141 *
1142 * @param value - The value to remove from the array. Values are
1143 * compared using strict `===` equality.
1144 *
1145 * @param start - The index of the first element in the range to be
1146 * searched, inclusive. The default value is `0`. Negative values
1147 * are taken as an offset from the end of the array.
1148 *
1149 * @param stop - The index of the last element in the range to be
1150 * searched, inclusive. The default value is `-1`. Negative values
1151 * are taken as an offset from the end of the array.
1152 *
1153 * @returns The index of the removed value, or `-1` if the value
1154 * is not contained in the array.
1155 *
1156 * #### Notes
1157 * If `stop < start` the search will wrap at the end of the array.
1158 *
1159 * #### Complexity
1160 * Linear.
1161 *
1162 * #### Example
1163 * ```typescript
1164 * import { ArrayExt } from '@lumino/algorithm';
1165 *
1166 * let data = [0, 12, 23, 39, 14, 12, 75];
1167 * ArrayExt.removeFirstOf(data, 12); // 1
1168 * ArrayExt.removeFirstOf(data, 17); // -1
1169 * ArrayExt.removeFirstOf(data, 39, 3); // -1
1170 * ArrayExt.removeFirstOf(data, 39, 3, 2); // 2
1171 * ```
1172 */
1173 export function removeFirstOf<T>(
1174 array: Array<T>,
1175 value: T,
1176 start = 0,
1177 stop = -1
1178 ): number {
1179 let index = firstIndexOf(array, value, start, stop);
1180 if (index !== -1) {
1181 removeAt(array, index);
1182 }
1183 return index;
1184 }
1185
1186 /**
1187 * Remove the last occurrence of a value from an array.
1188 *
1189 * @param array - The array of interest.
1190 *
1191 * @param value - The value to remove from the array. Values are
1192 * compared using strict `===` equality.
1193 *
1194 * @param start - The index of the first element in the range to be
1195 * searched, inclusive. The default value is `-1`. Negative values
1196 * are taken as an offset from the end of the array.
1197 *
1198 * @param stop - The index of the last element in the range to be
1199 * searched, inclusive. The default value is `0`. Negative values
1200 * are taken as an offset from the end of the array.
1201 *
1202 * @returns The index of the removed value, or `-1` if the value
1203 * is not contained in the array.
1204 *
1205 * #### Notes
1206 * If `start < stop` the search will wrap at the end of the array.
1207 *
1208 * #### Complexity
1209 * Linear.
1210 *
1211 * #### Example
1212 * ```typescript
1213 * import { ArrayExt } from '@lumino/algorithm';
1214 *
1215 * let data = [0, 12, 23, 39, 14, 12, 75];
1216 * ArrayExt.removeLastOf(data, 12); // 5
1217 * ArrayExt.removeLastOf(data, 17); // -1
1218 * ArrayExt.removeLastOf(data, 39, 2); // -1
1219 * ArrayExt.removeLastOf(data, 39, 2, 3); // 3
1220 * ```
1221 */
1222 export function removeLastOf<T>(
1223 array: Array<T>,
1224 value: T,
1225 start = -1,
1226 stop = 0
1227 ): number {
1228 let index = lastIndexOf(array, value, start, stop);
1229 if (index !== -1) {
1230 removeAt(array, index);
1231 }
1232 return index;
1233 }
1234
1235 /**
1236 * Remove all occurrences of a value from an array.
1237 *
1238 * @param array - The array of interest.
1239 *
1240 * @param value - The value to remove from the array. Values are
1241 * compared using strict `===` equality.
1242 *
1243 * @param start - The index of the first element in the range to be
1244 * searched, inclusive. The default value is `0`. Negative values
1245 * are taken as an offset from the end of the array.
1246 *
1247 * @param stop - The index of the last element in the range to be
1248 * searched, inclusive. The default value is `-1`. Negative values
1249 * are taken as an offset from the end of the array.
1250 *
1251 * @returns The number of elements removed from the array.
1252 *
1253 * #### Notes
1254 * If `stop < start` the search will conceptually wrap at the end of
1255 * the array, however the array will be traversed front-to-back.
1256 *
1257 * #### Complexity
1258 * Linear.
1259 *
1260 * #### Example
1261 * ```typescript
1262 * import { ArrayExt } from '@lumino/algorithm';
1263 *
1264 * let data = [14, 12, 23, 39, 14, 12, 19, 14];
1265 * ArrayExt.removeAllOf(data, 12); // 2
1266 * ArrayExt.removeAllOf(data, 17); // 0
1267 * ArrayExt.removeAllOf(data, 14, 1, 4); // 1
1268 * ```
1269 */
1270 export function removeAllOf<T>(
1271 array: Array<T>,
1272 value: T,
1273 start = 0,
1274 stop = -1
1275 ): number {
1276 let n = array.length;
1277 if (n === 0) {
1278 return 0;
1279 }
1280 if (start < 0) {
1281 start = Math.max(0, start + n);
1282 } else {
1283 start = Math.min(start, n - 1);
1284 }
1285 if (stop < 0) {
1286 stop = Math.max(0, stop + n);
1287 } else {
1288 stop = Math.min(stop, n - 1);
1289 }
1290 let count = 0;
1291 for (let i = 0; i < n; ++i) {
1292 if (start <= stop && i >= start && i <= stop && array[i] === value) {
1293 count++;
1294 } else if (
1295 stop < start &&
1296 (i <= stop || i >= start) &&
1297 array[i] === value
1298 ) {
1299 count++;
1300 } else if (count > 0) {
1301 array[i - count] = array[i];
1302 }
1303 }
1304 if (count > 0) {
1305 array.length = n - count;
1306 }
1307 return count;
1308 }
1309
1310 /**
1311 * Remove the first occurrence of a value which matches a predicate.
1312 *
1313 * @param array - The array of interest.
1314 *
1315 * @param fn - The predicate function to apply to the values.
1316 *
1317 * @param start - The index of the first element in the range to be
1318 * searched, inclusive. The default value is `0`. Negative values
1319 * are taken as an offset from the end of the array.
1320 *
1321 * @param stop - The index of the last element in the range to be
1322 * searched, inclusive. The default value is `-1`. Negative values
1323 * are taken as an offset from the end of the array.
1324 *
1325 * @returns The removed `{ index, value }`, which will be `-1` and
1326 * `undefined` if the value is not contained in the array.
1327 *
1328 * #### Notes
1329 * If `stop < start` the search will wrap at the end of the array.
1330 *
1331 * #### Complexity
1332 * Linear.
1333 *
1334 * #### Example
1335 * ```typescript
1336 * import { ArrayExt } from '@lumino/algorithm';
1337 *
1338 * function isEven(value: number): boolean {
1339 * return value % 2 === 0;
1340 * }
1341 *
1342 * let data = [0, 12, 23, 39, 14, 12, 75];
1343 * ArrayExt.removeFirstWhere(data, isEven); // { index: 0, value: 0 }
1344 * ArrayExt.removeFirstWhere(data, isEven, 2); // { index: 3, value: 14 }
1345 * ArrayExt.removeFirstWhere(data, isEven, 4); // { index: -1, value: undefined }
1346 * ```
1347 */
1348 export function removeFirstWhere<T>(
1349 array: Array<T>,
1350 fn: (value: T, index: number) => boolean,
1351 start = 0,
1352 stop = -1
1353 ): { index: number; value: T | undefined } {
1354 let value: T | undefined;
1355 let index = findFirstIndex(array, fn, start, stop);
1356 if (index !== -1) {
1357 value = removeAt(array, index);
1358 }
1359 return { index, value };
1360 }
1361
1362 /**
1363 * Remove the last occurrence of a value which matches a predicate.
1364 *
1365 * @param array - The array of interest.
1366 *
1367 * @param fn - The predicate function to apply to the values.
1368 *
1369 * @param start - The index of the first element in the range to be
1370 * searched, inclusive. The default value is `-1`. Negative values
1371 * are taken as an offset from the end of the array.
1372 *
1373 * @param stop - The index of the last element in the range to be
1374 * searched, inclusive. The default value is `0`. Negative values
1375 * are taken as an offset from the end of the array.
1376 *
1377 * @returns The removed `{ index, value }`, which will be `-1` and
1378 * `undefined` if the value is not contained in the array.
1379 *
1380 * #### Notes
1381 * If `start < stop` the search will wrap at the end of the array.
1382 *
1383 * #### Complexity
1384 * Linear.
1385 *
1386 * #### Example
1387 * ```typescript
1388 * import { ArrayExt } from '@lumino/algorithm';
1389 *
1390 * function isEven(value: number): boolean {
1391 * return value % 2 === 0;
1392 * }
1393 *
1394 * let data = [0, 12, 23, 39, 14, 12, 75];
1395 * ArrayExt.removeLastWhere(data, isEven); // { index: 5, value: 12 }
1396 * ArrayExt.removeLastWhere(data, isEven, 2); // { index: 1, value: 12 }
1397 * ArrayExt.removeLastWhere(data, isEven, 2, 1); // { index: -1, value: undefined }
1398 * ```
1399 */
1400 export function removeLastWhere<T>(
1401 array: Array<T>,
1402 fn: (value: T, index: number) => boolean,
1403 start = -1,
1404 stop = 0
1405 ): { index: number; value: T | undefined } {
1406 let value: T | undefined;
1407 let index = findLastIndex(array, fn, start, stop);
1408 if (index !== -1) {
1409 value = removeAt(array, index);
1410 }
1411 return { index, value };
1412 }
1413
1414 /**
1415 * Remove all occurrences of values which match a predicate.
1416 *
1417 * @param array - The array of interest.
1418 *
1419 * @param fn - The predicate function to apply to the values.
1420 *
1421 * @param start - The index of the first element in the range to be
1422 * searched, inclusive. The default value is `0`. Negative values
1423 * are taken as an offset from the end of the array.
1424 *
1425 * @param stop - The index of the last element in the range to be
1426 * searched, inclusive. The default value is `-1`. Negative values
1427 * are taken as an offset from the end of the array.
1428 *
1429 * @returns The number of elements removed from the array.
1430 *
1431 * #### Notes
1432 * If `stop < start` the search will conceptually wrap at the end of
1433 * the array, however the array will be traversed front-to-back.
1434 *
1435 * #### Complexity
1436 * Linear.
1437 *
1438 * #### Example
1439 * ```typescript
1440 * import { ArrayExt } from '@lumino/algorithm';
1441 *
1442 * function isEven(value: number): boolean {
1443 * return value % 2 === 0;
1444 * }
1445 *
1446 * function isNegative(value: number): boolean {
1447 * return value < 0;
1448 * }
1449 *
1450 * let data = [0, 12, -13, -9, 23, 39, 14, -15, 12, 75];
1451 * ArrayExt.removeAllWhere(data, isEven); // 4
1452 * ArrayExt.removeAllWhere(data, isNegative, 0, 3); // 2
1453 * ```
1454 */
1455 export function removeAllWhere<T>(
1456 array: Array<T>,
1457 fn: (value: T, index: number) => boolean,
1458 start = 0,
1459 stop = -1
1460 ): number {
1461 let n = array.length;
1462 if (n === 0) {
1463 return 0;
1464 }
1465 if (start < 0) {
1466 start = Math.max(0, start + n);
1467 } else {
1468 start = Math.min(start, n - 1);
1469 }
1470 if (stop < 0) {
1471 stop = Math.max(0, stop + n);
1472 } else {
1473 stop = Math.min(stop, n - 1);
1474 }
1475 let count = 0;
1476 for (let i = 0; i < n; ++i) {
1477 if (start <= stop && i >= start && i <= stop && fn(array[i], i)) {
1478 count++;
1479 } else if (stop < start && (i <= stop || i >= start) && fn(array[i], i)) {
1480 count++;
1481 } else if (count > 0) {
1482 array[i - count] = array[i];
1483 }
1484 }
1485 if (count > 0) {
1486 array.length = n - count;
1487 }
1488 return count;
1489 }
1490}