// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for array-specific algorithms. */ export namespace ArrayExt { /** * Find the index of the first occurrence of a value in an array. * * @param array - The array-like object to search. * * @param value - The value to locate in the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first occurrence of the value, or `-1` * if the value is not found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four', 'one']; * ArrayExt.firstIndexOf(data, 'red'); // -1 * ArrayExt.firstIndexOf(data, 'one'); // 0 * ArrayExt.firstIndexOf(data, 'one', 1); // 4 * ArrayExt.firstIndexOf(data, 'two', 2); // -1 * ArrayExt.firstIndexOf(data, 'two', 2, 1); // 1 * ``` */ export function firstIndexOf( array: ArrayLike, value: T, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { let j = (start + i) % n; if (array[j] === value) { return j; } } return -1; } /** * Find the index of the last occurrence of a value in an array. * * @param array - The array-like object to search. * * @param value - The value to locate in the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the last occurrence of the value, or `-1` * if the value is not found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four', 'one']; * ArrayExt.lastIndexOf(data, 'red'); // -1 * ArrayExt.lastIndexOf(data, 'one'); // 4 * ArrayExt.lastIndexOf(data, 'one', 1); // 0 * ArrayExt.lastIndexOf(data, 'two', 0); // -1 * ArrayExt.lastIndexOf(data, 'two', 0, 1); // 1 * ``` */ export function lastIndexOf( array: ArrayLike, value: T, start = -1, stop = 0 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (start < stop) { span = start + 1 + (n - stop); } else { span = start - stop + 1; } for (let i = 0; i < span; ++i) { let j = (start - i + n) % n; if (array[j] === value) { return j; } } return -1; } /** * Find the index of the first value which matches a predicate. * * @param array - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first matching value, or `-1` if no * matching value is found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findFirstIndex(data, isEven); // 1 * ArrayExt.findFirstIndex(data, isEven, 4); // 5 * ArrayExt.findFirstIndex(data, isEven, 6); // -1 * ArrayExt.findFirstIndex(data, isEven, 6, 5); // 1 * ``` */ export function findFirstIndex( array: ArrayLike, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { let j = (start + i) % n; if (fn(array[j], j)) { return j; } } return -1; } /** * Find the index of the last value which matches a predicate. * * @param object - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the last matching value, or `-1` if no * matching value is found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findLastIndex(data, isEven); // 5 * ArrayExt.findLastIndex(data, isEven, 4); // 3 * ArrayExt.findLastIndex(data, isEven, 0); // -1 * ArrayExt.findLastIndex(data, isEven, 0, 1); // 5 * ``` */ export function findLastIndex( array: ArrayLike, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let d: number; if (start < stop) { d = start + 1 + (n - stop); } else { d = start - stop + 1; } for (let i = 0; i < d; ++i) { let j = (start - i + n) % n; if (fn(array[j], j)) { return j; } } return -1; } /** * Find the first value which matches a predicate. * * @param array - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The first matching value, or `undefined` if no matching * value is found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findFirstValue(data, isEven); // 2 * ArrayExt.findFirstValue(data, isEven, 2); // 4 * ArrayExt.findFirstValue(data, isEven, 6); // undefined * ArrayExt.findFirstValue(data, isEven, 6, 5); // 2 * ``` */ export function findFirstValue( array: ArrayLike, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): T | undefined { let index = findFirstIndex(array, fn, start, stop); return index !== -1 ? array[index] : undefined; } /** * Find the last value which matches a predicate. * * @param object - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The last matching value, or `undefined` if no matching * value is found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findLastValue(data, isEven); // 2 * ArrayExt.findLastValue(data, isEven, 4); // 4 * ArrayExt.findLastValue(data, isEven, 0); // undefined * ArrayExt.findLastValue(data, isEven, 0, 1); // 2 * ``` */ export function findLastValue( array: ArrayLike, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): T | undefined { let index = findLastIndex(array, fn, start, stop); return index !== -1 ? array[index] : undefined; } /** * Find the index of the first element which compares `>=` to a value. * * @param array - The sorted array-like object to search. * * @param value - The value to locate in the array. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if an element is less than a value, `0` if * an element is equal to a value, or `> 0` if an element is greater * than a value. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first element which compares `>=` to the * value, or `length` if there is no such element. If the computed * index for `stop` is less than `start`, then the computed index * for `start` is returned. * * #### Notes * The array must already be sorted in ascending order according to * the comparison function. * * #### Complexity * Logarithmic. * * #### Undefined Behavior * Searching a range which is not sorted in ascending order. * * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.lowerBound(data, 0, numberCmp); // 0 * ArrayExt.lowerBound(data, 6, numberCmp); // 3 * ArrayExt.lowerBound(data, 7, numberCmp); // 3 * ArrayExt.lowerBound(data, -1, numberCmp); // 0 * ArrayExt.lowerBound(data, 10, numberCmp); // 6 * ``` */ export function lowerBound( array: ArrayLike, value: U, fn: (element: T, value: U) => number, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let begin = start; let span = stop - start + 1; while (span > 0) { let half = span >> 1; let middle = begin + half; if (fn(array[middle], value) < 0) { begin = middle + 1; span -= half + 1; } else { span = half; } } return begin; } /** * Find the index of the first element which compares `>` than a value. * * @param array - The sorted array-like object to search. * * @param value - The value to locate in the array. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if an element is less than a value, `0` if * an element is equal to a value, or `> 0` if an element is greater * than a value. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first element which compares `>` than the * value, or `length` if there is no such element. If the computed * index for `stop` is less than `start`, then the computed index * for `start` is returned. * * #### Notes * The array must already be sorted in ascending order according to * the comparison function. * * #### Complexity * Logarithmic. * * #### Undefined Behavior * Searching a range which is not sorted in ascending order. * * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.upperBound(data, 0, numberCmp); // 1 * ArrayExt.upperBound(data, 6, numberCmp); // 3 * ArrayExt.upperBound(data, 7, numberCmp); // 5 * ArrayExt.upperBound(data, -1, numberCmp); // 0 * ArrayExt.upperBound(data, 10, numberCmp); // 6 * ``` */ export function upperBound( array: ArrayLike, value: U, fn: (element: T, value: U) => number, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let begin = start; let span = stop - start + 1; while (span > 0) { let half = span >> 1; let middle = begin + half; if (fn(array[middle], value) > 0) { span = half; } else { begin = middle + 1; span -= half + 1; } } return begin; } /** * Test whether two arrays are shallowly equal. * * @param a - The first array-like object to compare. * * @param b - The second array-like object to compare. * * @param fn - The comparison function to apply to the elements. It * should return `true` if the elements are "equal". The default * compares elements using strict `===` equality. * * @returns Whether the two arrays are shallowly equal. * * #### Complexity * Linear. * * #### Undefined Behavior * Modifying the length of the arrays while comparing. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let d1 = [0, 3, 4, 7, 7, 9]; * let d2 = [0, 3, 4, 7, 7, 9]; * let d3 = [42]; * ArrayExt.shallowEqual(d1, d2); // true * ArrayExt.shallowEqual(d2, d3); // false * ``` */ export function shallowEqual( a: ArrayLike, b: ArrayLike, fn?: (a: T, b: T) => boolean ): boolean { // Check for object identity first. if (a === b) { return true; } // Bail early if the lengths are different. if (a.length !== b.length) { return false; } // Compare each element for equality. for (let i = 0, n = a.length; i < n; ++i) { if (fn ? !fn(a[i], b[i]) : a[i] !== b[i]) { return false; } } // The array are shallowly equal. return true; } /** * Create a slice of an array subject to an optional step. * * @param array - The array-like object of interest. * * @param options - The options for configuring the slice. * * @returns A new array with the specified values. * * @throws An exception if the slice `step` is `0`. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start`, `stop`, or `step` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.slice(data); // [0, 3, 4, 7, 7, 9] * ArrayExt.slice(data, { start: 2 }); // [4, 7, 7, 9] * ArrayExt.slice(data, { start: 0, stop: 4 }); // [0, 3, 4, 7] * ArrayExt.slice(data, { step: 2 }); // [0, 4, 7] * ArrayExt.slice(data, { step: -1 }); // [9, 7, 7, 4, 3, 0] * ``` */ export function slice( array: ArrayLike, options: slice.IOptions = {} ): T[] { // Extract the options. let { start, stop, step } = options; // Set up the `step` value. if (step === undefined) { step = 1; } // Validate the step size. if (step === 0) { throw new Error('Slice `step` cannot be zero.'); } // Look up the length of the array. let n = array.length; // Set up the `start` value. if (start === undefined) { start = step < 0 ? n - 1 : 0; } else if (start < 0) { start = Math.max(start + n, step < 0 ? -1 : 0); } else if (start >= n) { start = step < 0 ? n - 1 : n; } // Set up the `stop` value. if (stop === undefined) { stop = step < 0 ? -1 : n; } else if (stop < 0) { stop = Math.max(stop + n, step < 0 ? -1 : 0); } else if (stop >= n) { stop = step < 0 ? n - 1 : n; } // Compute the slice length. let length; if ((step < 0 && stop >= start) || (step > 0 && start >= stop)) { length = 0; } else if (step < 0) { length = Math.floor((stop - start + 1) / step + 1); } else { length = Math.floor((stop - start - 1) / step + 1); } // Compute the sliced result. let result: T[] = []; for (let i = 0; i < length; ++i) { result[i] = array[start + i * step]; } // Return the result. return result; } /** * The namespace for the `slice` function statics. */ export namespace slice { /** * The options for the `slice` function. */ export interface IOptions { /** * The starting index of the slice, inclusive. * * Negative values are taken as an offset from the end * of the array. * * The default is `0` if `step > 0` else `n - 1`. */ start?: number; /** * The stopping index of the slice, exclusive. * * Negative values are taken as an offset from the end * of the array. * * The default is `n` if `step > 0` else `-n - 1`. */ stop?: number; /** * The step value for the slice. * * This must not be `0`. * * The default is `1`. */ step?: number; } } /** * An array-like object which supports item assignment. */ export type MutableArrayLike = { readonly length: number; [index: number]: T; }; /** * Move an element in an array from one index to another. * * @param array - The mutable array-like object of interest. * * @param fromIndex - The index of the element to move. Negative * values are taken as an offset from the end of the array. * * @param toIndex - The target index of the element. Negative * values are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `fromIndex` or `toIndex` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.move(data, 1, 2); // [0, 2, 1, 3, 4] * ArrayExt.move(data, 4, 2); // [0, 2, 4, 1, 3] * ``` */ export function move( array: MutableArrayLike, fromIndex: number, toIndex: number ): void { let n = array.length; if (n <= 1) { return; } if (fromIndex < 0) { fromIndex = Math.max(0, fromIndex + n); } else { fromIndex = Math.min(fromIndex, n - 1); } if (toIndex < 0) { toIndex = Math.max(0, toIndex + n); } else { toIndex = Math.min(toIndex, n - 1); } if (fromIndex === toIndex) { return; } let value = array[fromIndex]; let d = fromIndex < toIndex ? 1 : -1; for (let i = fromIndex; i !== toIndex; i += d) { array[i] = array[i + d]; } array[toIndex] = value; } /** * Reverse an array in-place. * * @param array - The mutable array-like object of interest. * * @param start - The index of the first element in the range to be * reversed, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * reversed, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` index which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.reverse(data, 1, 3); // [0, 3, 2, 1, 4] * ArrayExt.reverse(data, 3); // [0, 3, 2, 4, 1] * ArrayExt.reverse(data); // [1, 4, 2, 3, 0] * ``` */ export function reverse( array: MutableArrayLike, start = 0, stop = -1 ): void { let n = array.length; if (n <= 1) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } while (start < stop) { let a = array[start]; let b = array[stop]; array[start++] = b; array[stop--] = a; } } /** * Rotate the elements of an array in-place. * * @param array - The mutable array-like object of interest. * * @param delta - The amount of rotation to apply to the elements. A * positive value will rotate the elements to the left. A negative * value will rotate the elements to the right. * * @param start - The index of the first element in the range to be * rotated, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * rotated, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `delta`, `start`, or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.rotate(data, 2); // [2, 3, 4, 0, 1] * ArrayExt.rotate(data, -2); // [0, 1, 2, 3, 4] * ArrayExt.rotate(data, 10); // [0, 1, 2, 3, 4] * ArrayExt.rotate(data, 9); // [4, 0, 1, 2, 3] * ArrayExt.rotate(data, 2, 1, 3); // [4, 2, 0, 1, 3] * ``` */ export function rotate( array: MutableArrayLike, delta: number, start = 0, stop = -1 ): void { let n = array.length; if (n <= 1) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } if (start >= stop) { return; } let length = stop - start + 1; if (delta > 0) { delta = delta % length; } else if (delta < 0) { delta = ((delta % length) + length) % length; } if (delta === 0) { return; } let pivot = start + delta; reverse(array, start, pivot - 1); reverse(array, pivot, stop); reverse(array, start, stop); } /** * Fill an array with a static value. * * @param array - The mutable array-like object to fill. * * @param value - The static value to use to fill the array. * * @param start - The index of the first element in the range to be * filled, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * filled, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Notes * If `stop < start` the fill will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four']; * ArrayExt.fill(data, 'r'); // ['r', 'r', 'r', 'r'] * ArrayExt.fill(data, 'g', 1); // ['r', 'g', 'g', 'g'] * ArrayExt.fill(data, 'b', 2, 3); // ['r', 'g', 'b', 'b'] * ArrayExt.fill(data, 'z', 3, 1); // ['z', 'z', 'b', 'z'] * ``` */ export function fill( array: MutableArrayLike, value: T, start = 0, stop = -1 ): void { let n = array.length; if (n === 0) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { array[(start + i) % n] = value; } } /** * Insert a value into an array at a specific index. * * @param array - The array of interest. * * @param index - The index at which to insert the value. Negative * values are taken as an offset from the end of the array. * * @param value - The value to set at the specified index. * * #### Complexity * Linear. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2]; * ArrayExt.insert(data, 0, -1); // [-1, 0, 1, 2] * ArrayExt.insert(data, 2, 12); // [-1, 0, 12, 1, 2] * ArrayExt.insert(data, -1, 7); // [-1, 0, 12, 1, 7, 2] * ArrayExt.insert(data, 6, 19); // [-1, 0, 12, 1, 7, 2, 19] * ``` */ export function insert(array: Array, index: number, value: T): void { let n = array.length; if (index < 0) { index = Math.max(0, index + n); } else { index = Math.min(index, n); } for (let i = n; i > index; --i) { array[i] = array[i - 1]; } array[index] = value; } /** * Remove and return a value at a specific index in an array. * * @param array - The array of interest. * * @param index - The index of the value to remove. Negative values * are taken as an offset from the end of the array. * * @returns The value at the specified index, or `undefined` if the * index is out of range. * * #### Complexity * Linear. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeAt(data, 2); // 23 * ArrayExt.removeAt(data, -2); // 12 * ArrayExt.removeAt(data, 10); // undefined; * ``` */ export function removeAt(array: Array, index: number): T | undefined { let n = array.length; if (index < 0) { index += n; } if (index < 0 || index >= n) { return undefined; } let value = array[index]; for (let i = index + 1; i < n; ++i) { array[i - 1] = array[i]; } array.length = n - 1; return value; } /** * Remove the first occurrence of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the removed value, or `-1` if the value * is not contained in the array. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeFirstOf(data, 12); // 1 * ArrayExt.removeFirstOf(data, 17); // -1 * ArrayExt.removeFirstOf(data, 39, 3); // -1 * ArrayExt.removeFirstOf(data, 39, 3, 2); // 2 * ``` */ export function removeFirstOf( array: Array, value: T, start = 0, stop = -1 ): number { let index = firstIndexOf(array, value, start, stop); if (index !== -1) { removeAt(array, index); } return index; } /** * Remove the last occurrence of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the removed value, or `-1` if the value * is not contained in the array. * * #### Notes * If `start < stop` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeLastOf(data, 12); // 5 * ArrayExt.removeLastOf(data, 17); // -1 * ArrayExt.removeLastOf(data, 39, 2); // -1 * ArrayExt.removeLastOf(data, 39, 2, 3); // 3 * ``` */ export function removeLastOf( array: Array, value: T, start = -1, stop = 0 ): number { let index = lastIndexOf(array, value, start, stop); if (index !== -1) { removeAt(array, index); } return index; } /** * Remove all occurrences of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The number of elements removed from the array. * * #### Notes * If `stop < start` the search will conceptually wrap at the end of * the array, however the array will be traversed front-to-back. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [14, 12, 23, 39, 14, 12, 19, 14]; * ArrayExt.removeAllOf(data, 12); // 2 * ArrayExt.removeAllOf(data, 17); // 0 * ArrayExt.removeAllOf(data, 14, 1, 4); // 1 * ``` */ export function removeAllOf( array: Array, value: T, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let count = 0; for (let i = 0; i < n; ++i) { if (start <= stop && i >= start && i <= stop && array[i] === value) { count++; } else if ( stop < start && (i <= stop || i >= start) && array[i] === value ) { count++; } else if (count > 0) { array[i - count] = array[i]; } } if (count > 0) { array.length = n - count; } return count; } /** * Remove the first occurrence of a value which matches a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The removed `{ index, value }`, which will be `-1` and * `undefined` if the value is not contained in the array. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeFirstWhere(data, isEven); // { index: 0, value: 0 } * ArrayExt.removeFirstWhere(data, isEven, 2); // { index: 3, value: 14 } * ArrayExt.removeFirstWhere(data, isEven, 4); // { index: -1, value: undefined } * ``` */ export function removeFirstWhere( array: Array, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): { index: number; value: T | undefined } { let value: T | undefined; let index = findFirstIndex(array, fn, start, stop); if (index !== -1) { value = removeAt(array, index); } return { index, value }; } /** * Remove the last occurrence of a value which matches a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The removed `{ index, value }`, which will be `-1` and * `undefined` if the value is not contained in the array. * * #### Notes * If `start < stop` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeLastWhere(data, isEven); // { index: 5, value: 12 } * ArrayExt.removeLastWhere(data, isEven, 2); // { index: 1, value: 12 } * ArrayExt.removeLastWhere(data, isEven, 2, 1); // { index: -1, value: undefined } * ``` */ export function removeLastWhere( array: Array, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): { index: number; value: T | undefined } { let value: T | undefined; let index = findLastIndex(array, fn, start, stop); if (index !== -1) { value = removeAt(array, index); } return { index, value }; } /** * Remove all occurrences of values which match a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The number of elements removed from the array. * * #### Notes * If `stop < start` the search will conceptually wrap at the end of * the array, however the array will be traversed front-to-back. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * function isNegative(value: number): boolean { * return value < 0; * } * * let data = [0, 12, -13, -9, 23, 39, 14, -15, 12, 75]; * ArrayExt.removeAllWhere(data, isEven); // 4 * ArrayExt.removeAllWhere(data, isNegative, 0, 3); // 2 * ``` */ export function removeAllWhere( array: Array, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let count = 0; for (let i = 0; i < n; ++i) { if (start <= stop && i >= start && i <= stop && fn(array[i], i)) { count++; } else if (stop < start && (i <= stop || i >= start) && fn(array[i], i)) { count++; } else if (count > 0) { array[i - count] = array[i]; } } if (count > 0) { array.length = n - count; } return count; } }