From 32abbf63fa5378f0ca48c5d603a730d8c1fff139 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Thu, 15 Aug 2019 10:36:51 -0400 Subject: [PATCH 1/7] divide and conquer and brute force algorithms for array-inversions counting --- divide_and_conquer/inversions.py | 148 +++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 divide_and_conquer/inversions.py diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py new file mode 100644 index 000000000000..59ad118a55e4 --- /dev/null +++ b/divide_and_conquer/inversions.py @@ -0,0 +1,148 @@ +from __future__ import print_function, absolute_import, division + +""" +Given an array-like data structure A[1..n], how many pairs +(i, j) for all 1 <= i < j <= n such that A[i] > A[j]? These pairs are +called inversions. Counting the number of such inversions in an array-like +object is the important. Among other things, counting inversions can help +us determine how close a given array is to being sorted + +In this implementation, I provide two algorithms, a divide-and-conquer +algorithm which runs in nlogn and the brute-force n^2 algorithm. + +""" + + +def count_inversions_bf(arr): + """ + Counts the number of inversions using a a naive brute-force algorithm + + Parameters + ---------- + arr: arr: array-like, the list containing the items for which the number + of inversions is desired. The elements of `arr` must be comparable. + + Returns + ------- + num_inversions: The total number of inversions in `arr` + + """ + + num_inversions = 0 + n = len(arr) + + for i in range(n-1): + for j in range(i + 1, n): + if arr[i] > arr[j]: + num_inversions += 1 + + return num_inversions + + +def count_inversions_recursive(arr): + """ + Counts the number of inversions using a divide-and-conquer algorithm + + Parameters + ----------- + arr: array-like, the list containing the items for which the number + of inversions is desired. The elements of `arr` must be comparable. + + Returns + ------- + C: a sorted copy of `arr`. + num_inversions: int, the total number of inversions in 'arr' + + """ + if len(arr) <= 1: + return arr, 0 + else: + mid = len(arr)//2 + P = arr[0:mid] + Q = arr[mid:] + + A, inversion_p = count_inversions_recursive(P) + B, inversions_q = count_inversions_recursive(Q) + C, cross_inversions = _count_cross_inversions(A, B) + + num_inversions = inversion_p + inversions_q + cross_inversions + return C, num_inversions + + +def _count_cross_inversions(P, Q): + """ + Counts the inversions across two sorted arrays. + And combine the two arrays into one sorted array + + For all 1<= i<=len(P) and for all 1 <= j <= len(Q), + if P[i] > Q[j], then (i, j) is a cross inversion + + Parameters + ---------- + P: array-like, sorted in non-decreasing order + Q: array-like, sorted in non-decreasing order + + Returns + ------ + R: array-like, a sorted array of the elements of `P` and `Q` + num_inversion: int, the number of inversions across `P` and `Q` + + """ + + R = [] + i = j = num_inversion = 0 + while i < len(P) and j < len(Q): + if P[i] > Q[j]: + # if P[1] is > Q[j], then P[k] for all k > i, P[K] > Q[j] + # and therefore are inversions. This claim emerges from the + # property that P is sorted. + num_inversion += (len(P) - i) + R.append(Q[j]) + j += 1 + else: + R.append(P[i]) + i += 1 + + if i < len(P): + R.extend(P[i:]) + else: + R.extend(Q[j:]) + + return R, num_inversion + + +def main(): + arr_1 = [10, 2, 1, 5, 5, 2, 11] + + # this arr has 8 inversions: + # (10, 2), (10, 1), (10, 5), (10, 5), (10, 2), (2, 1), (5, 2), (5, 2) + + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 8 + + print("number of inversions = ", num_inversions_bf) + + # testing an array with zero inversion (a sorted arr_1) + + arr_1.sort() + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 0 + print("number of inversions = ", num_inversions_bf) + + # an empty list should also have zero inversions + arr_1 = [] + num_inversions_bf = count_inversions_bf(arr_1) + _, num_inversions_recursive = count_inversions_recursive(arr_1) + + assert num_inversions_bf == num_inversions_recursive == 0 + print("number of inversions = ", num_inversions_bf) + + +if __name__ == "__main__": + main() + + From 2dc12654f5ee4dae1872c6430451ff08efc9050f Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Thu, 15 Aug 2019 10:42:31 -0400 Subject: [PATCH 2/7] divide and conquer and brute force algorithms for array-inversions counting --- divide_and_conquer/inversions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index 59ad118a55e4..089919670120 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -93,8 +93,8 @@ def _count_cross_inversions(P, Q): i = j = num_inversion = 0 while i < len(P) and j < len(Q): if P[i] > Q[j]: - # if P[1] is > Q[j], then P[k] for all k > i, P[K] > Q[j] - # and therefore are inversions. This claim emerges from the + # if P[1] > Q[j], then P[k] > Q[k] for all i < k <= len(P) + # These are all inversions. The claim emerges from the # property that P is sorted. num_inversion += (len(P) - i) R.append(Q[j]) From 913dbb0063dcb98af2e42aedd94923e3dafa39b6 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Thu, 15 Aug 2019 11:02:49 -0400 Subject: [PATCH 3/7] divide and conquer and brute force algorithms for array-inversions counting --- divide_and_conquer/inversions.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/divide_and_conquer/inversions.py b/divide_and_conquer/inversions.py index 089919670120..527741cad3b7 100644 --- a/divide_and_conquer/inversions.py +++ b/divide_and_conquer/inversions.py @@ -26,6 +26,15 @@ def count_inversions_bf(arr): ------- num_inversions: The total number of inversions in `arr` + Examples + --------- + + >>> count_inversions_bf([1, 4, 2, 4, 1]) + 4 + >>> count_inversions_bf([1, 1, 2, 4, 4]) + 0 + >>> count_inversions_bf([]) + 0 """ num_inversions = 0 @@ -53,6 +62,15 @@ def count_inversions_recursive(arr): C: a sorted copy of `arr`. num_inversions: int, the total number of inversions in 'arr' + Examples + -------- + + >>> count_inversions_recursive([1, 4, 2, 4, 1]) + ([1, 1, 2, 4, 4], 4) + >>> count_inversions_recursive([1, 1, 2, 4, 4]) + ([1, 1, 2, 4, 4], 0) + >>> count_inversions_recursive([]) + ([], 0) """ if len(arr) <= 1: return arr, 0 @@ -87,6 +105,13 @@ def _count_cross_inversions(P, Q): R: array-like, a sorted array of the elements of `P` and `Q` num_inversion: int, the number of inversions across `P` and `Q` + Examples + -------- + + >>> _count_cross_inversions([1, 2, 3], [0, 2, 5]) + ([0, 1, 2, 2, 3, 5], 4) + >>> _count_cross_inversions([1, 2, 3], [3, 4, 5]) + ([1, 2, 3, 3, 4, 5], 0) """ R = [] From 61e4f3395f155d7ecb7c1f3aba5f2f6c2bebe1a0 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sat, 17 Aug 2019 00:31:35 -0400 Subject: [PATCH 4/7] a naive and divide-and-conquer algorithms for the convex-hull problem --- divide_and_conquer/convex_hull.py | 412 ++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 divide_and_conquer/convex_hull.py diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py new file mode 100644 index 000000000000..4d5a511a2480 --- /dev/null +++ b/divide_and_conquer/convex_hull.py @@ -0,0 +1,412 @@ +from __future__ import print_function, absolute_import, division + +from numbers import Number +""" +Explain the convex hull problem + +code out the brute-force, divide and conquer, javis march etc + +""" + + +class Point: + """ + Defines a 2-d point for use by all convex-hull algorithms. + + Parameters + ---------- + x: an int or a float, the x-coordinate of the 2-d point + y: an int or a float, the y-coordinate of the 2-d point + + Examples + -------- + >>> Point(1, 2) + (1, 2) + >>> Point("1", "2") + (1.0, 2.0) + >>> Point(1, 2) > Point(0, 1) + True + >>> Point(1, 1) == Point(1, 1) + True + >>> Point(-0.5, 1) == Point(0.5, 1) + False + >>> Point("pi", "e") + Traceback (most recent call last): + ... + ValueError: x and y must be both numeric types but got , instead + """ + + def __init__(self, x, y): + if not (isinstance(x, Number) and isinstance(y, Number)): + try: + x, y = float(x), float(y) + except ValueError as e: + e.args = ("x and y must be both numeric types " + "but got {}, {} instead".format(str(type(x)), str(type(y))), ) + raise + + self.x = x + self.y = y + + def __eq__(self, other): + return self.x == other.x and self.y == other.y + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + if self.x > other.x: + return True + elif self.x == other.x: + return self.y > other.y + return False + + def __lt__(self, other): + return not self > other + + def __ge__(self, other): + if self.x > other.x: + return True + elif self.x == other.x: + return self.y >= other.y + return False + + def __le__(self, other): + if self.x < other.x: + return True + elif self.x == other.x: + return self.y <= other.y + return False + + def __repr__(self): + return "({}, {})".format(self.x, self.y) + + def __hash__(self): + return hash(self.x) + + +def _construct_points(list_of_tuples): + """ + constructs a list of points from an array-like object of numbers + + Arguments + --------- + + list_of_tuples: array-like object of type numbers. Acceptable types so far + are lists, tuples and sets. + + Returns + -------- + points: a list where each item is of type Point. This contains only objects + which can be converted into a Point. + + Examples + ------- + >>> _construct_points([[1, 1], [2, -1], [0.3, 4]]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points(([1, 1], [2, -1], [0.3, 4])) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([(1, 1), (2, -1), (0.3, 4)]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([[1, 1], (2, -1), [0.3, 4]]) + [(1, 1), (2, -1), (0.3, 4)] + >>> _construct_points([1, 2]) + Ignoring deformed point 1. All points must have at least 2 coordinates. + Ignoring deformed point 2. All points must have at least 2 coordinates. + [] + >>> _construct_points([]) + [] + >>> _construct_points(None) + [] + """ + + points = [] + if list_of_tuples: + for p in list_of_tuples: + try: + points.append(Point(p[0], p[1])) + except (IndexError, TypeError): + print("Ignoring deformed point {}. All points" + " must have at least 2 coordinates.".format(p)) + return points + + +def _validate_input(points): + """ + validates an input instance before a convex-hull algorithms uses it + + Parameters + --------- + points: array-like, the list of 2d points to validate before using with + a convex-hull algorithm. The elements of points must be either lists, tuples or + Points. + + Returns + ------- + points: array_like, an iterable of all well-defined Points constructed passed in. + + + Exception + --------- + ValueError: if points is empty or None, or if a wrong data structure like a scalar is passed + + TypeError: if an iterable but non-indexable object (eg. set) is passed. + + + Examples + ------- + >>> _validate_input([[1, 2]]) + [(1, 2)] + >>> _validate_input([(1, 2)]) + [(1, 2)] + >>> _validate_input([]) + Traceback (most recent call last): + ... + ValueError: Expecting a list of points but got [] + >>> _validate_input(1) + Traceback (most recent call last): + ... + ValueError: Expecting an iterable object but got an non-iterable type 1 + """ + + if not points: + raise ValueError("Expecting a list of points but got {}".format(points)) + try: + if hasattr(points, "__iter__") and not isinstance(points[0], Point): + if isinstance(points[0], (list, tuple)): + points = _construct_points(points) + else: + raise ValueError("Expecting an iterable of type Point, list or tuple. " + "Found objects of type {} instead" + .format(["point", "list", "tuple"], type(points[0]))) + elif not hasattr(points, "__iter__"): + raise ValueError("Expecting an iterable object " + "but got an non-iterable type {}".format(points)) + except TypeError as e: + print("Expecting an iterable of type Point, list or tuple.") + raise + + return points + + +def convex_hull_bf(points): + """ + Constructs the convex hull of a set of 2D points using a brute force algorithm. + The algorithm basically considers all combinations of points (i, j) and uses the + definition of convexity to determine whether (i, j) is part of the convex hull or not. + (i, j) is part of the convex hull if and only iff there are no points on both sides + of the line segment connecting the ij, and there is no point k such that k is on either end + of the ij. + + Runtime: O(n^3) - definitely horrible + + Parameters + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Returns + ------ + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + See Also + -------- + convex_hull_recursive, + + Examples + --------- + >>> convex_hull_bf([[0, 0], [1, 0], [10, 1]]) + [(0, 0), (1, 0), (10, 1)] + >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) + [(0, 0), (10, 0)] + >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + [(-1, -1), (-1, 1), (1, -1), (1, 1)] + >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + """ + + points = _validate_input(points) + n = len(points) + points.sort() + convex_set = set() + + for i in range(n-1): + for j in range(i + 1, n): + points_left_of_ij = points_right_of_ij = False + ij_part_of_convex_hull = True + for k in range(n): + + if k != i and k != j: + det_k = _det(points[i], points[j], points[k]) + + if det_k > 0: + points_left_of_ij = True + elif det_k < 0: + points_right_of_ij = True + else: + # point[i], point[j], point[k] all lie on a straight line + # if point[k] is to the left of point[i] or it's to the + # right of point[j], then point[i], point[j] cannot be + # part of the convex hull of A + if points[k] < points[i] or points[k] > points[j]: + ij_part_of_convex_hull = False + break + + if points_left_of_ij and points_right_of_ij: + ij_part_of_convex_hull = False + break + + if ij_part_of_convex_hull: + convex_set.update([points[i], points[j]]) + + return sorted(convex_set) + + +def _det(a, b, c): + """ + Computes the sign perpendicular distance of a 2d point c from a line segment + ab. The sign indicates the direction of c relative to ab. + A Positive value means c is above ab (to the left), while a negative value + means c is below ab (to the right). 0 means all three points are on a straight line. + + As a side note, 0.5 * abs|det| is the area of triangle abc + + Parameters + ---------- + a: point, the point on the left end of line segment ab + b: point, the point on the right end of line segment ab + c: point, the point for which the direction and location is desired. + + Returns + -------- + det: float, abs(det) is the distance of c from ab. The sign + indicates which side of line segment ab c is. det is computed as + (a_xb_y + c_xa_y + b_xc_y) - (a_yb_x + c_ya_x + b_yc_x) + + Examples + ---------- + >>> _det(Point(1, 1), Point(1, 2), Point(1, 5)) + 0 + >>> _det(Point(0, 0), Point(10, 0), Point(0, 10)) + 100 + >>> _det(Point(0, 0), Point(10, 0), Point(0, -10)) + -100 + """ + + det = (a.x * b.y + b.x * c.y + c.x * a.y) - (a.y * b.x + b.y * c.x + c.y * a.x) + return det + + +def convex_hull_recursive(points): + """ + Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy + The algorithm exploits the geometric properties of the problem by repeatedly partitioning + the set of points into smaller hulls, and finding the convex hull of these smaller hulls. + The union of the convex hull from smaller hulls is the solution to the convex hull of the larger problem. + + Parameter + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Runtime: O(n log n) + + Returns + ------- + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + Examples + --------- + >>> convex_hull_bf([[0, 0], [1, 0], [10, 1]]) + [(0, 0), (1, 0), (10, 1)] + >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) + [(0, 0), (10, 0)] + >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + [(-1, -1), (-1, 1), (1, -1), (1, 1)] + >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + + """ + points = _validate_input(points) + n = len(points) + points.sort() + + # divide all the points into an upper hull and a lower hull + # the left most point and the right most point are definitely + # members of the convex hull by definition. + # use these two anchors to divide all the points into two hulls, + # an upper hull and a lower hull. + + # all points to the left (above) the line joining the extreme points belong to the upper hull + # all points to the right (below) the line joining the extreme points below to the lower hull + # ignore all points on the line joining the extreme points since they cannot be part of the + # convex hull + + left_most_point = points[0] + right_most_point = points[n-1] + + convex_set = {left_most_point, right_most_point} + upperhull = [] + lowerhull = [] + + for i in range(1, n-1): + det = _det(left_most_point, right_most_point, points[i]) + + if det > 0: + upperhull.append(points[i]) + elif det < 0: + lowerhull.append(points[i]) + + _construct_hull(upperhull, left_most_point, right_most_point, convex_set) + _construct_hull(lowerhull, right_most_point, left_most_point, convex_set) + + return sorted(convex_set) + + +def _construct_hull(points, left, right, convex_set): + """ + + Parameters + --------- + points: list or None, the hull of points from which to choose the next convex-hull point + left: Point, the point to the left of line segment joining left and right + right: The point to the right of the line segment joining left and right + convex_set: set, the current convex-hull. The state of convex-set gets updated by this function + + Note + ---- + For the line segment 'ab', 'a' is on the left and 'b' on the right. + but the reverse is true for the line segment 'ba'. + + Returns + ------- + Nothing, only updates the state of convex-set + """ + if points: + extreme_point = None + extreme_point_distance = float('-inf') + candidate_points = [] + + for p in points: + det = _det(left, right, p) + + if det > 0: + candidate_points.append(p) + + if det > extreme_point_distance: + extreme_point_distance = det + extreme_point = p + + if extreme_point: + _construct_hull(candidate_points, left, extreme_point, convex_set) + convex_set.add(p) + _construct_hull(candidate_points, extreme_point, right, convex_set) + + +def convex_hull_javis_match(points): + # TODO: will implement tomorrow, another Olog n algorithm + pass + + + +convex_hull_bf([[-1, -1], [0.5, 0.5],[1, 1], [-0.75, 1]]) \ No newline at end of file From c6c7fab516beda56d4e5386ee7eab8fdd9dcd222 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sat, 17 Aug 2019 08:51:23 -0400 Subject: [PATCH 5/7] two convex-hull algorithms, a divide-and-conquer and a naive algorithm --- divide_and_conquer/convex_hull.py | 121 ++++++++++++++++++------------ 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index 4d5a511a2480..b500ae9c3bee 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -2,9 +2,17 @@ from numbers import Number """ -Explain the convex hull problem +The convex hull problem is problem of finding all the vertices of convex polygon, P of +a set of points in a plane such that all the points are either on the vertices of P or +inside P. TH convex hull problem has several applications in geometrical problems, +computer graphics and game development. -code out the brute-force, divide and conquer, javis march etc +Two algorithms have been implemented for the convex hull problem here. +1. A brute-force algorithm which runs in O(n^3) +2. A divide-and-conquer algorithm which runs in O(n^3) + +There are other several other algorithms for the convex hull problem +which have not been implemented here, yet. """ @@ -137,7 +145,7 @@ def _validate_input(points): Parameters --------- - points: array-like, the list of 2d points to validate before using with + points: array-like, the 2d points to validate before using with a convex-hull algorithm. The elements of points must be either lists, tuples or Points. @@ -150,7 +158,8 @@ def _validate_input(points): --------- ValueError: if points is empty or None, or if a wrong data structure like a scalar is passed - TypeError: if an iterable but non-indexable object (eg. set) is passed. + TypeError: if an iterable but non-indexable object (eg. dictionary) is passed. + The exception to this a set which we'll convert to a list before using Examples @@ -159,6 +168,8 @@ def _validate_input(points): [(1, 2)] >>> _validate_input([(1, 2)]) [(1, 2)] + >>> _validate_input([Point(2, 1), Point(-1, 2)]) + [(2, 1), (-1, 2)] >>> _validate_input([]) Traceback (most recent call last): ... @@ -171,6 +182,10 @@ def _validate_input(points): if not points: raise ValueError("Expecting a list of points but got {}".format(points)) + + if isinstance(points, set): + points = list(points) + try: if hasattr(points, "__iter__") and not isinstance(points[0], Point): if isinstance(points[0], (list, tuple)): @@ -189,6 +204,41 @@ def _validate_input(points): return points +def _det(a, b, c): + """ + Computes the sign perpendicular distance of a 2d point c from a line segment + ab. The sign indicates the direction of c relative to ab. + A Positive value means c is above ab (to the left), while a negative value + means c is below ab (to the right). 0 means all three points are on a straight line. + + As a side note, 0.5 * abs|det| is the area of triangle abc + + Parameters + ---------- + a: point, the point on the left end of line segment ab + b: point, the point on the right end of line segment ab + c: point, the point for which the direction and location is desired. + + Returns + -------- + det: float, abs(det) is the distance of c from ab. The sign + indicates which side of line segment ab c is. det is computed as + (a_xb_y + c_xa_y + b_xc_y) - (a_yb_x + c_ya_x + b_yc_x) + + Examples + ---------- + >>> _det(Point(1, 1), Point(1, 2), Point(1, 5)) + 0 + >>> _det(Point(0, 0), Point(10, 0), Point(0, 10)) + 100 + >>> _det(Point(0, 0), Point(10, 0), Point(0, -10)) + -100 + """ + + det = (a.x * b.y + b.x * c.y + c.x * a.y) - (a.y * b.x + b.y * c.x + c.y * a.x) + return det + + def convex_hull_bf(points): """ Constructs the convex hull of a set of 2D points using a brute force algorithm. @@ -262,41 +312,6 @@ def convex_hull_bf(points): return sorted(convex_set) -def _det(a, b, c): - """ - Computes the sign perpendicular distance of a 2d point c from a line segment - ab. The sign indicates the direction of c relative to ab. - A Positive value means c is above ab (to the left), while a negative value - means c is below ab (to the right). 0 means all three points are on a straight line. - - As a side note, 0.5 * abs|det| is the area of triangle abc - - Parameters - ---------- - a: point, the point on the left end of line segment ab - b: point, the point on the right end of line segment ab - c: point, the point for which the direction and location is desired. - - Returns - -------- - det: float, abs(det) is the distance of c from ab. The sign - indicates which side of line segment ab c is. det is computed as - (a_xb_y + c_xa_y + b_xc_y) - (a_yb_x + c_ya_x + b_yc_x) - - Examples - ---------- - >>> _det(Point(1, 1), Point(1, 2), Point(1, 5)) - 0 - >>> _det(Point(0, 0), Point(10, 0), Point(0, 10)) - 100 - >>> _det(Point(0, 0), Point(10, 0), Point(0, -10)) - -100 - """ - - det = (a.x * b.y + b.x * c.y + c.x * a.y) - (a.y * b.x + b.y * c.x + c.y * a.x) - return det - - def convex_hull_recursive(points): """ Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy @@ -317,13 +332,13 @@ def convex_hull_recursive(points): Examples --------- - >>> convex_hull_bf([[0, 0], [1, 0], [10, 1]]) + >>> convex_hull_recursive([[0, 0], [1, 0], [10, 1]]) [(0, 0), (1, 0), (10, 1)] - >>> convex_hull_bf([[0, 0], [1, 0], [10, 0]]) + >>> convex_hull_recursive([[0, 0], [1, 0], [10, 0]]) [(0, 0), (10, 0)] - >>> convex_hull_bf([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) + >>> convex_hull_recursive([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], [-0.75, 1]]) [(-1, -1), (-1, 1), (1, -1), (1, 1)] - >>> convex_hull_bf([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) + >>> convex_hull_recursive([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)]) [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] """ @@ -399,14 +414,24 @@ def _construct_hull(points, left, right, convex_set): if extreme_point: _construct_hull(candidate_points, left, extreme_point, convex_set) - convex_set.add(p) + convex_set.add(extreme_point) _construct_hull(candidate_points, extreme_point, right, convex_set) -def convex_hull_javis_match(points): - # TODO: will implement tomorrow, another Olog n algorithm - pass +def main(): + + points = [(0, 3), (2, 2), (1, 1), (2, 1), + (3, 0), (0, 0), (3, 3), (2, -1), + (2, -4), (1, -3) + ] + # the convex set of points is + # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] + results_recursive = convex_hull_recursive(points) + results_bf = convex_hull_bf(points) + assert results_bf == results_recursive + print(results_bf) -convex_hull_bf([[-1, -1], [0.5, 0.5],[1, 1], [-0.75, 1]]) \ No newline at end of file +if __name__ == '__main__': + main() From 6a47b781bbf460870c32f9f1dd924ff82e242cd8 Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sat, 17 Aug 2019 10:38:38 -0400 Subject: [PATCH 6/7] two convex-hull algorithms, a divide-and-conquer and a naive algorithm --- divide_and_conquer/convex_hull.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index b500ae9c3bee..f08ce24d63cb 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -50,7 +50,7 @@ def __init__(self, x, y): x, y = float(x), float(y) except ValueError as e: e.args = ("x and y must be both numeric types " - "but got {}, {} instead".format(str(type(x)), str(type(y))), ) + "but got {}, {} instead".format(type(x), type(y)), ) raise self.x = x @@ -285,7 +285,6 @@ def convex_hull_bf(points): points_left_of_ij = points_right_of_ij = False ij_part_of_convex_hull = True for k in range(n): - if k != i and k != j: det_k = _det(points[i], points[j], points[k]) @@ -419,11 +418,8 @@ def _construct_hull(points, left, right, convex_set): def main(): - - points = [(0, 3), (2, 2), (1, 1), (2, 1), - (3, 0), (0, 0), (3, 3), (2, -1), - (2, -4), (1, -3) - ] + points = [(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), + (0, 0), (3, 3), (2, -1), (2, -4), (1, -3)] # the convex set of points is # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] results_recursive = convex_hull_recursive(points) From 06d8dcf1ddb8791f0ba4293daf00e8bd1507b96e Mon Sep 17 00:00:00 2001 From: Maxwell Aladago Date: Sat, 17 Aug 2019 10:40:14 -0400 Subject: [PATCH 7/7] two convex-hull algorithms, a divide-and-conquer and a naive algorithm --- divide_and_conquer/convex_hull.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index f08ce24d63cb..f15d74ddea68 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -275,9 +275,8 @@ def convex_hull_bf(points): [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] """ - points = _validate_input(points) + points = sorted(_validate_input(points)) n = len(points) - points.sort() convex_set = set() for i in range(n-1): @@ -341,9 +340,8 @@ def convex_hull_recursive(points): [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] """ - points = _validate_input(points) + points = sorted(_validate_input(points)) n = len(points) - points.sort() # divide all the points into an upper hull and a lower hull # the left most point and the right most point are definitely