import math import random class Circle: def __init__(self, x, y, d): self.x = x self.y = y self.diameter = d self.radius = d / 2 self.abs_area = math.pi * self.radius**2 self.area = None class RandomCircle: def __init__(self, boundaries, size, *, precision=8): if boundaries[1] < size[0] or boundaries[1] < size[1]: raise ValueError("Boundary size should be higher than minimum/maximum circle size.") self.diameter = random.uniform(size[0], size[1]) self.radius = self.diameter / 2 self.abs_area = math.pi * self.radius**2 self.area = None min_xy = boundaries[0] + self.radius max_xy = boundaries[1] - self.radius self.x = random.uniform(min_xy, max_xy) self.y = random.uniform(min_xy, max_xy) if precision == 0: self.x = int(self.x) self.y = int(self.y) self.diameter = int(self.diameter) self.radius = int(self.radius) self.abs_area = int(self.abs_area) elif precision > 0: self.x = round(self.x, precision) self.y = round(self.y, precision) self.diameter = round(self.diameter, precision) self.radius = round(self.radius, precision) self.abs_area = round(self.abs_area, precision) # ignore the shadowed variables, the functions aren't supposed to access global variables anyways # noinspection PyShadowingNames def detect_overlaps(circles): overlaps = {chr(ord("a") + i): [] for i in range(len(circles))} for c1_id, c1 in tuple(circles.items()): isinner = False for c2_id, c2 in tuple(circles.items()): if c1_id == c2_id: continue distance = math.hypot(c2.x - c1.x, c2.y - c1.y) if distance >= c1.radius + c2.radius: pass elif c1.radius + distance <= c2.radius: isinner = True break else: overlaps[c1_id].append(c2_id) if isinner: del overlaps[c1_id] return overlaps # noinspection PyShadowingNames def calculate_overlap_points(circles, overlaps): overlap_points = {c1_indx: {} for c1_indx in tuple(overlaps.keys())} for c1_indx, o_list in tuple(overlaps.items()): if len(o_list) == 0: del overlap_points[c1_indx] else: overlap_points[c1_indx] = {c2_indx: [] for c2_indx in overlaps[c1_indx]} for c1_indx, o_dict in tuple(overlap_points.items()): for c2_indx, o_points in tuple(o_dict.items()): c1 = circles[c1_indx] c2 = circles[c2_indx] d = math.hypot(c2.x - c1.x, c2.y - c1.y) ex = (c2.x - c1.x) / d ey = (c2.y - c1.y) / d x = (c1.radius**2 - c2.radius**2 + d**2) / 2 * d y = math.sqrt(c1.radius**2 - x**2) p1 = ( c1.x + x * ex - y * ey, c1.y + x * ey + y * ex ) p2 = ( c1.x + x * ex + y * ey, c1.y + x * ey - y * ex ) o_points.extend((p1, p2)) return overlap_points # noinspection PyShadowingNames def calculate_circle_area(circles, overlaps): for c_indx, c in tuple(circles.items()): if c_indx not in overlaps: c.area = 0 elif len(overlaps[c_indx]) == 0: c.area = c.abs_area # noinspection PyShadowingNames def debug_print(circles, overlaps, overlap_points): for c_indx, c in tuple(circles.items()): upper_c_indx = c_indx.upper() print(f"Circle {upper_c_indx}:") print(f"- X/Y Coordinates: {c.x}, {c.y}") print(f"- Radius: {c.radius}") print(f"- Absolute Area: {c.abs_area}") area = "???" if c.area is None else c.area print(f"- Area: {area}") if c_indx not in overlaps: print(f"Circle {upper_c_indx} is inside another circle.") elif len(overlaps[c_indx]) == 0: print(f"Circle {upper_c_indx} doesn't overlap.") else: print(f"Circle {upper_c_indx} overlaps with:") for c2_indx, o_points in tuple(overlap_points[c_indx].items()): print(f"- Circle {c2_indx.upper()} at X/Y points:") print(f"\t- {o_points[0][0]}, {o_points[0][1]}") print(f"\t- {o_points[1][0]}, {o_points[1][1]}") print("") if __name__ == '__main__': circles = {chr(ord("a") + i): RandomCircle((0, 1), (0.1, 0.5)) for i in range(5)} overlaps = detect_overlaps(circles) overlap_points = calculate_overlap_points(circles, overlaps) calculate_circle_area(circles, overlaps) debug_print(circles, overlaps, overlap_points)