@@ -85,42 +85,59 @@ def jarvis_march(points: list[Point]) -> list[Point]:
8585 if len (points ) <= 2 :
8686 return []
8787
88+ # Remove duplicate points to avoid infinite loops
89+ unique_points = list (set (points ))
90+
91+ if len (unique_points ) <= 2 :
92+ return []
93+
8894 convex_hull : list [Point ] = []
8995
9096 # Find the leftmost point (and bottom-most in case of tie)
9197 left_point_idx = 0
92- for i in range (1 , len (points )):
93- if points [i ].x < points [left_point_idx ].x or (
94- points [i ].x == points [left_point_idx ].x
95- and points [i ].y < points [left_point_idx ].y
98+ for i in range (1 , len (unique_points )):
99+ if unique_points [i ].x < unique_points [left_point_idx ].x or (
100+ unique_points [i ].x == unique_points [left_point_idx ].x
101+ and unique_points [i ].y < unique_points [left_point_idx ].y
96102 ):
97103 left_point_idx = i
98104
99- convex_hull .append (Point (points [left_point_idx ].x , points [left_point_idx ].y ))
105+ convex_hull .append (Point (unique_points [left_point_idx ].x , unique_points [left_point_idx ].y ))
100106
101107 current_idx = left_point_idx
102108 while True :
103109 # Find the next counter-clockwise point
104- next_idx = (current_idx + 1 ) % len (points )
105- for i in range (len (points )):
106- if _cross_product (points [current_idx ], points [i ], points [next_idx ]) > 0 :
110+ next_idx = (current_idx + 1 ) % len (unique_points )
111+ # Make sure next_idx is not the same as current_idx (handle duplicates)
112+ while next_idx == current_idx :
113+ next_idx = (next_idx + 1 ) % len (unique_points )
114+
115+ for i in range (len (unique_points )):
116+ # Skip the current point itself (handles duplicates)
117+ if i == current_idx :
118+ continue
119+ if _cross_product (unique_points [current_idx ], unique_points [i ], unique_points [next_idx ]) > 0 :
107120 next_idx = i
108121
109122 if next_idx == left_point_idx :
110123 # Completed constructing the hull
111124 break
112125
126+ # Safety check: if next_idx == current_idx, we have duplicates causing issues
127+ if next_idx == current_idx :
128+ break
129+
113130 current_idx = next_idx
114131
115132 # Check if the last point is collinear with new point and second-to-last
116133 last = len (convex_hull ) - 1
117134 if len (convex_hull ) > 1 and _is_point_on_segment (
118- convex_hull [last - 1 ], convex_hull [last ], points [current_idx ]
135+ convex_hull [last - 1 ], convex_hull [last ], unique_points [current_idx ]
119136 ):
120137 # Remove the last point from the hull
121- convex_hull [last ] = Point (points [current_idx ].x , points [current_idx ].y )
138+ convex_hull [last ] = Point (unique_points [current_idx ].x , unique_points [current_idx ].y )
122139 else :
123- convex_hull .append (Point (points [current_idx ].x , points [current_idx ].y ))
140+ convex_hull .append (Point (unique_points [current_idx ].x , unique_points [current_idx ].y ))
124141
125142 # Check for edge case: last point collinear with first and second-to-last
126143 if len (convex_hull ) <= 2 :
@@ -132,6 +149,20 @@ def jarvis_march(points: list[Point]) -> list[Point]:
132149 if len (convex_hull ) == 2 :
133150 return []
134151
152+ # Final check: verify the hull forms a valid polygon (at least one non-zero cross product)
153+ # If all cross products are zero, all points are collinear
154+ has_turn = False
155+ for i in range (len (convex_hull )):
156+ p1 = convex_hull [i ]
157+ p2 = convex_hull [(i + 1 ) % len (convex_hull )]
158+ p3 = convex_hull [(i + 2 ) % len (convex_hull )]
159+ if abs (_cross_product (p1 , p2 , p3 )) > 1e-9 :
160+ has_turn = True
161+ break
162+
163+ if not has_turn :
164+ return []
165+
135166 return convex_hull
136167
137168
0 commit comments