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

Conversation

davemarco
Copy link
Contributor

@davemarco davemarco commented Sep 29, 2024

References

new-log-viewer series: #45 #46 #48 #51 #52 #53 #54 #55 #56 #59 #60 #61 #62 #63 #66 #67 #68 #69 #70 #71 #72 #73 #74 #76

Description

The new event num cursor in #76 allows the front-end to request a page from the back-end using a log event number. When log events are filtered, the back-end must be able to search for that log event number in the filtered log event map. Specifically, the back-end response must return the closest existing log event in the map, and all the events on the page with the closest log event.

For example consider the following filtered log event map (log event num are values), and a user setting of 3 events per page (simplified for example).
[2, 6, 8, 22, 123, 186].

If the front-end requests log-event number 146. The back end should be able to find and return closest log event (123), as well as send back decoded events 22, 123, 186 (i.e. the second page).

This PR allows the back-end to find the index of the closest log event in the filtered map with binary search methods.

Where methods will be used

The new search utils file exports two similar methods. The implement binary search to find the largest less than or equal index ("the closest log event") and provide slightly different behaviour for edge cases.

findLargestIdxLte is useful for the example above, where we want to find the closest log event in the back-end. If the value requested is outside the range, for example, say the user requested event 10,000 in the example above, it would just return 186.

strictFindLargestIdxLte is useful for the front-end to avoid unnecessary event num cursor requests. Say the user inputs a new log event number, this method can be used to find a close log event on the page (front-end stores all the log events on the current page). If there is a close log event, it will return an index, and the back-end request is never sent. It is strict in that if the log event is not on the page, it will let the caller know by retuning null, and a request can be sent.

Note this methods are just implemented and they are not called anywhere yet.

Validation performed

Tested basic functionality.

Summary by CodeRabbit

  • New Features
    • Introduced new functions for enhanced data retrieval in sorted arrays, including checks for value bounds and finding the largest index less than or equal to a specified value.
  • Documentation
    • Comprehensive documentation provided for new functions, detailing parameters, return values, and usage examples.

Copy link

coderabbitai bot commented Sep 29, 2024

Walkthrough

The changes introduce new functions and modifications to the export statements in two files: data.ts and search.ts. In data.ts, five functions are added: isWithinBounds, clampWithinBounds, findNearestLessThanOrEqualElement, binarySearch, and getLargestIdxLte, which handle various operations on sorted arrays. In search.ts, three functions are added: binarySearch, findLargestIdxLte, and strictFindLargestIdxLte, which also focus on binary search operations. All new functions are documented and exported for use in other modules.

Changes

File Change Summary
new-log-viewer/src/utils/data.ts Added five functions: isWithinBounds, clampWithinBounds, findNearestLessThanOrEqualElement, binarySearch, and getLargestIdxLte.
Updated export statement to include new functions.
new-log-viewer/src/utils/search.ts Added three functions: binarySearch, findLargestIdxLte, and strictFindLargestIdxLte.
Exported findLargestIdxLte and strictFindLargestIdxLte.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DataUtils
    participant SearchUtils

    User->>DataUtils: Call isWithinBounds(data, x)
    DataUtils-->>User: Return true/false

    User->>DataUtils: Call clampWithinBounds(data, num)
    DataUtils-->>User: Return clamped value

    User->>SearchUtils: Call findLargestIdxLte(data, x)
    SearchUtils->>SearchUtils: Perform binarySearch
    SearchUtils-->>User: Return largest index i where data[i] <= x

    User->>SearchUtils: Call strictFindLargestIdxLte(data, x)
    SearchUtils->>SearchUtils: Check bounds
    SearchUtils->>SearchUtils: Perform binarySearch
    SearchUtils-->>User: Return largest index i or null
Loading

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
new-log-viewer/src/utils/search.ts (2)

59-59: Unnecessary type assertion with as number

In line 59, the type assertion as number is unnecessary because data is already an array of numbers. Removing the type assertion simplifies the code without affecting functionality.

Suggested change:

-    const firstGreaterIdx: number = binarySearch(n, (i) => data[i] as number > x);
+    const firstGreaterIdx: number = binarySearch(n, (i) => data[i] > x);

94-95: Unnecessary type assertion with as number

Similar to the previous function, in line 94, the type assertion as number is unnecessary. Removing it enhances code readability.

Suggested change:

     const firstGreaterIdx: number = binarySearch(n, (i) => data[i] as number > x);

Modify to:

     const firstGreaterIdx: number = binarySearch(n, (i) => data[i] > x);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d185624 and e07805b.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/search.ts (1 hunks)
🔇 Additional comments (1)
new-log-viewer/src/utils/search.ts (1)

18-35: Well-implemented binarySearch function

The binarySearch function is correctly implemented with clear documentation and example usage. It adheres to best practices and provides a generic utility for binary search operations.

new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
Copy link
Member

@junhaoliao junhaoliao left a comment

Choose a reason for hiding this comment

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

Thanks for the changes. I posted some questions as the purpose of strictFindLargestIdxLte seems a bit unclear to me.

Copy link
Member

Choose a reason for hiding this comment

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

Nit: shall we place those util functions into utils/data.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure i'll move them

new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
* const result = strictFindLargestIdxLte(arr, 8);
* console.log(result); // Output: 3 (since arr[3] is 7).
*/
const strictFindLargestIdxLte = (data: number[], x: number): Nullable<number> => {
Copy link
Member

Choose a reason for hiding this comment

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

I tried reading the PR description but can't understand the purpose of this function. Would you mind helping me to understand it a bit?

Say we are performing binary search in an array, it's with the assumption that the numbers are strictly incremental. If we want to make sure the desired number x is within the range of data[0] and data[data.length-1], why don't we do the comparisons before we kick off the binary search? i.e. Why are we not coming up with a withinRange(data: number[], x: number) function instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure that might make more sense lol. I added a new functionisWithinBounds = (data: number[], x: number): boolean and removed strictFindLargestIdxLte

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
new-log-viewer/src/utils/search.ts (2)

4-35: LGTM: Well-implemented and documented binary search function.

The binarySearch function is correctly implemented and efficiently finds the smallest index where the condition function returns true. The documentation is comprehensive and includes a helpful example.

Consider exporting this function alongside the others, as it could be useful in other parts of the application. This would promote code reuse and avoid duplication.

To export the function, modify the export statement at the end of the file:

 export {
+    binarySearch,
     findLargestIdxLte,
     strictFindLargestIdxLte,
 };

108-111: LGTM: Exports are correct, consider adding binarySearch.

The export statement correctly includes the main functions findLargestIdxLte and strictFindLargestIdxLte. As mentioned earlier, consider also exporting binarySearch for potential reuse in other parts of the application.

To include binarySearch in the exports, modify the export statement:

 export {
+    binarySearch,
     findLargestIdxLte,
     strictFindLargestIdxLte,
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e07805b and 8cbe208.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/search.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: lint-check
new-log-viewer/src/utils/search.ts

[failure] 52-52:
'length' is assigned a value but never used


[failure] 59-59:
Unsafe argument of type any assigned to a parameter of type number

🔇 Additional comments (1)
new-log-viewer/src/utils/search.ts (1)

1-1: LGTM: Import statement is appropriate.

The import of Nullable from "../typings/common" is correct and necessary for the function signatures in this file.

new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/search.ts Outdated Show resolved Hide resolved
@davemarco davemarco requested a review from junhaoliao October 2, 2024 01:18
junhaoliao
junhaoliao previously approved these changes Oct 2, 2024
Copy link
Member

@junhaoliao junhaoliao left a comment

Choose a reason for hiding this comment

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

lgtm. @kirkrodrigues could you check if the docs make sense to you? Feel free to modify my suggestions.

* @param x Target value.
* @return True if is `x` is within bounds and false if outside of bounds or 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 result = findLargestIdxLte(arr, 8);
* console.log(result); // Output: 3 (since arr[3] is 7).
*/
const findLargestIdxLte = (data: number[], x: number): Nullable<number> => {
Copy link
Member

Choose a reason for hiding this comment

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

For consistency I personally prefer get (i.e., getLargestIdxLte) over find.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can do

new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved

/**
* 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
new-log-viewer/src/utils/data.ts (3)

20-52: LGTM! Well-implemented generic binary search function.

The binarySearch function is correctly implemented based on Go's standard library, which is a reliable source. The generic implementation allows for versatile use in various binary search scenarios.

One minor suggestion to improve the example in the JSDoc:

Consider modifying the example to showcase the generic nature of the function:

/**
 * @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, the first element >= 10)
 * 
 * // Example with a custom condition
 * const firstEven = binarySearch(arr.length, (i) => arr[i] % 2 === 0);
 * console.log(firstEven); // Output: 4 (since 10 is the first even number in the array)
 */

This addition demonstrates the function's flexibility with different condition functions.


54-83: LGTM! Efficient implementation using binary search.

The getLargestIdxLte function is well-implemented, making efficient use of the binarySearch function. It correctly handles all edge cases and is well-documented.

One minor suggestion to improve the example in the JSDoc:

Consider modifying the example to showcase more scenarios:

/**
 * @example
 * const arr = [2, 3, 5, 7, 10, 15, 20];
 * console.log(getLargestIdxLte(arr, 8));  // Output: 3 (since arr[3] is 7)
 * console.log(getLargestIdxLte(arr, 2));  // Output: 0 (since arr[0] is 2)
 * console.log(getLargestIdxLte(arr, 1));  // Output: 0 (edge case: x < arr[0])
 * console.log(getLargestIdxLte(arr, 20)); // Output: 6 (since arr[6] is 20)
 * console.log(getLargestIdxLte([], 5));   // Output: null (edge case: empty array)
 */

This addition demonstrates the function's behaviour in various scenarios, including edge cases.


122-127: Consider reordering exports alphabetically.

The export statement has been correctly updated to include the new functions. However, for consistency and ease of maintenance, consider reordering the exports alphabetically.

Here's the suggested order:

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

This alphabetical ordering makes it easier to locate specific exports, especially as the list grows.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8cbe208 and b3f890f.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/data.ts (2 hunks)
🔇 Additional comments (1)
new-log-viewer/src/utils/data.ts (1)

4-18: LGTM! Function implementation and documentation are well-done.

The isWithinBounds function is correctly implemented and well-documented. The logic handles the empty array case and correctly checks if the value is within bounds.

Regarding the past comment about using 'range' instead of 'bounds' in the function name, I agree with the current choice of 'bounds'. It clearly distinguishes this function from others that might deal with index ranges, making the code more self-explanatory.

new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
Comment on lines 60 to 62
* @return The largest index where `data[i] <= target`. There are 2 edge cases where returns:
* - 0 if `target` is less than `data[0]`.
* - `null` if array is empty.
Copy link
Member

Choose a reason for hiding this comment

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

I find the 0 return case confusing:

If we're looking for i such that data[i] <= target, then

  1. if target is not in the bounds of data, we return 0.
  2. if data[0] <= target, we also return 0.

Since these two cases are indistinguishable, I feel like we should instead return length?

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.

Explained in person. Maybe we can call the function findNearestIndexLessThanOrEqual or findNearestIndex. I feel like nearest is more vague, so the edge case makes more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is also sort of inline with getMapValueWithNearestLessThanOrEqualKey later in the file

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 think we can fix this by clamping the value by the bounds of the array before calling the function. I know you already rewrote the docs... Let me try this, and ill request your review

new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
* 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

new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
new-log-viewer/src/utils/data.ts Outdated Show resolved Hide resolved
* const result = getLargestIdxLte(arr, 8);
* console.log(result); // Output: 3 (since arr[3] is 7).
*/
const getLargestIdxLte = (data: number[], target: number): Nullable<number> => {
Copy link
Member

Choose a reason for hiding this comment

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

This name is a bit confusing to me. How about findLessThanOrEqualElement? (I know what Lte means but it could be misinterpreted as LTE, lol.)

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.

How about findNearestIndexLessThanOrEqual or findNearestIndex

Copy link
Member

Choose a reason for hiding this comment

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

How about findFirstNearestElement?

  • "first nearest" is an abstract concept so hopefully users of the function will read the docstring before using it.
  • I think using "element" instead of "index" is a bit more intuitive since we really care about the element; it's just that the function indicates the found element by returning an index.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
new-log-viewer/src/utils/data.ts (3)

4-18: LGTM! Consider using non-null assertion for improved type safety.

The implementation of isWithinBounds looks good. The function correctly handles the empty array case and efficiently checks if the target is within bounds.

A minor suggestion for improvement:

Consider using non-null assertion operator (!) instead of type assertion (as number) for improved type safety:

-    return (target >= (data[0] as number)) && (target <= (data[length - 1] as number));
+    return (target >= data[0]!) && (target <= data[length - 1]!);

This change assumes that data is non-empty, which is guaranteed by the previous check.


20-48: LGTM! Consider renaming conditionFn to binaryPredicate.

The implementation of binarySearch is correct and efficient. The documentation is clear and the included example is helpful.

A minor suggestion for improvement:

Consider renaming the conditionFn parameter to binaryPredicate as suggested in a previous review comment. This name is more commonly used in the context of binary search algorithms:

-const binarySearch = (length: number, conditionFn: (index: number) => boolean): number => {
+const binarySearch = (length: number, binaryPredicate: (index: number) => boolean): number => {
     // ... (update usage inside the function)

This change would improve consistency with common terminology in computer science.


50-79: LGTM! Consider a more descriptive function name.

The implementation of getLargestIdxLte is correct and efficient. It properly handles edge cases and uses the binarySearch function effectively.

A suggestion for improvement:

Consider renaming the function to make its purpose more immediately clear. As suggested in a previous review comment, a name like findLessThanOrEqualElement might be more descriptive:

-const getLargestIdxLte = (data: number[], target: number): Nullable<number> => {
+const findLessThanOrEqualElement = (data: number[], target: number): Nullable<number> => {
     // ... (function body remains the same)

This change would make the function's purpose more self-explanatory, potentially improving code readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b3f890f and 6f33822.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/data.ts (2 hunks)
🔇 Additional comments (2)
new-log-viewer/src/utils/data.ts (2)

118-123: LGTM! Export statement is correct and well-organized.

The export statement has been properly updated to include the new functions. The alphabetical ordering of the exported functions enhances readability and makes it easier to locate specific exports.


Line range hint 1-124: Overall, excellent implementation aligning with PR objectives.

The new binary search methods introduced in this file provide a solid foundation for efficiently locating log events in a filtered log event map. The implementations are correct, well-documented, and include helpful examples. These changes align well with the PR objectives of supporting future log filtering capabilities.

The suggested minor improvements in naming and type assertions will further enhance the code's readability and type safety. Great job on this implementation!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
new-log-viewer/src/utils/data.ts (3)

4-18: LGTM! Consider removing unnecessary type assertions.

The function logic is correct, and the implementation addresses the previous review comments. However, you might want to consider removing the unnecessary type assertions as number if the data array is properly typed as number[].

Here's a suggested improvement:

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

20-48: LGTM! Consider using a more idiomatic comparison.

The binary search implementation looks correct and efficient. The documentation is clear and includes a helpful example. Great job on making it generic and reusable!

For improved readability, consider changing the comparison in the if statement to a more idiomatic form:

-        if (false === predicate(mid)) {
+        if (!predicate(mid)) {

This change makes the code more concise and easier to read at a glance.


50-79: LGTM! Consider using a more descriptive function name.

The implementation looks correct and efficient, making good use of the binarySearch function. The documentation is clear and includes a helpful example.

While the function name has been updated based on previous comments, consider using a more descriptive name that avoids abbreviations:

-const getLargestIdxLte = (data: number[], target: number): Nullable<number> => {
+const getLargestIndexLessThanOrEqual = (data: number[], target: number): Nullable<number> => {

This change makes the function name more self-explanatory, which can improve code readability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6f33822 and 6794146.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/data.ts (2 hunks)
🔇 Additional comments (2)
new-log-viewer/src/utils/data.ts (2)

118-123: LGTM! Export statement is correct and well-organized.

The export statement has been properly updated to include all the necessary functions, including the newly added ones. The alphabetical ordering of the exports is a nice touch for consistency and readability.


Line range hint 1-124: Overall, excellent implementation of binary search utilities.

The new functions (isWithinBounds, binarySearch, and getLargestIdxLte) are well-implemented and documented. They align perfectly with the PR objectives of introducing binary search methods to support future log filtering. The code is efficient, readable, and includes helpful examples in the documentation.

A few minor suggestions have been made to further improve code clarity and consistency:

  1. Remove unnecessary type assertions in isWithinBounds.
  2. Use a more idiomatic comparison in binarySearch.
  3. Consider a more descriptive name for getLargestIdxLte.

These changes are not critical but could enhance the overall quality of the code. Great job on implementing these utility functions!

Comment on lines +51 to +52
* Finds the largest index `i` in a sorted array `data` such that `data[i] <= target`. Uses binary
* search for efficiency.
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
* Finds the largest index `i` in a sorted array `data` such that `data[i] <= target`. Uses binary
* search for efficiency.
* Finds the index `i` in a sorted array `data` such that `data[i]` is the "first nearest" element
* to `target`, where "first nearest" is defined as follows:
* - If `target` is in or greater than the range of `data`'s elements, then `i` is the largest index
* such that `data[i] <= target`.
* - Otherwise, `target` is less than the range of `data`'s elements, so `i` is 0 since `data[0]` is
* the array element closest to `target`.

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

Comment on lines 56 to 58
* @return The largest index where `data[i] <= target` or:
* - 0 if `target` is less than `data[0]`.
* - `null` if array is empty.
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 The largest index where `data[i] <= target` or:
* - 0 if `target` is less than `data[0]`.
* - `null` if array is empty.
* @return The index of the first nearest (as defined above) element or `null` if the array is
* empty.

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

const firstGreaterIdx: number = binarySearch(length, (i) => data[i] as number > target);

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

* const result = getLargestIdxLte(arr, 8);
* console.log(result); // Output: 3 (since arr[3] is 7).
*/
const getLargestIdxLte = (data: number[], target: number): Nullable<number> => {
Copy link
Member

Choose a reason for hiding this comment

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

How about findFirstNearestElement?

  • "first nearest" is an abstract concept so hopefully users of the function will read the docstring before using it.
  • I think using "element" instead of "index" is a bit more intuitive since we really care about the element; it's just that the function indicates the found element by returning an index.

Copy link
Contributor Author

@davemarco davemarco left a comment

Choose a reason for hiding this comment

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

I tried a different approach where the interface for findNearestLessThanOrEqualElement is closer to what you would expect for binary search. Now to use for our use case for searching the filtered log event map, first you call clampWithinBounds followed by findNearestLessThanOrEqualElement, this will achieve the same effect as the previous less intuitive single function.

Comment on lines +51 to +52
* Finds the largest index `i` in a sorted array `data` such that `data[i] <= target`. 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.

see review comment

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
new-log-viewer/src/utils/data.ts (3)

5-19: LGTM with a minor suggestion.

The function looks good and implements the required logic correctly. The documentation is clear and concise.

However, consider removing the type assertions as number if the data parameter is properly typed as number[]. These assertions are unnecessary and may hide potential type errors.

Here's a suggested improvement:

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

21-35: LGTM with a minor suggestion.

The function is well-implemented and properly documented. It correctly handles the edge case of an empty array and uses the clamp utility function, which is good for readability and maintainability.

Similar to the isWithinBounds function, consider removing the type assertions as number if the data parameter is properly typed as number[].

Here's a suggested improvement:

-    return clamp(num, data[0] as number, data[length - 1] as number);
+    return clamp(num, data[0], data[length - 1]);

67-96: LGTM with a minor suggestion.

The function is well-implemented and properly documented. It correctly handles the edge case of an empty array and uses an efficient binary search algorithm. The function name and documentation are clear and have been approved in previous reviews.

As with the previous functions, consider removing the type assertion as number if the data parameter is properly typed as number[].

Here's a suggested improvement:

-    const firstGreaterIdx: number = binarySearch(length, (i) => data[i] as number > target);
+    const firstGreaterIdx: number = binarySearch(length, (i) => data[i] > target);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6794146 and 7ba6706.

📒 Files selected for processing (1)
  • new-log-viewer/src/utils/data.ts (2 hunks)
🔇 Additional comments (2)
new-log-viewer/src/utils/data.ts (2)

135-141: LGTM! Export statement updated correctly.

The export statement has been properly updated to include all the new functions while maintaining the existing exports. The alphabetical sorting of the exports is a nice touch for readability and maintainability.


Line range hint 1-141: Overall, excellent implementation aligning with PR objectives.

The new utility functions (isWithinBounds, clampWithinBounds, and findNearestLessThanOrEqualElement) provide a solid foundation for implementing efficient log filtering capabilities in the y-scope log viewer. These functions, particularly the binary search implementation, will enable the back-end to efficiently locate log events in a filtered log event map, as outlined in the PR objectives.

The implementation is well-documented and includes proper error handling for edge cases. The use of binary search ensures efficient performance, which is crucial for handling large log datasets.

While the functions are not yet invoked in the existing code, they are now properly exported and ready for use in implementing the log filtering feature. This sets the stage for future development of the log viewer's capabilities, particularly in supporting front-end requests for specific log event numbers.

Copy link
Member

@kirkrodrigues kirkrodrigues left a comment

Choose a reason for hiding this comment

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

For the PR title, how about:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants