-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms): sliding window #155
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
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds sliding-window algorithms (Max Sum Of Subarray with three implementations; Max Points From Cards), their READMEs and tests; introduces binary tree utilities (create_tree_from_nodes, level_order_traversal), a mirror/invert implementation and tests, plus minor test import path updates and DIRECTORY.md entries. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@algorithms/sliding_window/max_sum_of_subarray/__init__.py`:
- Around line 4-23: The function max_sum_subarray incorrectly initializes
max_sum to 0 which yields wrong results for all-negative inputs; fix it by
setting max_sum to the sum of the first window (or to negative infinity) before
the sliding loop so comparisons are correct for negative sums—compute
initial_window = sum(nums[0:k]) and set max_sum = initial_window (and set state
= initial_window and start = 0, then iterate end from k to n updating state,
max_sum, and start accordingly) or alternatively set max_sum = float("-inf") and
preserve the existing loop but rely on comparisons to update it.
🧹 Nitpick comments (1)
algorithms/sliding_window/max_sum_of_subarray/__init__.py (1)
45-64: Performance: O(n·k) time complexity due to repeatedsum()calls.The
sum(nums[lower_bound:upper_bound])on line 59 is called for each window position, making this O(k) per iteration and O(n·k) overall. For large inputs (e.g., n = 10^5), this will be significantly slower than the O(n) implementations above.Additionally,
max_sum = 0has the same negative-value issue asmax_sum_subarray.♻️ Suggested refactor for O(n) complexity
lower_bound = 0 upper_bound = k - max_sum = 0 + current_sum = sum(nums[:k]) + max_sum = current_sum while upper_bound <= n: - current_sum = sum(nums[lower_bound:upper_bound]) max_sum = max(current_sum, max_sum) + if upper_bound < n: + current_sum = current_sum - nums[lower_bound] + nums[upper_bound] lower_bound += 1 upper_bound += 1 return max_sum
…negative values Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@datastructures/trees/binary/test_utils.py`:
- Around line 230-247: Test expectations in MIRROR_BINARY_TREE_TEST_CASES
include None placeholders that will never be produced by create_tree_from_nodes
or level_order_traversal; update the two affected tuples so expected lists omit
None values (change the second case expected from [1, None, 2, None, 3, None, 4]
to [1,2,3,4] and the third listed mirrored expectation to remove the spurious
None so it becomes [25,75,50,350,125,100]) so
MirrorBinaryTreeTestCase.test_mirror_binary_tree compares against the actual
output produced by mirror_binary_tree and level_order_traversal.
In `@datastructures/trees/binary/tree/test_binary_tree_invert_tree.py`:
- Around line 1-18: This file is a duplicate that tests BinaryTree.create_tree
but is named test_binary_tree_invert_tree.py and imports an unused
BinaryTreeNode; either delete this file to remove the duplicate, or replace its
contents with proper invert_tree tests: remove the unused BinaryTreeNode import,
rename the test class/methods appropriately, and add tests that call
BinaryTree.invert_tree on constructed trees (using BinaryTree.create_tree to
build inputs) and assert the expected root/child structure after inversion;
ensure test file name, class, and test functions reflect invert_tree behavior if
you choose to keep and repurpose it.
In `@datastructures/trees/binary/tree/tree_utils.py`:
- Around line 1-44: The function create_tree_from_nodes currently constructs a
BinaryTreeNode from nodes[0] without guarding against a None root; add a check
after the empty-list guard to return None if nodes[0] is None. Replace the use
of queue.Queue with collections.deque: change the queue variable type to deque,
initialize with deque(), and replace queue.put(...) with queue.append(...) and
queue.get() with queue.popleft(); update any type hints from
Queue[BinaryTreeNode] to deque[BinaryTreeNode] or typing.Deque[BinaryTreeNode]
as appropriate. Ensure all references to curr.left / curr.right assignments
remain the same and the function still returns the root or None.
In `@DIRECTORY.md`:
- Around line 234-237: Fix the MD007 list indentation by aligning the new
bullets with the surrounding nested list levels: ensure the "Max Points From
Cards" and its test link, and the "Max Sum Of Subarray" and its test link
entries use the same indentation spacing as other nested list items (match the
parent bullet's indent level), and apply the same correction to the equivalent
block at lines referenced (489-493); adjust the leading spaces so each
sub-bullet is consistently indented under its parent list item.
♻️ Duplicate comments (1)
algorithms/sliding_window/max_sum_of_subarray/__init__.py (1)
52-61: Handle all-negative inputs inmax_sum_subarray_3.
max_sumstarts at 0, which yields incorrect results for arrays with all negative sums. Initialize from the first window (or-inf) instead.🐛 Proposed fix
- max_sum = 0 + max_sum = sum(nums[:k])
🧹 Nitpick comments (1)
datastructures/trees/binary/tree/tree_utils.py (1)
47-73: Clarify placeholder behavior inlevel_order_traversal.
The current implementation never enqueuesNonechildren, so theNonebranch is effectively dead code. If you intend to preserve shape with placeholders, enqueue both children (includingNone) and stop when an entire level is empty; otherwise, remove the placeholder logic for clarity.♻️ One way to preserve structure safely
- while current_level: + while current_level and any(node is not None for node in current_level): level: List[Any] = [] next_level: List[Optional[BinaryTreeNode]] = [] for node in current_level: - if not node: - level.append(None) - next_level.append(None) - else: - level.append(node.data) - - if node.left: - next_level.append(node.left) - if node.right: - next_level.append(node.right) + if node is None: + level.append(None) + next_level.extend([None, None]) + continue + level.append(node.data) + next_level.append(node.left) + next_level.append(node.right)
| MIRROR_BINARY_TREE_TEST_CASES = [ | ||
| ([100, 50, 200, 25, 75, 125, 350], [100, 200, 50, 350, 125, 75, 25]), | ||
| ([1, 2, None, 3, None, 4], [1, None, 2, None, 3, None, 4]), | ||
| ([25, 50, 75, 100, 125, 350], [25, 75, 50, None, 350, 125, 100]), | ||
| ([100], [100]), | ||
| ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), | ||
| ([], []), | ||
| ([2, 1, 3], [2, 3, 1]), | ||
| ] | ||
|
|
||
|
|
||
| class MirrorBinaryTreeTestCase(unittest.TestCase): | ||
| @parameterized.expand(MIRROR_BINARY_TREE_TEST_CASES) | ||
| def test_mirror_binary_tree(self, data: List[int], expected: List[int]): | ||
| root = create_tree_from_nodes(data) | ||
| actual = mirror_binary_tree(root) | ||
| actual_data = level_order_traversal(actual) | ||
| self.assertEqual(expected, actual_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and inspect level_order_traversal implementation
find . -name "tree_utils.py" -type f | head -5Repository: BrianLusina/PythonSnips
Length of output: 115
🏁 Script executed:
#!/bin/bash
# Search for level_order_traversal implementation
rg -nA 30 'def level_order_traversal' --type pyRepository: BrianLusina/PythonSnips
Length of output: 13160
🏁 Script executed:
#!/bin/bash
# Search for mirror_binary_tree implementation
rg -nA 30 'def mirror_binary_tree' --type pyRepository: BrianLusina/PythonSnips
Length of output: 1738
🏁 Script executed:
#!/bin/bash
# Search for create_tree_from_nodes implementation
rg -nA 50 'def create_tree_from_nodes' --type pyRepository: BrianLusina/PythonSnips
Length of output: 4198
🏁 Script executed:
#!/bin/bash
# Look at the test file to understand imports and context
head -50 datastructures/trees/binary/test_utils.pyRepository: BrianLusina/PythonSnips
Length of output: 1751
🏁 Script executed:
#!/bin/bash
# Check if there are other usages of level_order_traversal in tests
rg -n 'level_order_traversal' datastructures/trees/binary/test_utils.pyRepository: BrianLusina/PythonSnips
Length of output: 152
🏁 Script executed:
#!/bin/bash
# Trace through test case [25, 50, 75, 100, 125, 350] with a simple Python script
python3 << 'EOF'
# Simulating tree construction from [25, 50, 75, 100, 125, 350]
# Using level-order construction like create_tree_from_nodes
nodes = [25, 50, 75, 100, 125, 350]
print("Tree construction from nodes:", nodes)
print("Index mapping (level-order):")
print(" 0:25")
print(" / \\")
print("1:50 2:75")
print("/ \\ / \\")
print("3:100 4:125 5:350")
print()
# After mirroring (swap left/right at each node)
print("After mirroring (swapping left/right):")
print(" 0:25")
print(" / \\")
print("2:75 1:50")
print("/ \\ / \\")
print("5:350 4:125 3:100")
print()
print("Expected from test: [25, 75, 50, None, 350, 125, 100]")
print("Actual from level_order_traversal should be: [25, 75, 50, 350, 125, 100]")
print("(no None values)")
EOFRepository: BrianLusina/PythonSnips
Length of output: 153
Test expectations contain incorrect None placeholders that level_order_traversal will not produce.
The level_order_traversal function in tree_utils.py (lines 54–71) only appends child nodes to next_level when they exist (lines 65–68); it never adds None placeholders for missing children. This means None values do not propagate through levels, and the output contains only actual node values.
Additionally, create_tree_from_nodes skips creating children when input values are None (lines 30–32, 37–39), so None entries in the input list do not appear as None nodes in the tree structure.
Affected test cases will fail:
- Line 232: Input
[1, 2, None, 3, None, 4]creates a left-skewed tree. After mirroring,level_order_traversalreturns[1, 2, 3, 4], not[1, None, 2, None, 3, None, 4]. - Line 233: Input
[25, 50, 75, 100, 125, 350]after mirroring produces[25, 75, 50, 350, 125, 100], not[25, 75, 50, None, 350, 125, 100].
Update expected values to remove None placeholders.
🤖 Prompt for AI Agents
In `@datastructures/trees/binary/test_utils.py` around lines 230 - 247, Test
expectations in MIRROR_BINARY_TREE_TEST_CASES include None placeholders that
will never be produced by create_tree_from_nodes or level_order_traversal;
update the two affected tuples so expected lists omit None values (change the
second case expected from [1, None, 2, None, 3, None, 4] to [1,2,3,4] and the
third listed mirrored expectation to remove the spurious None so it becomes
[25,75,50,350,125,100]) so MirrorBinaryTreeTestCase.test_mirror_binary_tree
compares against the actual output produced by mirror_binary_tree and
level_order_traversal.
| import unittest | ||
|
|
||
| from datastructures.trees.binary.tree import BinaryTree | ||
| from datastructures.trees.binary.node import BinaryTreeNode | ||
|
|
||
|
|
||
| class BinaryTreeCreateTreeTestCases(unittest.TestCase): | ||
| def test_create_tree_1(self): | ||
| """should create a tree from [1, None, 1, 1, 1, None, None, 1, 1, None, 1, None, None, None, 1]""" | ||
| elements = [1, None, 1, 1, 1, None, None, 1, 1, None, 1, None, None, None, 1] | ||
| tree = BinaryTree.create_tree(elements) | ||
|
|
||
| self.assertEqual(1, tree.root.data) | ||
| self.assertIsNone(tree.root.left) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File name does not match test content; duplicate test detected.
This file is named test_binary_tree_invert_tree.py but contains BinaryTreeCreateTreeTestCases testing BinaryTree.create_tree. Additionally, this exact test already exists in datastructures/trees/binary/tree/test_binary_tree.py (lines 7-14 per relevant code snippets).
Either:
- Rename this file to contain actual
invert_treetests, or - Remove this duplicate file entirely
Also, BinaryTreeNode is imported on line 4 but never used.
🤖 Prompt for AI Agents
In `@datastructures/trees/binary/tree/test_binary_tree_invert_tree.py` around
lines 1 - 18, This file is a duplicate that tests BinaryTree.create_tree but is
named test_binary_tree_invert_tree.py and imports an unused BinaryTreeNode;
either delete this file to remove the duplicate, or replace its contents with
proper invert_tree tests: remove the unused BinaryTreeNode import, rename the
test class/methods appropriately, and add tests that call BinaryTree.invert_tree
on constructed trees (using BinaryTree.create_tree to build inputs) and assert
the expected root/child structure after inversion; ensure test file name, class,
and test functions reflect invert_tree behavior if you choose to keep and
repurpose it.
| from typing import List, Optional, Any | ||
| from datastructures.trees.binary.node import BinaryTreeNode | ||
| from queue import Queue | ||
| from itertools import chain | ||
|
|
||
|
|
||
| def create_tree_from_nodes(nodes: List[Any]) -> Optional[BinaryTreeNode]: | ||
| """ | ||
| Factory method to create a BinaryTreeNode given a list of values | ||
| Args: | ||
| nodes: List of values to be used to create the tree | ||
| Returns: | ||
| Optional[BinaryTreeNode]: The root of the created tree | ||
| """ | ||
| if len(nodes) == 0: | ||
| return None | ||
|
|
||
| root = BinaryTreeNode(nodes[0]) | ||
|
|
||
| queue: Queue[BinaryTreeNode] = Queue() | ||
| queue.put(root) | ||
|
|
||
| i = 1 | ||
| while i < len(nodes): | ||
| # Get the next node from the queue | ||
| curr = queue.get() | ||
|
|
||
| # If the node is not None, create a new TreeNode object for its left child, | ||
| # set it as the left child of the current node, and add it to the queue | ||
| if nodes[i] is not None: | ||
| curr.left = BinaryTreeNode(nodes[i]) | ||
| queue.put(curr.left) | ||
|
|
||
| i += 1 | ||
| # If there are more nodes in the list and the next node is not None, create a new BinaryTreeNode for its | ||
| # right child, set it as the right child of the current node, and add it to the queue | ||
| if i < len(nodes) and nodes[i] is not None: | ||
| curr.right = BinaryTreeNode(nodes[i]) | ||
| queue.put(curr.right) | ||
|
|
||
| i += 1 | ||
|
|
||
| # Return the root of the binary tree | ||
| return root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and examine it
fd -t f "tree_utils.py" --exec cat -n {} \;Repository: BrianLusina/PythonSnips
Length of output: 2780
🏁 Script executed:
# Let's also check if there are any tests or examples showing how this function is used
fd -t f -e "test_*.py" -o -e "*_test.py" | head -20Repository: BrianLusina/PythonSnips
Length of output: 183
🏁 Script executed:
# Search for usages of create_tree_from_nodes
rg "create_tree_from_nodes" -A 3 -B 3Repository: BrianLusina/PythonSnips
Length of output: 1729
🏁 Script executed:
# Let's trace through the algorithm logic with Python to verify the blocking issue
python3 << 'EOF'
# Simulate the algorithm with different inputs to see if blocking occurs
def trace_algorithm(nodes):
"""Trace through the algorithm to see if queue can empty while i < len(nodes)"""
from collections import deque
print(f"\nInput nodes: {nodes}")
if len(nodes) == 0:
print(" -> Returns None (empty list)")
return
# Simulate with Queue.Queue behavior
queue_items = ["root"] # Simulate the queue
i = 1
step = 0
while i < len(nodes):
step += 1
if not queue_items:
print(f" Step {step}: Queue is EMPTY but i={i} < len(nodes)={len(nodes)}")
print(f" -> BLOCKING: queue.Queue.get() would block here!")
return True
print(f" Step {step}: i={i}, queue has {len(queue_items)} items, checking nodes[{i}]={nodes[i]}")
curr = queue_items.pop(0) # get()
# Check left child (nodes[i])
if nodes[i] is not None:
print(f" -> nodes[{i}] is not None, adding left child to queue")
queue_items.append(f"left_child_{i}")
else:
print(f" -> nodes[{i}] is None, skipping left child")
i += 1
# Check right child (nodes[i])
if i < len(nodes):
if nodes[i] is not None:
print(f" -> nodes[{i}] is not None, adding right child to queue")
queue_items.append(f"right_child_{i}")
else:
print(f" -> nodes[{i}] is None, skipping right child")
i += 1
else:
print(f" -> No more nodes to process (i would be {i} >= len(nodes)={len(nodes)})")
i += 1
print(f" -> Loop completed successfully")
return False
# Test cases
test_cases = [
[1], # Single node
[1, 2, 3], # Complete tree
[1, 2, 3, None, None], # Tree with trailing Nones
[1, 2, 3, None, None, None], # More trailing Nones
[1, None, None], # Root has no children (all Nones)
[None], # None as root
[1, 2], # Incomplete right child
]
for nodes in test_cases:
blocking = trace_algorithm(nodes)
if blocking:
print(" ⚠️ BLOCKING CONDITION FOUND\n")
EOFRepository: BrianLusina/PythonSnips
Length of output: 1772
Guard against None as the root node value.
Line 18 creates a BinaryTreeNode from nodes[0] without checking if it's None. Extend the empty-list check to also return early if the root is None.
For defensive robustness, replace queue.Queue with collections.deque and use popleft()/append() instead of get()/put().
Suggested fix
-from queue import Queue
+from collections import deque
@@
- if len(nodes) == 0:
+ if not nodes or nodes[0] is None:
return None
@@
- queue: Queue[BinaryTreeNode] = Queue()
- queue.put(root)
+ queue = deque([root])
@@
- curr = queue.get()
+ curr = queue.popleft()
@@
- queue.put(curr.left)
+ queue.append(curr.left)
@@
- queue.put(curr.right)
+ queue.append(curr.right)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from typing import List, Optional, Any | |
| from datastructures.trees.binary.node import BinaryTreeNode | |
| from queue import Queue | |
| from itertools import chain | |
| def create_tree_from_nodes(nodes: List[Any]) -> Optional[BinaryTreeNode]: | |
| """ | |
| Factory method to create a BinaryTreeNode given a list of values | |
| Args: | |
| nodes: List of values to be used to create the tree | |
| Returns: | |
| Optional[BinaryTreeNode]: The root of the created tree | |
| """ | |
| if len(nodes) == 0: | |
| return None | |
| root = BinaryTreeNode(nodes[0]) | |
| queue: Queue[BinaryTreeNode] = Queue() | |
| queue.put(root) | |
| i = 1 | |
| while i < len(nodes): | |
| # Get the next node from the queue | |
| curr = queue.get() | |
| # If the node is not None, create a new TreeNode object for its left child, | |
| # set it as the left child of the current node, and add it to the queue | |
| if nodes[i] is not None: | |
| curr.left = BinaryTreeNode(nodes[i]) | |
| queue.put(curr.left) | |
| i += 1 | |
| # If there are more nodes in the list and the next node is not None, create a new BinaryTreeNode for its | |
| # right child, set it as the right child of the current node, and add it to the queue | |
| if i < len(nodes) and nodes[i] is not None: | |
| curr.right = BinaryTreeNode(nodes[i]) | |
| queue.put(curr.right) | |
| i += 1 | |
| # Return the root of the binary tree | |
| return root | |
| from typing import List, Optional, Any | |
| from datastructures.trees.binary.node import BinaryTreeNode | |
| from collections import deque | |
| from itertools import chain | |
| def create_tree_from_nodes(nodes: List[Any]) -> Optional[BinaryTreeNode]: | |
| """ | |
| Factory method to create a BinaryTreeNode given a list of values | |
| Args: | |
| nodes: List of values to be used to create the tree | |
| Returns: | |
| Optional[BinaryTreeNode]: The root of the created tree | |
| """ | |
| if not nodes or nodes[0] is None: | |
| return None | |
| root = BinaryTreeNode(nodes[0]) | |
| queue = deque([root]) | |
| i = 1 | |
| while i < len(nodes): | |
| # Get the next node from the queue | |
| curr = queue.popleft() | |
| # If the node is not None, create a new TreeNode object for its left child, | |
| # set it as the left child of the current node, and add it to the queue | |
| if nodes[i] is not None: | |
| curr.left = BinaryTreeNode(nodes[i]) | |
| queue.append(curr.left) | |
| i += 1 | |
| # If there are more nodes in the list and the next node is not None, create a new BinaryTreeNode for its | |
| # right child, set it as the right child of the current node, and add it to the queue | |
| if i < len(nodes) and nodes[i] is not None: | |
| curr.right = BinaryTreeNode(nodes[i]) | |
| queue.append(curr.right) | |
| i += 1 | |
| # Return the root of the binary tree | |
| return root |
🤖 Prompt for AI Agents
In `@datastructures/trees/binary/tree/tree_utils.py` around lines 1 - 44, The
function create_tree_from_nodes currently constructs a BinaryTreeNode from
nodes[0] without guarding against a None root; add a check after the empty-list
guard to return None if nodes[0] is None. Replace the use of queue.Queue with
collections.deque: change the queue variable type to deque, initialize with
deque(), and replace queue.put(...) with queue.append(...) and queue.get() with
queue.popleft(); update any type hints from Queue[BinaryTreeNode] to
deque[BinaryTreeNode] or typing.Deque[BinaryTreeNode] as appropriate. Ensure all
references to curr.left / curr.right assignments remain the same and the
function still returns the root or None.
| * Max Points From Cards | ||
| * [Test Max Points From Cards](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/max_points_from_cards/test_max_points_from_cards.py) | ||
| * Max Sum Of Subarray | ||
| * [Test Max Sum Sub Array](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/max_sum_of_subarray/test_max_sum_sub_array.py) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix MD007 list indentation for the new bullets.
markdownlint reports list indentation issues at these lines; please align indentation with the surrounding list nesting to satisfy MD007.
Also applies to: 489-493
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
234-234: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
235-235: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
236-236: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
237-237: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
🤖 Prompt for AI Agents
In `@DIRECTORY.md` around lines 234 - 237, Fix the MD007 list indentation by
aligning the new bullets with the surrounding nested list levels: ensure the
"Max Points From Cards" and its test link, and the "Max Sum Of Subarray" and its
test link entries use the same indentation spacing as other nested list items
(match the parent bullet's indent level), and apply the same correction to the
equivalent block at lines referenced (489-493); adjust the leading spaces so
each sub-bullet is consistently indented under its parent list item.
Describe your change:
Sliding window algorithm problems
Checklist:
Fixes: #{$ISSUE_NO}.Summary by CodeRabbit
New Features
Documentation
Tests
Utilities
✏️ Tip: You can customize this high-level summary in your review settings.