1313
1414"""
1515
16+ from typing import Iterable , List , Set , Union
17+
1618
1719class Point :
1820 """
@@ -81,7 +83,9 @@ def __hash__(self):
8183 return hash (self .x )
8284
8385
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 ]:
8589 """
8690 constructs a list of points from an array-like object of numbers
8791
@@ -110,20 +114,23 @@ def _construct_points(list_of_tuples):
110114 []
111115 """
112116
113- points = []
117+ points : List [ Point ] = []
114118 if list_of_tuples :
115119 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+ )
123130 return points
124131
125132
126- def _validate_input (points ) :
133+ def _validate_input (points : Union [ List [ Point ], List [ List [ float ]]]) -> List [ Point ] :
127134 """
128135 validates an input instance before a convex-hull algorithms uses it
129136
@@ -165,33 +172,18 @@ def _validate_input(points):
165172 ValueError: Expecting an iterable object but got an non-iterable type 1
166173 """
167174
175+ if not hasattr (points , "__iter__" ):
176+ raise ValueError (
177+ f"Expecting an iterable object but got an non-iterable type { points } "
178+ )
179+
168180 if not points :
169181 raise ValueError (f"Expecting a list of points but got { points } " )
170182
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 )
192184
193185
194- def _det (a , b , c ) :
186+ def _det (a : Point , b : Point , c : Point ) -> float :
195187 """
196188 Computes the sign perpendicular distance of a 2d point c from a line segment
197189 ab. The sign indicates the direction of c relative to ab.
@@ -226,7 +218,7 @@ def _det(a, b, c):
226218 return det
227219
228220
229- def convex_hull_bf (points ) :
221+ def convex_hull_bf (points : List [ Point ]) -> List [ Point ] :
230222 """
231223 Constructs the convex hull of a set of 2D points using a brute force algorithm.
232224 The algorithm basically considers all combinations of points (i, j) and uses the
@@ -299,7 +291,7 @@ def convex_hull_bf(points):
299291 return sorted (convex_set )
300292
301293
302- def convex_hull_recursive (points ) :
294+ def convex_hull_recursive (points : List [ Point ]) -> List [ Point ] :
303295 """
304296 Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy
305297 The algorithm exploits the geometric properties of the problem by repeatedly
@@ -369,7 +361,9 @@ def convex_hull_recursive(points):
369361 return sorted (convex_set )
370362
371363
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 :
373367 """
374368
375369 Parameters
@@ -411,6 +405,77 @@ def _construct_hull(points, left, right, convex_set):
411405 _construct_hull (candidate_points , extreme_point , right , convex_set )
412406
413407
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+
414479def main ():
415480 points = [
416481 (0 , 3 ),
@@ -426,10 +491,14 @@ def main():
426491 ]
427492 # the convex set of points is
428493 # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)]
429- results_recursive = convex_hull_recursive (points )
430494 results_bf = convex_hull_bf (points )
495+
496+ results_recursive = convex_hull_recursive (points )
431497 assert results_bf == results_recursive
432498
499+ results_melkman = convex_hull_melkman (points )
500+ assert results_bf == results_melkman
501+
433502 print (results_bf )
434503
435504
0 commit comments