|
1 | 1 | import {Nullable} from "../typings/common";
|
| 2 | +import {clamp} from "./math"; |
2 | 3 |
|
3 | 4 |
|
| 5 | +/** |
| 6 | + * Checks if `target` is bounded by the first and last value in a sorted array of numbers. |
| 7 | + * |
| 8 | + * @param data An array sorted in ascending order. |
| 9 | + * @param target |
| 10 | + * @return Whether `target` is within the bounds of the array's values. |
| 11 | + */ |
| 12 | +const isWithinBounds = (data: number[], target: number): boolean => { |
| 13 | + const {length} = data; |
| 14 | + if (0 === length) { |
| 15 | + return false; |
| 16 | + } |
| 17 | + |
| 18 | + return (target >= (data[0] as number)) && (target <= (data[length - 1] as number)); |
| 19 | +}; |
| 20 | + |
| 21 | +/** |
| 22 | + * Clamps a number using the first and last value in a sorted array of numbers. |
| 23 | + * |
| 24 | + * @param data An array sorted in ascending order. |
| 25 | + * @param num The number to be clamped. |
| 26 | + * @return The clamped number. |
| 27 | + */ |
| 28 | +const clampWithinBounds = (data: number[], num: number): number => { |
| 29 | + const {length} = data; |
| 30 | + if (0 === length) { |
| 31 | + return num; |
| 32 | + } |
| 33 | + |
| 34 | + return clamp(num, data[0] as number, data[length - 1] as number); |
| 35 | +}; |
| 36 | + |
| 37 | +/** |
| 38 | + * Performs binary search to find the smallest index `i` in the range `[0, length)` where |
| 39 | + * `predicate` is true. `predicate` should be false for some prefix of the input range and true |
| 40 | + * for the remainder. |
| 41 | + * |
| 42 | + * @param length The length of the range to search. |
| 43 | + * @param predicate A function that takes an index and returns `true` or `false`. |
| 44 | + * @return The smallest index where `predicate(i)` is true, or `length` if no such index exists. |
| 45 | + * @example |
| 46 | + * const arr = [1, 3, 5, 7, 10, 15, 20]; |
| 47 | + * const result = binarySearch(arr.length, (i) => arr[i] >= 10); |
| 48 | + * console.log(result); // Output: 4 (since arr[4] is 10). |
| 49 | + */ |
| 50 | +const binarySearch = (length: number, predicate: (index: number) => boolean): number => { |
| 51 | + // Generic implementation based on Go standard library implementation. |
| 52 | + // Reference: https://pkg.go.dev/sort#Search |
| 53 | + let i = 0; |
| 54 | + let j = length; |
| 55 | + while (i < j) { |
| 56 | + const mid = Math.floor((i + j) / 2); |
| 57 | + if (false === predicate(mid)) { |
| 58 | + i = mid + 1; |
| 59 | + } else { |
| 60 | + j = mid; |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + return i; |
| 65 | +}; |
| 66 | + |
| 67 | +/** |
| 68 | + * Finds the largest index `i` in a sorted array `data` such that `data[i] <= target`. Uses binary |
| 69 | + * search for efficiency. |
| 70 | + * |
| 71 | + * @param data An array sorted in ascending order. |
| 72 | + * @param target |
| 73 | + * @return The largest index where `data[i] <= target` or: |
| 74 | + * - `length` if no such index exists. |
| 75 | + * - `null` if array is empty. |
| 76 | + * @example |
| 77 | + * const arr = [2, 3, 5, 7, 10, 15, 20]; |
| 78 | + * const result = findNearestLessThanOrEqualElement(arr, 8); |
| 79 | + * console.log(result); // Output: 3 (since arr[3] is 7). |
| 80 | + */ |
| 81 | +const findNearestLessThanOrEqualElement = (data: number[], target: number): Nullable<number> => { |
| 82 | + const {length} = data; |
| 83 | + |
| 84 | + if (0 === length) { |
| 85 | + return null; |
| 86 | + } |
| 87 | + |
| 88 | + // Binary search to find the first index where data[i] > target. |
| 89 | + const firstGreaterIdx: number = binarySearch(length, (i) => data[i] as number > target); |
| 90 | + |
| 91 | + if (0 === firstGreaterIdx) { |
| 92 | + return length; |
| 93 | + } |
| 94 | + |
| 95 | + return firstGreaterIdx - 1; |
| 96 | +}; |
| 97 | + |
4 | 98 | /**
|
5 | 99 | * Finds the key in a map based on the provided value.
|
6 | 100 | *
|
@@ -38,7 +132,11 @@ const getMapValueWithNearestLessThanOrEqualKey = <T>(
|
38 | 132 | map.get(lowerBoundKey) as T;
|
39 | 133 | };
|
40 | 134 |
|
| 135 | + |
41 | 136 | export {
|
| 137 | + clampWithinBounds, |
| 138 | + findNearestLessThanOrEqualElement, |
42 | 139 | getMapKeyByValue,
|
43 | 140 | getMapValueWithNearestLessThanOrEqualKey,
|
| 141 | + isWithinBounds, |
44 | 142 | };
|
0 commit comments