13
13
14
14
"""
15
15
16
+ from typing import Iterable , List , Set , Union
17
+
16
18
17
19
class Point :
18
20
"""
@@ -81,7 +83,9 @@ def __hash__(self):
81
83
return hash (self .x )
82
84
83
85
84
- def _construct_points (list_of_tuples ):
86
+ def _construct_points (
87
+ list_of_tuples : Union [List [Point ], List [List [float ]], Iterable [List [float ]]]
88
+ ) -> List [Point ]:
85
89
"""
86
90
constructs a list of points from an array-like object of numbers
87
91
@@ -110,20 +114,23 @@ def _construct_points(list_of_tuples):
110
114
[]
111
115
"""
112
116
113
- points = []
117
+ points : List [ Point ] = []
114
118
if list_of_tuples :
115
119
for p in list_of_tuples :
116
- try :
117
- points .append (Point (p [0 ], p [1 ]))
118
- except (IndexError , TypeError ):
119
- print (
120
- f"Ignoring deformed point { p } . All points"
121
- " must have at least 2 coordinates."
122
- )
120
+ if isinstance (p , Point ):
121
+ points .append (p )
122
+ else :
123
+ try :
124
+ points .append (Point (p [0 ], p [1 ]))
125
+ except (IndexError , TypeError ):
126
+ print (
127
+ f"Ignoring deformed point { p } . All points"
128
+ " must have at least 2 coordinates."
129
+ )
123
130
return points
124
131
125
132
126
- def _validate_input (points ) :
133
+ def _validate_input (points : Union [ List [ Point ], List [ List [ float ]]]) -> List [ Point ] :
127
134
"""
128
135
validates an input instance before a convex-hull algorithms uses it
129
136
@@ -165,33 +172,18 @@ def _validate_input(points):
165
172
ValueError: Expecting an iterable object but got an non-iterable type 1
166
173
"""
167
174
175
+ if not hasattr (points , "__iter__" ):
176
+ raise ValueError (
177
+ f"Expecting an iterable object but got an non-iterable type { points } "
178
+ )
179
+
168
180
if not points :
169
181
raise ValueError (f"Expecting a list of points but got { points } " )
170
182
171
- if isinstance (points , set ):
172
- points = list (points )
173
-
174
- try :
175
- if hasattr (points , "__iter__" ) and not isinstance (points [0 ], Point ):
176
- if isinstance (points [0 ], (list , tuple )):
177
- points = _construct_points (points )
178
- else :
179
- raise ValueError (
180
- "Expecting an iterable of type Point, list or tuple. "
181
- f"Found objects of type { type (points [0 ])} instead"
182
- )
183
- elif not hasattr (points , "__iter__" ):
184
- raise ValueError (
185
- f"Expecting an iterable object but got an non-iterable type { points } "
186
- )
187
- except TypeError :
188
- print ("Expecting an iterable of type Point, list or tuple." )
189
- raise
190
-
191
- return points
183
+ return _construct_points (points )
192
184
193
185
194
- def _det (a , b , c ) :
186
+ def _det (a : Point , b : Point , c : Point ) -> float :
195
187
"""
196
188
Computes the sign perpendicular distance of a 2d point c from a line segment
197
189
ab. The sign indicates the direction of c relative to ab.
@@ -226,7 +218,7 @@ def _det(a, b, c):
226
218
return det
227
219
228
220
229
- def convex_hull_bf (points ) :
221
+ def convex_hull_bf (points : List [ Point ]) -> List [ Point ] :
230
222
"""
231
223
Constructs the convex hull of a set of 2D points using a brute force algorithm.
232
224
The algorithm basically considers all combinations of points (i, j) and uses the
@@ -299,7 +291,7 @@ def convex_hull_bf(points):
299
291
return sorted (convex_set )
300
292
301
293
302
- def convex_hull_recursive (points ) :
294
+ def convex_hull_recursive (points : List [ Point ]) -> List [ Point ] :
303
295
"""
304
296
Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy
305
297
The algorithm exploits the geometric properties of the problem by repeatedly
@@ -369,7 +361,9 @@ def convex_hull_recursive(points):
369
361
return sorted (convex_set )
370
362
371
363
372
- def _construct_hull (points , left , right , convex_set ):
364
+ def _construct_hull (
365
+ points : List [Point ], left : Point , right : Point , convex_set : Set [Point ]
366
+ ) -> None :
373
367
"""
374
368
375
369
Parameters
@@ -411,6 +405,77 @@ def _construct_hull(points, left, right, convex_set):
411
405
_construct_hull (candidate_points , extreme_point , right , convex_set )
412
406
413
407
408
+ def convex_hull_melkman (points : List [Point ]) -> List [Point ]:
409
+ """
410
+ Constructs the convex hull of a set of 2D points using the melkman algorithm.
411
+ The algorithm works by iteratively inserting points of a simple polygonal chain
412
+ (meaning that no line segments between two consecutive points cross each other).
413
+ Sorting the points yields such a polygonal chain.
414
+
415
+ For a detailed description, see http://cgm.cs.mcgill.ca/~athens/cs601/Melkman.html
416
+
417
+ Runtime: O(n log n) - O(n) if points are already sorted in the input
418
+
419
+ Parameters
420
+ ---------
421
+ points: array-like of object of Points, lists or tuples.
422
+ The set of 2d points for which the convex-hull is needed
423
+
424
+ Returns
425
+ ------
426
+ convex_set: list, the convex-hull of points sorted in non-decreasing order.
427
+
428
+ See Also
429
+ --------
430
+
431
+ Examples
432
+ ---------
433
+ >>> convex_hull_melkman([[0, 0], [1, 0], [10, 1]])
434
+ [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)]
435
+ >>> convex_hull_melkman([[0, 0], [1, 0], [10, 0]])
436
+ [(0.0, 0.0), (10.0, 0.0)]
437
+ >>> convex_hull_melkman([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1],
438
+ ... [-0.75, 1]])
439
+ [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)]
440
+ >>> convex_hull_melkman([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3),
441
+ ... (2, -1), (2, -4), (1, -3)])
442
+ [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)]
443
+ """
444
+ points = sorted (_validate_input (points ))
445
+ n = len (points )
446
+
447
+ convex_hull = points [:2 ]
448
+ for i in range (2 , n ):
449
+ det = _det (convex_hull [1 ], convex_hull [0 ], points [i ])
450
+ if det > 0 :
451
+ convex_hull .insert (0 , points [i ])
452
+ break
453
+ elif det < 0 :
454
+ convex_hull .append (points [i ])
455
+ break
456
+ else :
457
+ convex_hull [1 ] = points [i ]
458
+ i += 1
459
+
460
+ for i in range (i , n ):
461
+ if (
462
+ _det (convex_hull [0 ], convex_hull [- 1 ], points [i ]) > 0
463
+ and _det (convex_hull [- 1 ], convex_hull [0 ], points [1 ]) < 0
464
+ ):
465
+ # The point lies within the convex hull
466
+ continue
467
+
468
+ convex_hull .insert (0 , points [i ])
469
+ convex_hull .append (points [i ])
470
+ while _det (convex_hull [0 ], convex_hull [1 ], convex_hull [2 ]) >= 0 :
471
+ del convex_hull [1 ]
472
+ while _det (convex_hull [- 1 ], convex_hull [- 2 ], convex_hull [- 3 ]) <= 0 :
473
+ del convex_hull [- 2 ]
474
+
475
+ # `convex_hull` is contains the convex hull in circular order
476
+ return sorted (convex_hull [1 :] if len (convex_hull ) > 3 else convex_hull )
477
+
478
+
414
479
def main ():
415
480
points = [
416
481
(0 , 3 ),
@@ -426,10 +491,14 @@ def main():
426
491
]
427
492
# the convex set of points is
428
493
# [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)]
429
- results_recursive = convex_hull_recursive (points )
430
494
results_bf = convex_hull_bf (points )
495
+
496
+ results_recursive = convex_hull_recursive (points )
431
497
assert results_bf == results_recursive
432
498
499
+ results_melkman = convex_hull_melkman (points )
500
+ assert results_bf == results_melkman
501
+
433
502
print (results_bf )
434
503
435
504
0 commit comments