Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new-log-viewer: Add bounds checking and search methods to support log filtering around a specific log event. #81

Merged
merged 16 commits into from
Oct 3, 2024
84 changes: 84 additions & 0 deletions new-log-viewer/src/utils/data.ts
davemarco marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,87 @@
import {Nullable} from "../typings/common";


/**
* Checks if 'x' is bounded by the first and last value in a sorted array of numbers.
*
* @param data A number array sorted in ascending order.
* @param x Target value.
* @return `true` if is `x` is within bounds; `false` otherwise or when the array is empty.
*/
const isWithinBounds = (data: number[], x: number): boolean => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: personally I prefer range in the function name (i.e. isWithinRange()) over bounds as in we have methods named decode_range() or similar elsewhere lol. not a strong preference though

Copy link
Contributor Author

@davemarco davemarco Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose bounds to distinguish from range. Felt like range is used for the indexes elsewhere, but here we are referring to the values of the array. Let me know if you still prefer range. I don't have strong preference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha. let's keep bounds then

const {length} = data;
if (0 === length) {
return false;
}

return (x >= (data[0] as number)) && (x <= (data[length - 1] as number));
};

/**
* Performs binary search to find the smallest index `i` in the range [0, length) where the
* `conditionFn` is true. Assumes that the `conditionFn` is false for some prefix of the
* input range and true for the remainder. The most common use is find the index `i` of
* a value x in a sorted array.
*
* @param length The length of the range to search.
* @param conditionFn A function that takes an index and returns `true` or `false`.
* @return The smallest index where `conditionFn(i)` is true. If no such index exists, returns
* `length`.
davemarco marked this conversation as resolved.
Show resolved Hide resolved
* @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, conditionFn: (index: number) => boolean): number => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename conditionFn to binaryPredicate? I think that's usually what it's called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to predicate

// Generic implementation based on Go standard library implementation.
// Reference: https://pkg.go.dev/sort#Search
let i = 0;
let j = length;

davemarco marked this conversation as resolved.
Show resolved Hide resolved
while (i < j) {
const mid = Math.floor((i + j) / 2);

davemarco marked this conversation as resolved.
Show resolved Hide resolved
if (false === conditionFn(mid)) {
i = mid + 1;
} else {
j = mid;
}
}

return i;
};

/**
* Finds the largest index `i` in a sorted array `data` such that `data[i] <= x`.
* Uses binary search for efficiency. Returns 0 if `x` is less than `data[0]`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Uses binary search for efficiency. Returns 0 if `x` is less than `data[0]`.
* Uses binary search for efficiency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to leave this in. The comment is here as this is not normal behaviour for binary search. Would normally just return length for not found, but this is custom use case.

Copy link
Member

@junhaoliao junhaoliao Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I got that the fallback value is worth documenting. I meant to say we want to follow this rule: "The description should be omitted if the return value description would repeat the description."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Okay makes i remove.

*
* @param data A number array sorted in ascending order.
davemarco marked this conversation as resolved.
Show resolved Hide resolved
* @param x Target value.
* @return The largest index where `data[i] <= x`. There are 2 edge cases where returns:
* - 0 if `x` is less than `data[0]`.
* - `null` if array is empty.
* @example
* const arr = [2, 3, 5, 7, 10, 15, 20];
* const result = findLargestIdxLte(arr, 8);
* console.log(result); // Output: 3 (since arr[3] is 7).
*/
const getLargestIdxLte = (data: number[], x: number): Nullable<number> => {
const {length} = data;

if (0 === length) {
return null;
}

// Binary search to find the first index where data[i] > x.
const firstGreaterIdx: number = binarySearch(length, (i) => data[i] as number > x);

if (0 === firstGreaterIdx) {
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return 0;
// `target` < `data[0]` so the first nearest element is `data[0]`.
return 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see review comment

}

return firstGreaterIdx - 1;
};

/**
* Finds the key in a map based on the provided value.
*
Expand Down Expand Up @@ -38,7 +119,10 @@ const getMapValueWithNearestLessThanOrEqualKey = <T>(
map.get(lowerBoundKey) as T;
};


export {
getLargestIdxLte,
getMapKeyByValue,
getMapValueWithNearestLessThanOrEqualKey,
isWithinBounds,
};
Loading