diff --git a/new-log-viewer/src/utils/data.ts b/new-log-viewer/src/utils/data.ts index 25096fe2..34ba3e81 100644 --- a/new-log-viewer/src/utils/data.ts +++ b/new-log-viewer/src/utils/data.ts @@ -1,6 +1,100 @@ import {Nullable} from "../typings/common"; +import {clamp} from "./math"; +/** + * Checks if `target` is bounded by the first and last value in a sorted array of numbers. + * + * @param data An array sorted in ascending order. + * @param target + * @return Whether `target` is within the bounds of the array's values. + */ +const isWithinBounds = (data: number[], target: number): boolean => { + const {length} = data; + if (0 === length) { + return false; + } + + return (target >= (data[0] as number)) && (target <= (data[length - 1] as number)); +}; + +/** + * Clamps a number using the first and last value in a sorted array of numbers. + * + * @param data An array sorted in ascending order. + * @param num The number to be clamped. + * @return The clamped number. + */ +const clampWithinBounds = (data: number[], num: number): number => { + const {length} = data; + if (0 === length) { + return num; + } + + return clamp(num, data[0] as number, data[length - 1] as number); +}; + +/** + * Performs binary search to find the smallest index `i` in the range `[0, length)` where + * `predicate` is true. `predicate` should be false for some prefix of the input range and true + * for the remainder. + * + * @param length The length of the range to search. + * @param predicate A function that takes an index and returns `true` or `false`. + * @return The smallest index where `predicate(i)` is true, or `length` if no such index exists. + * @example + * const arr = [1, 3, 5, 7, 10, 15, 20]; + * const result = binarySearch(arr.length, (i) => arr[i] >= 10); + * console.log(result); // Output: 4 (since arr[4] is 10). + */ +const binarySearch = (length: number, predicate: (index: number) => boolean): number => { + // Generic implementation based on Go standard library implementation. + // Reference: https://pkg.go.dev/sort#Search + let i = 0; + let j = length; + while (i < j) { + const mid = Math.floor((i + j) / 2); + if (false === predicate(mid)) { + i = mid + 1; + } else { + j = mid; + } + } + + return i; +}; + +/** + * Finds the largest index `i` in a sorted array `data` such that `data[i] <= target`. Uses binary + * search for efficiency. + * + * @param data An array sorted in ascending order. + * @param target + * @return The largest index where `data[i] <= target` or: + * - `length` if no such index exists. + * - `null` if array is empty. + * @example + * const arr = [2, 3, 5, 7, 10, 15, 20]; + * const result = findNearestLessThanOrEqualElement(arr, 8); + * console.log(result); // Output: 3 (since arr[3] is 7). + */ +const findNearestLessThanOrEqualElement = (data: number[], target: number): Nullable => { + const {length} = data; + + if (0 === length) { + return null; + } + + // Binary search to find the first index where data[i] > target. + const firstGreaterIdx: number = binarySearch(length, (i) => data[i] as number > target); + + if (0 === firstGreaterIdx) { + return length; + } + + return firstGreaterIdx - 1; +}; + /** * Finds the key in a map based on the provided value. * @@ -38,7 +132,11 @@ const getMapValueWithNearestLessThanOrEqualKey = ( map.get(lowerBoundKey) as T; }; + export { + clampWithinBounds, + findNearestLessThanOrEqualElement, getMapKeyByValue, getMapValueWithNearestLessThanOrEqualKey, + isWithinBounds, };