import random from collections import namedtuple import matplotlib.pyplot as plt from enum import Enum, auto from math import atan2, degrees, tau, pi, acos, sqrt Point = namedtuple('Point', 'x y') class Side(Enum): ON = auto() ABOVE = auto() BELOW = auto() def display(points, hull): x = [point.x for point in points] y = [point.y for point in points] h_x = [point.x for point in hull] h_y = [point.y for point in hull] plt.plot(h_x, h_y, 'ro-') plt.scatter(x, y) plt.show() def gen_point(): a = random.uniform(1, 5) b = random.uniform(1, 5) x_i = random.uniform(1, 5) p_i = Point(x_i, a * x_i + b) return p_i def sidedness(p1, p2, p3, eps=0.0000001): y = p1.y * (p3.x - p2.x) x = p1.x a = (p3.y - p2.y) b = p3.y * (p3.x - p2.x) - a * p3.x line = a*x+b if y - eps < line < y + eps: return [Side.ON, y - line] elif y > line: return [Side.ABOVE, y - line] return [Side.BELOW, y - line] def prune_below_find_highest(points: set, line_segment, side = Side.BELOW, lel=max): min_pt, max_pt = line_segment above_pts = [] for point in points: sidedness_res = sidedness(min_pt, max_pt, point) if sidedness_res[0] != side: above_pts.append(point) highest_above = lel(above_pts, key=lambda pt: pt[1]) return above_pts, highest_above def find_line_segment(points): min_pt = min(points, key=lambda pt: pt.x) max_pt = max(points, key=lambda pt: pt.x) return min_pt, max_pt def quick_hull(points, side=Side.BELOW, lel=max, hull=None): if hull is None: hull = set() print(len(points)) if len(points) <= 2: return hull min_pt, max_pt = find_line_segment(points) above_points, max_dist_pt = prune_below_find_highest(points, (min_pt, max_pt), side, lel) left_side = prune_below_find_highest(points, (min_pt, max_dist_pt), side, lel)[0] right_side = prune_below_find_highest(points, (max_dist_pt, max_pt), side, lel)[0] hull.add(max_dist_pt) quick_hull(left_side, side, lel, hull=hull).union(quick_hull(right_side, side, lel, hull=hull)) return hull points = {Point(2, 5), Point(3, 1), Point(3, 4), Point(9, 4), Point(1, 5), Point(5, 7)} #points = {gen_point() for _ in range(10)} line_segment = find_line_segment(points) uh_hull = quick_hull(points) lh_hull = quick_hull(points, Side.ABOVE, min) hull = uh_hull.union(lh_hull) display(points, hull)