Skip to content

Commit 39fd0bf

Browse files
Copilotdjeada
andauthored
Fix, modernize and improve all Python graph examples (#26)
* Initial plan * Modernize and fix Python graph examples with type hints, docstrings, and improvements Co-authored-by: djeada <37275728+djeada@users.noreply.github.com> * Modernize Python collections with type hints, docstrings, and fix SyntaxWarnings Co-authored-by: djeada <37275728+djeada@users.noreply.github.com> * Fix Heap __copy__ method to properly copy the comparison function Co-authored-by: djeada <37275728+djeada@users.noreply.github.com> * Modernize Python brain teaser examples with type hints, docstrings, and improvements Co-authored-by: djeada <37275728+djeada@users.noreply.github.com> * Fix LRU cache bug: nodes now store key-value pairs for correct eviction Co-authored-by: djeada <37275728+djeada@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: djeada <37275728+djeada@users.noreply.github.com>
1 parent 5450fb7 commit 39fd0bf

56 files changed

Lines changed: 1758 additions & 1028 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/brain_teasers/python/add_string_numbers/src/add_string_numbers.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
1+
"""Add two numbers represented as strings, supporting decimals."""
2+
3+
from __future__ import annotations
4+
5+
16
def add_string_numbers(s1: str, s2: str) -> str:
7+
"""
8+
Add two numbers represented as strings.
9+
10+
Supports both integers and decimal numbers. Handles arbitrarily large numbers
11+
that would overflow standard integer types.
212
13+
Args:
14+
s1: First number as a string.
15+
s2: Second number as a string.
16+
17+
Returns:
18+
The sum of the two numbers as a string.
19+
"""
320
decimal_places = 0
421

522
def is_float(s: str) -> bool:
@@ -9,10 +26,10 @@ def number_of_decimal_places(s: str) -> int:
926
return len(s) - s.index(".") - 1
1027

1128
if is_float(s1) or is_float(s2):
12-
# check which number has more decimal places and add zeros to the other
13-
# keep track of the number of decimal places
14-
decimal_places_1 = number_of_decimal_places(s1)
15-
decimal_places_2 = number_of_decimal_places(s2)
29+
# Check which number has more decimal places and add zeros to the other
30+
# Keep track of the number of decimal places
31+
decimal_places_1 = number_of_decimal_places(s1) if is_float(s1) else 0
32+
decimal_places_2 = number_of_decimal_places(s2) if is_float(s2) else 0
1633
if is_float(s1) and is_float(s2):
1734
decimal_places = max(decimal_places_1, decimal_places_2)
1835
s1 = s1.replace(".", "")
@@ -23,16 +40,12 @@ def number_of_decimal_places(s: str) -> int:
2340
decimal_places = decimal_places_1
2441
s1 = s1.replace(".", "")
2542
s2 += "0" * decimal_places
26-
2743
elif is_float(s2):
2844
decimal_places = decimal_places_2
2945
s2 = s2.replace(".", "")
3046
s1 += "0" * decimal_places
3147

32-
# we can't convert to int, because the numbers might be too big and it will overflow
33-
# so we'll add the numbers digit by digit
34-
# we'll start from the end of the strings
35-
48+
# Add the numbers digit by digit starting from the end
3649
result = ""
3750
carry = 0
3851

@@ -42,9 +55,9 @@ def number_of_decimal_places(s: str) -> int:
4255
while i >= 0 or j >= 0:
4356
digit_1 = int(s1[i]) if i >= 0 else 0
4457
digit_2 = int(s2[j]) if j >= 0 else 0
45-
sum = digit_1 + digit_2 + carry
46-
carry = sum // 10
47-
result = str(sum % 10) + result
58+
total = digit_1 + digit_2 + carry
59+
carry = total // 10
60+
result = str(total % 10) + result
4861
i -= 1
4962
j -= 1
5063

src/brain_teasers/python/array_product/src/array_product.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
from typing import List
1+
"""Product of array except self - compute array where each element is the product of all other elements."""
22

3+
from __future__ import annotations
34

4-
def product_of_array_except_self(arr: List[int]) -> List[int]:
5+
6+
def product_of_array_except_self(arr: list[int]) -> list[int]:
7+
"""
8+
Return an array where each element is the product of all other elements.
9+
10+
This algorithm runs in O(n) time without using division.
11+
12+
Args:
13+
arr: Input array of integers.
14+
15+
Returns:
16+
Array where result[i] is the product of all elements except arr[i].
17+
"""
518
# Initialize the result array with all 1s
619
result = [1] * len(arr)
720
# Calculate the product of all elements to the left of each index
Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,53 @@
1-
from typing import List
1+
"""Binary tree right side view - return values visible from the right side."""
2+
3+
from __future__ import annotations
4+
5+
from collections import deque
6+
from typing import Optional
27

38

49
class TreeNode:
5-
def __init__(self, value: int, left: "TreeNode" = None, right: "TreeNode" = None):
10+
"""A node in a binary tree."""
11+
12+
def __init__(
13+
self,
14+
value: int,
15+
left: Optional[TreeNode] = None,
16+
right: Optional[TreeNode] = None,
17+
) -> None:
618
self.value = value
719
self.left = left
820
self.right = right
921

1022

11-
def right_side_view(root: TreeNode) -> List[int]:
12-
# Edge case: if the tree is empty, return an empty list
23+
def right_side_view(root: Optional[TreeNode]) -> list[int]:
24+
"""
25+
Return the values of nodes visible from the right side of a binary tree.
26+
27+
Uses level-order traversal (BFS) and returns the rightmost node at each level.
28+
29+
Args:
30+
root: The root node of the binary tree.
31+
32+
Returns:
33+
List of values visible from the right side, from top to bottom.
34+
"""
1335
if not root:
1436
return []
15-
# Initialize a queue and a result list
16-
queue = [root]
17-
result = []
18-
# Loop until the queue is empty
37+
38+
queue: deque[TreeNode] = deque([root])
39+
result: list[int] = []
40+
1941
while queue:
20-
# Get the size of the queue
2142
size = len(queue)
22-
# Loop through all the nodes in the queue
2343
for i in range(size):
24-
# Remove the first node from the queue
25-
node = queue.pop(0)
26-
# If this is the last node in the current level, add its value to the result list
44+
node = queue.popleft()
45+
# If this is the last node in the current level, add its value
2746
if i == size - 1:
2847
result.append(node.value)
29-
# If the node has a left child, add it to the queue
3048
if node.left:
3149
queue.append(node.left)
32-
# If the node has a right child, add it to the queue
3350
if node.right:
3451
queue.append(node.right)
35-
# Return the result list
52+
3653
return result

src/brain_teasers/python/binary_tree_right_side_view/tests/binary_tree_right_side_view.py renamed to src/brain_teasers/python/binary_tree_right_side_view/tests/test_binary_tree_right_side_view.py

File renamed without changes.
Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,124 @@
1-
from typing import List
1+
"""Convert a binary search tree to a doubly linked list."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Optional
26

37

48
class TreeNode:
5-
def __init__(self, value: int, left: "TreeNode" = None, right: "TreeNode" = None):
9+
"""A node in a binary search tree."""
10+
11+
def __init__(
12+
self,
13+
value: int,
14+
left: Optional[TreeNode] = None,
15+
right: Optional[TreeNode] = None,
16+
) -> None:
617
self.value = value
718
self.left = left
819
self.right = right
920

1021

1122
class ListNode:
12-
def __init__(self, value: int, next: "ListNode" = None):
23+
"""A node in a doubly linked list."""
24+
25+
def __init__(self, value: int, next: Optional[ListNode] = None) -> None:
1326
self.value = value
1427
self.next = next
15-
self.prev = None
28+
self.prev: Optional[ListNode] = None
1629

1730

1831
class DoubleLinkedList:
19-
def __init__(self):
20-
self.head = None
21-
self.tail = None
32+
"""A doubly linked list data structure."""
33+
34+
def __init__(self) -> None:
35+
self.head: Optional[ListNode] = None
36+
self.tail: Optional[ListNode] = None
2237

23-
def insert_before(self, node: ListNode, value: int) -> ListNode:
38+
def insert_before(self, node: Optional[ListNode], value: int) -> ListNode:
2439
"""Insert a new node with the given value before the given node."""
25-
# Create the new node
2640
new_node = ListNode(value)
27-
# If the given node is None, set the new node as the head and tail
2841
if not node:
2942
self.head = new_node
3043
self.tail = new_node
31-
# Otherwise, insert the new node before the given node
3244
else:
33-
# Set the new node's next to the given node
3445
new_node.next = node
35-
# If the given node is the head, set the new node as the head
3646
if node == self.head:
3747
self.head = new_node
38-
# Otherwise, set the new node's prev to the given node's prev and the given node's prev's next to the new node
3948
else:
4049
new_node.prev = node.prev
41-
node.prev.next = new_node
42-
# Set the given node's prev to the new node
50+
if node.prev:
51+
node.prev.next = new_node
4352
node.prev = new_node
44-
# Return the new node
4553
return new_node
4654

47-
def insert_after(self, node: ListNode, value: int) -> ListNode:
55+
def insert_after(self, node: Optional[ListNode], value: int) -> ListNode:
4856
"""Insert a new node with the given value after the given node."""
49-
# Create the new node
5057
new_node = ListNode(value)
51-
# If the given node is None, set the new node as the head and tail
5258
if not node:
5359
self.head = new_node
5460
self.tail = new_node
55-
# Otherwise, insert the new node after the given node
5661
else:
57-
# Set the new node's prev to the given node
5862
new_node.prev = node
59-
# If the given node is the tail, set the new node as the tail
6063
if node == self.tail:
6164
self.tail = new_node
62-
# Otherwise, set the new node's next to the given node's next and the given node's next's prev to the new node
6365
else:
6466
new_node.next = node.next
65-
node.next.prev = new_node
66-
# Set the given node's next to the new node
67+
if node.next:
68+
node.next.prev = new_node
6769
node.next = new_node
68-
# Return the new node
6970
return new_node
7071

7172
def delete(self, node: ListNode) -> None:
72-
"""Delete the given node."""
73-
# If the node is the head, set the head to the node's next
73+
"""Delete the given node from the list."""
7474
if node == self.head:
7575
self.head = node.next
76-
# Otherwise, set the node's prev's next to the node's next
7776
else:
78-
node.prev.next = node.next
79-
# If the node is the tail, set the tail to the node's prev
77+
if node.prev:
78+
node.prev.next = node.next
8079
if node == self.tail:
8180
self.tail = node.prev
82-
# Otherwise, set the node's next's prev to the node's prev
8381
else:
84-
node.next.prev = node.prev
82+
if node.next:
83+
node.next.prev = node.prev
8584

8685
def __iter__(self):
8786
node = self.head
8887
while node:
8988
yield node.value
9089
node = node.next
9190

92-
def __str__(self):
91+
def __str__(self) -> str:
9392
return " -> ".join(map(str, self))
9493

95-
def to_list(self) -> List[int]:
94+
def to_list(self) -> list[int]:
95+
"""Convert the linked list to a Python list."""
9696
return list(self)
9797

9898

99-
def bst_to_double_linked_list(root: TreeNode) -> DoubleLinkedList:
100-
"""Convert a binary search tree to a double linked list."""
101-
# Create the double linked list
99+
def bst_to_double_linked_list(root: Optional[TreeNode]) -> DoubleLinkedList:
100+
"""
101+
Convert a binary search tree to a doubly linked list.
102+
103+
Performs an in-order traversal to maintain sorted order.
104+
105+
Args:
106+
root: The root node of the binary search tree.
107+
108+
Returns:
109+
A doubly linked list containing all values in sorted order.
110+
"""
102111
double_linked_list = DoubleLinkedList()
103-
# Create a stack to store the nodes
104-
stack = []
105-
# Start at the root node
112+
stack: list[TreeNode] = []
106113
node = root
107-
# Traverse the tree
114+
108115
while node or stack:
109-
# If the current node is not None, push it onto the stack and go to the left child
110116
if node:
111117
stack.append(node)
112118
node = node.left
113-
# Otherwise, pop the top node from the stack, add it to the double linked list, and go to the right child
114119
else:
115120
node = stack.pop()
116121
double_linked_list.insert_before(double_linked_list.head, node.value)
117122
node = node.right
118-
# Return the double linked list
123+
119124
return double_linked_list
Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1+
"""Count minimum deletions to make parentheses valid."""
2+
3+
from __future__ import annotations
4+
5+
16
def deletions_to_make_valid_parentheses(s: str) -> int:
2-
# Stack to keep track of unmatched parentheses
3-
stack = []
4-
# Count of deletions needed
7+
"""
8+
Return the minimum number of parentheses to delete to make the string valid.
9+
10+
A string is valid if every opening parenthesis has a matching closing one.
11+
12+
Args:
13+
s: A string containing parentheses and potentially other characters.
14+
15+
Returns:
16+
The minimum number of deletions needed.
17+
"""
18+
stack: list[str] = []
519
deletions = 0
6-
# Loop through the string
20+
721
for c in s:
8-
# If the current character is an opening parenthesis, push it onto the stack
922
if c == "(":
1023
stack.append(c)
11-
# If the current character is a closing parenthesis
1224
elif c == ")":
13-
# If the stack is empty, we need to delete this closing parenthesis
1425
if not stack:
1526
deletions += 1
16-
# If the stack is not empty, we can match this closing parenthesis with an opening one
1727
else:
1828
stack.pop()
19-
# At the end of the loop, any remaining opening parentheses in the stack must be deleted
29+
30+
# Any remaining opening parentheses must be deleted
2031
deletions += len(stack)
2132
return deletions

0 commit comments

Comments
 (0)