| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| export const removeElement = <T>(array: T[], element: T, firstOnly?: boolean): boolean => { |
| let index = array.indexOf(element); |
| if (index === -1) { |
| return false; |
| } |
| if (firstOnly) { |
| array.splice(index, 1); |
| return true; |
| } |
| for (let i = index + 1, n = array.length; i < n; ++i) { |
| if (array[i] !== element) { |
| array[index++] = array[i]; |
| } |
| } |
| array.length = index; |
| return true; |
| }; |
| |
| type NumberComparator = (a: number, b: number) => number; |
| |
| export function swap<T>(array: T[], i1: number, i2: number): void { |
| const temp = array[i1]; |
| array[i1] = array[i2]; |
| array[i2] = temp; |
| } |
| |
| function partition( |
| array: number[], comparator: NumberComparator, left: number, right: number, pivotIndex: number): number { |
| const pivotValue = array[pivotIndex]; |
| swap(array, right, pivotIndex); |
| let storeIndex = left; |
| for (let i = left; i < right; ++i) { |
| if (comparator(array[i], pivotValue) < 0) { |
| swap(array, storeIndex, i); |
| ++storeIndex; |
| } |
| } |
| swap(array, right, storeIndex); |
| return storeIndex; |
| } |
| |
| function quickSortRange( |
| array: number[], comparator: NumberComparator, left: number, right: number, sortWindowLeft: number, |
| sortWindowRight: number): void { |
| if (right <= left) { |
| return; |
| } |
| const pivotIndex = Math.floor(Math.random() * (right - left)) + left; |
| const pivotNewIndex = partition(array, comparator, left, right, pivotIndex); |
| if (sortWindowLeft < pivotNewIndex) { |
| quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowLeft, sortWindowRight); |
| } |
| if (pivotNewIndex < sortWindowRight) { |
| quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindowLeft, sortWindowRight); |
| } |
| } |
| |
| export function sortRange( |
| array: number[], comparator: NumberComparator, leftBound: number, rightBound: number, sortWindowLeft: number, |
| sortWindowRight: number): number[] { |
| if (leftBound === 0 && rightBound === (array.length - 1) && sortWindowLeft === 0 && sortWindowRight >= rightBound) { |
| array.sort(comparator); |
| } else { |
| quickSortRange(array, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight); |
| } |
| return array; |
| } |
| export const binaryIndexOf = <T, S>(array: T[], value: S, comparator: (a: S, b: T) => number): number => { |
| const index = lowerBound(array, value, comparator); |
| return index < array.length && comparator(value, array[index]) === 0 ? index : -1; |
| }; |
| |
| function mergeOrIntersect<T>( |
| array1: T[], array2: T[], comparator: (a: T, b: T) => number, mergeNotIntersect: boolean): T[] { |
| const result = []; |
| let i = 0; |
| let j = 0; |
| while (i < array1.length && j < array2.length) { |
| const compareValue = comparator(array1[i], array2[j]); |
| if (mergeNotIntersect || !compareValue) { |
| result.push(compareValue <= 0 ? array1[i] : array2[j]); |
| } |
| if (compareValue <= 0) { |
| i++; |
| } |
| if (compareValue >= 0) { |
| j++; |
| } |
| } |
| if (mergeNotIntersect) { |
| while (i < array1.length) { |
| result.push(array1[i++]); |
| } |
| while (j < array2.length) { |
| result.push(array2[j++]); |
| } |
| } |
| return result; |
| } |
| |
| export const intersectOrdered = <T>(array1: T[], array2: T[], comparator: (a: T, b: T) => number): T[] => { |
| return mergeOrIntersect(array1, array2, comparator, false); |
| }; |
| |
| export const mergeOrdered = <T>(array1: T[], array2: T[], comparator: (a: T, b: T) => number): T[] => { |
| return mergeOrIntersect(array1, array2, comparator, true); |
| }; |
| |
| export const DEFAULT_COMPARATOR = (a: string|number, b: string|number): -1|0|1 => { |
| return a < b ? -1 : (a > b ? 1 : 0); |
| }; |
| |
| /** |
| * Returns the index of the element closest to the needle that is equal to or |
| * greater than it. Assumes that the provided array is sorted. |
| * |
| * If no element is found, the right bound is returned. |
| * |
| * Uses the provided comparator function to determine if two items are equal or |
| * if one is greater than the other. If you are working with strings or |
| * numbers, you can use ArrayUtilities.DEFAULT_COMPARATOR. Otherwise, you |
| * should define one that takes the needle element and an element from the |
| * array and returns a positive or negative number to indicate which is greater |
| * than the other. |
| * |
| * When specified, |left| (inclusive) and |right| (exclusive) indices |
| * define the search window. |
| */ |
| export function lowerBound<T>( |
| array: Uint32Array|Int32Array, needle: T, comparator: (needle: T, b: number) => number, left?: number, |
| right?: number): number; |
| export function lowerBound<S, T>( |
| array: S[], needle: T, comparator: (needle: T, b: S) => number, left?: number, right?: number): number; |
| export function lowerBound<S, T>( |
| array: readonly S[], needle: T, comparator: (needle: T, b: S) => number, left?: number, right?: number): number; |
| export function lowerBound<S, T, A extends S[]>( |
| array: A, needle: T, comparator: (needle: T, b: S) => number, left?: number, right?: number): number { |
| let l = left || 0; |
| let r = right !== undefined ? right : array.length; |
| while (l < r) { |
| const m = (l + r) >> 1; |
| if (comparator(needle, array[m]) > 0) { |
| l = m + 1; |
| } else { |
| r = m; |
| } |
| } |
| return r; |
| } |
| |
| /** |
| * Returns the index of the element closest to the needle that is greater than |
| * it. Assumes that the provided array is sorted. |
| * |
| * If no element is found, the right bound is returned. |
| * |
| * Uses the provided comparator function to determine if two items are equal or |
| * if one is greater than the other. If you are working with strings or |
| * numbers, you can use ArrayUtilities.DEFAULT_COMPARATOR. Otherwise, you |
| * should define one that takes the needle element and an element from the |
| * array and returns a positive or negative number to indicate which is greater |
| * than the other. |
| * |
| * When specified, |left| (inclusive) and |right| (exclusive) indices |
| * define the search window. |
| */ |
| export function upperBound<T>( |
| array: Uint32Array, needle: T, comparator: (needle: T, b: number) => number, left?: number, right?: number): number; |
| export function upperBound<S, T>( |
| array: S[], needle: T, comparator: (needle: T, b: S) => number, left?: number, right?: number): number; |
| export function upperBound<S, T, A extends S[]>( |
| array: A, needle: T, comparator: (needle: T, b: S) => number, left?: number, right?: number): number { |
| let l = left || 0; |
| let r = right !== undefined ? right : array.length; |
| while (l < r) { |
| const m = (l + r) >> 1; |
| if (comparator(needle, array[m]) >= 0) { |
| l = m + 1; |
| } else { |
| r = m; |
| } |
| } |
| return r; |
| } |
| |
| const enum NearestSearchStart { |
| BEGINNING = 'BEGINNING', |
| END = 'END', |
| } |
| /** |
| * Obtains the first or last item in the array that satisfies the predicate function. |
| * So, for example, if the array were arr = [2, 4, 6, 8, 10], and you are looking for |
| * the last item arr[i] such that arr[i] < 5 you would be returned 1, because |
| * array[1] is 4, the last item in the array that satisfies the |
| * predicate function. |
| * |
| * If instead you were looking for the first item in the same array that satisfies |
| * arr[i] > 5 you would be returned 2 because array[2] = 6. |
| * |
| * Please note: this presupposes that the array is already ordered. |
| * This function uses a variation of Binary Search. |
| */ |
| function nearestIndex<T>( |
| arr: readonly T[], predicate: (arrayItem: T) => boolean, searchStart: NearestSearchStart): number|null { |
| const searchFromEnd = searchStart === NearestSearchStart.END; |
| if (arr.length === 0) { |
| return null; |
| } |
| |
| let left = 0; |
| let right = arr.length - 1; |
| let pivot = 0; |
| let matchesPredicate = false; |
| let moveToTheRight = false; |
| let middle = 0; |
| do { |
| middle = left + (right - left) / 2; |
| pivot = searchFromEnd ? Math.ceil(middle) : Math.floor(middle); |
| matchesPredicate = predicate(arr[pivot]); |
| moveToTheRight = matchesPredicate === searchFromEnd; |
| if (moveToTheRight) { |
| left = Math.min(right, pivot + (left === pivot ? 1 : 0)); |
| } else { |
| right = Math.max(left, pivot + (right === pivot ? -1 : 0)); |
| } |
| } while (right !== left); |
| |
| // Special-case: the indexed item doesn't pass the predicate. This |
| // occurs when none of the items in the array are a match for the |
| // predicate. |
| if (!predicate(arr[left])) { |
| return null; |
| } |
| return left; |
| } |
| |
| /** |
| * Obtains the first item in the array that satisfies the predicate function. |
| * So, for example, if the array was arr = [2, 4, 6, 8, 10], and you are looking for |
| * the first item arr[i] such that arr[i] > 5 you would be returned 2, because |
| * array[2] is 6, the first item in the array that satisfies the |
| * predicate function. |
| * |
| * Please note: this presupposes that the array is already ordered. |
| */ |
| export function nearestIndexFromBeginning<T>(arr: T[], predicate: (arrayItem: T) => boolean): number|null { |
| return nearestIndex(arr, predicate, NearestSearchStart.BEGINNING); |
| } |
| |
| /** |
| * Obtains the last item in the array that satisfies the predicate function. |
| * So, for example, if the array was arr = [2, 4, 6, 8, 10], and you are looking for |
| * the last item arr[i] such that arr[i] < 5 you would be returned 1, because |
| * arr[1] is 4, the last item in the array that satisfies the |
| * predicate function. |
| * |
| * Please note: this presupposes that the array is already ordered. |
| */ |
| |
| export function nearestIndexFromEnd<T>(arr: readonly T[], predicate: (arrayItem: T) => boolean): number|null { |
| return nearestIndex(arr, predicate, NearestSearchStart.END); |
| } |
| |
| /** Type guard for ensuring that `arr` does not contain null or undefined **/ |
| export function arrayDoesNotContainNullOrUndefined<T>(arr: Array<T|null|undefined>): arr is T[] { |
| return !arr.includes(null) && !arr.includes(undefined); |
| } |
| |
| export function assertArrayIsSorted<T>(arr: readonly T[], compareFn?: (a: T, b: T) => number): void { |
| const comparator = compareFn || (DEFAULT_COMPARATOR as unknown as (a: T, b: T) => number); |
| |
| for (let i = 0; i < arr.length - 1; i++) { |
| const current = arr[i]; |
| const next = arr[i + 1]; |
| |
| if (comparator(current, next) > 0) { |
| throw new Error(`Array is not sorted at index ${i}: ${JSON.stringify(current)} > ${JSON.stringify(next)}`); |
| } |
| } |
| } |