From 978414bd50ae294352e0e4d93566f49074450857 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 3 Nov 2022 01:56:30 +0300 Subject: [PATCH] Reduce the complexity of other/graham_scan.py (#7953) * Reduce the complexity of other/graham_scan.py * Lower the --max-complexity threshold in the file .flake8 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix tests * Update other/graham_scan.py Co-authored-by: Christian Clauss * Update graham_scan.py * Update other/graham_scan.py Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss --- .flake8 | 2 +- other/graham_scan.py | 150 ++++++++++++++++++++++--------------------- 2 files changed, 77 insertions(+), 75 deletions(-) diff --git a/.flake8 b/.flake8 index 834d1f63d..2f74f421d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] max-line-length = 88 # max-complexity should be 10 -max-complexity = 20 +max-complexity = 19 extend-ignore = # Formatting style for `black` E203 # Whitespace before ':' diff --git a/other/graham_scan.py b/other/graham_scan.py index 91bb6812f..8e83bfcf4 100644 --- a/other/graham_scan.py +++ b/other/graham_scan.py @@ -14,6 +14,82 @@ from math import atan2, degrees from sys import maxsize +# traversal from the lowest and the most left point in anti-clockwise direction +# if direction gets right, the previous point is not the convex hull. +class Direction(Enum): + left = 1 + straight = 2 + right = 3 + + def __repr__(self): + return f"{self.__class__.__name__}.{self.name}" + + +def angle_comparer(point: tuple[int, int], minx: int, miny: int) -> float: + """Return the angle toward to point from (minx, miny) + + :param point: The target point + minx: The starting point's x + miny: The starting point's y + :return: the angle + + Examples: + >>> angle_comparer((1,1), 0, 0) + 45.0 + + >>> angle_comparer((100,1), 10, 10) + -5.710593137499642 + + >>> angle_comparer((5,5), 2, 3) + 33.690067525979785 + """ + # sort the points accorgind to the angle from the lowest and the most left point + x, y = point + return degrees(atan2(y - miny, x - minx)) + + +def check_direction( + starting: tuple[int, int], via: tuple[int, int], target: tuple[int, int] +) -> Direction: + """Return the direction toward to the line from via to target from starting + + :param starting: The starting point + via: The via point + target: The target point + :return: the Direction + + Examples: + >>> check_direction((1,1), (2,2), (3,3)) + Direction.straight + + >>> check_direction((60,1), (-50,199), (30,2)) + Direction.left + + >>> check_direction((0,0), (5,5), (10,0)) + Direction.right + """ + x0, y0 = starting + x1, y1 = via + x2, y2 = target + via_angle = degrees(atan2(y1 - y0, x1 - x0)) + via_angle %= 360 + target_angle = degrees(atan2(y2 - y0, x2 - x0)) + target_angle %= 360 + # t- + # \ \ + # \ v + # \| + # s + # via_angle is always lower than target_angle, if direction is left. + # If they are same, it means they are on a same line of convex hull. + if target_angle > via_angle: + return Direction.left + elif target_angle == via_angle: + return Direction.straight + else: + return Direction.right + + def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: """Pure implementation of graham scan algorithm in Python @@ -57,86 +133,12 @@ def graham_scan(points: list[tuple[int, int]]) -> list[tuple[int, int]]: # remove the lowest and the most left point from points for preparing for sort points.pop(minidx) - def angle_comparer(point: tuple[int, int], minx: int, miny: int) -> float: - """Return the angle toward to point from (minx, miny) - - :param point: The target point - minx: The starting point's x - miny: The starting point's y - :return: the angle - - Examples: - >>> angle_comparer((1,1), 0, 0) - 45.0 - - >>> angle_comparer((100,1), 10, 10) - -5.710593137499642 - - >>> angle_comparer((5,5), 2, 3) - 33.690067525979785 - """ - # sort the points accorgind to the angle from the lowest and the most left point - x = point[0] - y = point[1] - angle = degrees(atan2(y - miny, x - minx)) - return angle - sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) # This insert actually costs complexity, # and you should instead add (minx, miny) into stack later. # I'm using insert just for easy understanding. sorted_points.insert(0, (minx, miny)) - # traversal from the lowest and the most left point in anti-clockwise direction - # if direction gets right, the previous point is not the convex hull. - class Direction(Enum): - left = 1 - straight = 2 - right = 3 - - def check_direction( - starting: tuple[int, int], via: tuple[int, int], target: tuple[int, int] - ) -> Direction: - """Return the direction toward to the line from via to target from starting - - :param starting: The starting point - via: The via point - target: The target point - :return: the Direction - - Examples: - >>> check_direction((1,1), (2,2), (3,3)) - Direction.straight - - >>> check_direction((60,1), (-50,199), (30,2)) - Direction.left - - >>> check_direction((0,0), (5,5), (10,0)) - Direction.right - """ - x0, y0 = starting - x1, y1 = via - x2, y2 = target - via_angle = degrees(atan2(y1 - y0, x1 - x0)) - if via_angle < 0: - via_angle += 360 - target_angle = degrees(atan2(y2 - y0, x2 - x0)) - if target_angle < 0: - target_angle += 360 - # t- - # \ \ - # \ v - # \| - # s - # via_angle is always lower than target_angle, if direction is left. - # If they are same, it means they are on a same line of convex hull. - if target_angle > via_angle: - return Direction.left - elif target_angle == via_angle: - return Direction.straight - else: - return Direction.right - stack: deque[tuple[int, int]] = deque() stack.append(sorted_points[0]) stack.append(sorted_points[1])