Skip to content

Commit 9059a37

Browse files
Merge branch 'master' into fix-gcd-edge-case
2 parents efd5c5d + 81fcb90 commit 9059a37

File tree

13 files changed

+841
-28
lines changed

13 files changed

+841
-28
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ repos:
1414
- id: requirements-txt-fixer
1515

1616
- repo: https://github.com/MarcoGorelli/auto-walrus
17-
rev: 0.3.4
17+
rev: 0.4.1
1818
hooks:
1919
- id: auto-walrus
2020

2121
- repo: https://github.com/astral-sh/ruff-pre-commit
22-
rev: v0.14.10
22+
rev: v0.14.14
2323
hooks:
2424
- id: ruff-check
2525
- id: ruff-format
@@ -32,7 +32,7 @@ repos:
3232
- tomli
3333

3434
- repo: https://github.com/tox-dev/pyproject-fmt
35-
rev: v2.11.1
35+
rev: v2.12.1
3636
hooks:
3737
- id: pyproject-fmt
3838

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@
881881
* [Quine](other/quine.py)
882882
* [Scoring Algorithm](other/scoring_algorithm.py)
883883
* [Sdes](other/sdes.py)
884+
* [Sliding Window Maximum](other/sliding_window_maximum.py)
884885
* [Tower Of Hanoi](other/tower_of_hanoi.py)
885886
* [Word Search](other/word_search.py)
886887

backtracking/generate_parentheses_iterative.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
def generate_parentheses_iterative(length: int) -> list:
1+
def generate_parentheses_iterative(length: int) -> list[str]:
22
"""
33
Generate all valid combinations of parentheses (Iterative Approach).
44
55
The algorithm works as follows:
66
1. Initialize an empty list to store the combinations.
77
2. Initialize a stack to keep track of partial combinations.
8-
3. Start with empty string and push it onstack along with the counts of '(' and ')'.
8+
3. Start with empty string and push it on stack along with
9+
the counts of '(' and ')'.
910
4. While the stack is not empty:
1011
a. Pop a partial combination and its open and close counts from the stack.
1112
b. If the combination length is equal to 2*length, add it to the result.
@@ -34,8 +35,11 @@ def generate_parentheses_iterative(length: int) -> list:
3435
>>> generate_parentheses_iterative(0)
3536
['']
3637
"""
37-
result = []
38-
stack = []
38+
if length == 0:
39+
return [""]
40+
41+
result: list[str] = []
42+
stack: list[tuple[str, int, int]] = []
3943

4044
# Each element in stack is a tuple (current_combination, open_count, close_count)
4145
stack.append(("", 0, 0))
@@ -45,6 +49,7 @@ def generate_parentheses_iterative(length: int) -> list:
4549

4650
if len(current_combination) == 2 * length:
4751
result.append(current_combination)
52+
continue
4853

4954
if open_count < length:
5055
stack.append((current_combination + "(", open_count + 1, close_count))

data_structures/hashing/hash_table_with_linked_list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def __init__(self, *args, **kwargs):
88
super().__init__(*args, **kwargs)
99

1010
def _set_value(self, key, data):
11-
self.values[key] = deque([]) if self.values[key] is None else self.values[key]
11+
self.values[key] = deque() if self.values[key] is None else self.values[key]
1212
self.values[key].appendleft(data)
1313
self._keys[key] = self.values[key]
1414

geometry/graham_scan.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""
2+
Graham Scan algorithm for finding the convex hull of a set of points.
3+
4+
The Graham scan is a method of computing the convex hull of a finite set of points
5+
in the plane with time complexity O(n log n). It is named after Ronald Graham, who
6+
published the original algorithm in 1972.
7+
8+
The algorithm finds all vertices of the convex hull ordered along its boundary.
9+
It uses a stack to efficiently identify and remove points that would create
10+
non-convex angles.
11+
12+
References:
13+
- https://en.wikipedia.org/wiki/Graham_scan
14+
- Graham, R.L. (1972). "An Efficient Algorithm for Determining the Convex Hull of a
15+
Finite Planar Set"
16+
"""
17+
18+
from __future__ import annotations
19+
20+
from collections.abc import Sequence
21+
from dataclasses import dataclass
22+
from typing import TypeVar
23+
24+
T = TypeVar("T", bound="Point")
25+
26+
27+
@dataclass
28+
class Point:
29+
"""
30+
A point in 2D space.
31+
32+
>>> Point(0, 0)
33+
Point(x=0.0, y=0.0)
34+
>>> Point(1.5, 2.5)
35+
Point(x=1.5, y=2.5)
36+
"""
37+
38+
x: float
39+
y: float
40+
41+
def __init__(self, x_coordinate: float, y_coordinate: float) -> None:
42+
"""
43+
Initialize a 2D point.
44+
45+
Args:
46+
x_coordinate: The x-coordinate (horizontal position) of the point
47+
y_coordinate: The y-coordinate (vertical position) of the point
48+
"""
49+
self.x = float(x_coordinate)
50+
self.y = float(y_coordinate)
51+
52+
def __eq__(self, other: object) -> bool:
53+
"""
54+
Check if two points are equal.
55+
56+
>>> Point(1, 2) == Point(1, 2)
57+
True
58+
>>> Point(1, 2) == Point(2, 1)
59+
False
60+
"""
61+
if not isinstance(other, Point):
62+
return NotImplemented
63+
return self.x == other.x and self.y == other.y
64+
65+
def __lt__(self, other: Point) -> bool:
66+
"""
67+
Compare two points for sorting (bottom-most, then left-most).
68+
69+
>>> Point(1, 2) < Point(1, 3)
70+
True
71+
>>> Point(1, 2) < Point(2, 2)
72+
True
73+
>>> Point(2, 2) < Point(1, 2)
74+
False
75+
"""
76+
if self.y == other.y:
77+
return self.x < other.x
78+
return self.y < other.y
79+
80+
def euclidean_distance(self, other: Point) -> float:
81+
"""
82+
Calculate Euclidean distance between two points.
83+
84+
>>> Point(0, 0).euclidean_distance(Point(3, 4))
85+
5.0
86+
>>> Point(1, 1).euclidean_distance(Point(4, 5))
87+
5.0
88+
"""
89+
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
90+
91+
def consecutive_orientation(self, point_a: Point, point_b: Point) -> float:
92+
"""
93+
Calculate the cross product of vectors (self -> point_a) and
94+
(point_a -> point_b).
95+
96+
Returns:
97+
- Positive value: counter-clockwise turn
98+
- Negative value: clockwise turn
99+
- Zero: collinear points
100+
101+
>>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(1, 1))
102+
1.0
103+
>>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(1, -1))
104+
-1.0
105+
>>> Point(0, 0).consecutive_orientation(Point(1, 0), Point(2, 0))
106+
0.0
107+
"""
108+
return (point_a.x - self.x) * (point_b.y - point_a.y) - (point_a.y - self.y) * (
109+
point_b.x - point_a.x
110+
)
111+
112+
113+
def graham_scan(points: Sequence[Point]) -> list[Point]:
114+
"""
115+
Find the convex hull of a set of points using the Graham scan algorithm.
116+
117+
The algorithm works as follows:
118+
1. Find the bottom-most point (or left-most in case of tie)
119+
2. Sort all other points by polar angle with respect to the bottom-most point
120+
3. Process points in order, maintaining a stack of hull candidates
121+
4. Remove points that would create a clockwise turn
122+
123+
Args:
124+
points: A sequence of Point objects
125+
126+
Returns:
127+
A list of Point objects representing the convex hull in counter-clockwise order.
128+
Returns an empty list if there are fewer than 3 distinct points or if all
129+
points are collinear.
130+
131+
Time Complexity: O(n log n) due to sorting
132+
Space Complexity: O(n) for the output hull
133+
134+
>>> graham_scan([])
135+
[]
136+
>>> graham_scan([Point(0, 0)])
137+
[]
138+
>>> graham_scan([Point(0, 0), Point(1, 1)])
139+
[]
140+
>>> hull = graham_scan([Point(0, 0), Point(1, 0), Point(0.5, 1)])
141+
>>> len(hull)
142+
3
143+
>>> Point(0, 0) in hull and Point(1, 0) in hull and Point(0.5, 1) in hull
144+
True
145+
"""
146+
if len(points) <= 2:
147+
return []
148+
149+
# Find the bottom-most point (left-most in case of tie)
150+
min_point = min(points)
151+
152+
# Remove the min_point from the list
153+
points_list = [p for p in points if p != min_point]
154+
if not points_list:
155+
# Edge case where all points are the same
156+
return []
157+
158+
def polar_angle_key(point: Point) -> tuple[float, float, float]:
159+
"""
160+
Key function for sorting points by polar angle relative to min_point.
161+
162+
Points are sorted counter-clockwise. When two points have the same angle,
163+
the farther point comes first (we'll remove duplicates later).
164+
"""
165+
# We use a dummy third point (min_point itself) to calculate relative angles
166+
# Instead, we'll compute the angle between points
167+
dx = point.x - min_point.x
168+
dy = point.y - min_point.y
169+
170+
# Use atan2 for angle, but we can also use cross product for comparison
171+
# For sorting, we compare orientations between consecutive points
172+
distance = min_point.euclidean_distance(point)
173+
return (dx, dy, -distance) # Negative distance to sort farther points first
174+
175+
# Sort by polar angle using a comparison based on cross product
176+
def compare_points(point_a: Point, point_b: Point) -> int:
177+
"""Compare two points by polar angle relative to min_point."""
178+
orientation = min_point.consecutive_orientation(point_a, point_b)
179+
if orientation < 0.0:
180+
return 1 # point_a comes after point_b (clockwise)
181+
elif orientation > 0.0:
182+
return -1 # point_a comes before point_b (counter-clockwise)
183+
else:
184+
# Collinear: farther point should come first
185+
dist_a = min_point.euclidean_distance(point_a)
186+
dist_b = min_point.euclidean_distance(point_b)
187+
if dist_b < dist_a:
188+
return -1
189+
elif dist_b > dist_a:
190+
return 1
191+
else:
192+
return 0
193+
194+
from functools import cmp_to_key
195+
196+
points_list.sort(key=cmp_to_key(compare_points))
197+
198+
# Build the convex hull
199+
convex_hull: list[Point] = [min_point, points_list[0]]
200+
201+
for point in points_list[1:]:
202+
# Skip consecutive points with the same angle (collinear with min_point)
203+
if min_point.consecutive_orientation(point, convex_hull[-1]) == 0.0:
204+
continue
205+
206+
# Remove points that create a clockwise turn (or are collinear)
207+
while len(convex_hull) >= 2:
208+
orientation = convex_hull[-2].consecutive_orientation(
209+
convex_hull[-1], point
210+
)
211+
if orientation <= 0.0:
212+
convex_hull.pop()
213+
else:
214+
break
215+
216+
convex_hull.append(point)
217+
218+
# Need at least 3 points for a valid convex hull
219+
if len(convex_hull) <= 2:
220+
return []
221+
222+
return convex_hull
223+
224+
225+
if __name__ == "__main__":
226+
import doctest
227+
228+
doctest.testmod()
229+
230+
# Example usage
231+
points = [
232+
Point(0, 0),
233+
Point(1, 0),
234+
Point(2, 0),
235+
Point(2, 1),
236+
Point(2, 2),
237+
Point(1, 2),
238+
Point(0, 2),
239+
Point(0, 1),
240+
Point(1, 1), # Interior point
241+
]
242+
243+
hull = graham_scan(points)
244+
print("Convex hull vertices:")
245+
for point in hull:
246+
print(f" ({point.x}, {point.y})")

0 commit comments

Comments
 (0)