mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-11-23 21:11:08 +00:00
adding a geometry module (#11138)
* adding a geometry module * fixing errors and adding type hints * Create code_review_feedback.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * implementing suggestions * fixing ruff errors * Update geometry/code_review_feedback.py * Update geometry/code_review_feedback.py * Update geometry/geometry.py * Apply suggestions from code review * Delete geometry/code_review_feedback.py * Update geometry/geometry.py * Update geometry/geometry.py --------- Co-authored-by: Christian Clauss <cclauss@me.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5f61af4fbb
commit
3999abfea3
259
geometry/geometry.py
Normal file
259
geometry/geometry.py
Normal file
|
@ -0,0 +1,259 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from types import NoneType
|
||||
from typing import Self
|
||||
|
||||
# Building block classes
|
||||
|
||||
|
||||
@dataclass
|
||||
class Angle:
|
||||
"""
|
||||
An Angle in degrees (unit of measurement)
|
||||
|
||||
>>> Angle()
|
||||
Angle(degrees=90)
|
||||
>>> Angle(45.5)
|
||||
Angle(degrees=45.5)
|
||||
>>> Angle(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: degrees must be a numeric value between 0 and 360.
|
||||
>>> Angle(361)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: degrees must be a numeric value between 0 and 360.
|
||||
"""
|
||||
|
||||
degrees: float = 90
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.degrees, (int, float)) or not 0 <= self.degrees <= 360:
|
||||
raise TypeError("degrees must be a numeric value between 0 and 360.")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Side:
|
||||
"""
|
||||
A side of a two dimensional Shape such as Polygon, etc.
|
||||
adjacent_sides: a list of sides which are adjacent to the current side
|
||||
angle: the angle in degrees between each adjacent side
|
||||
length: the length of the current side in meters
|
||||
|
||||
>>> Side(5)
|
||||
Side(length=5, angle=Angle(degrees=90), next_side=None)
|
||||
>>> Side(5, Angle(45.6))
|
||||
Side(length=5, angle=Angle(degrees=45.6), next_side=None)
|
||||
>>> Side(5, Angle(45.6), Side(1, Angle(2))) # doctest: +ELLIPSIS
|
||||
Side(length=5, angle=Angle(degrees=45.6), next_side=Side(length=1, angle=Angle(d...
|
||||
"""
|
||||
|
||||
length: float
|
||||
angle: Angle = field(default_factory=Angle)
|
||||
next_side: Side | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.length, (int, float)) or self.length <= 0:
|
||||
raise TypeError("length must be a positive numeric value.")
|
||||
if not isinstance(self.angle, Angle):
|
||||
raise TypeError("angle must be an Angle object.")
|
||||
if not isinstance(self.next_side, (Side, NoneType)):
|
||||
raise TypeError("next_side must be a Side or None.")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Ellipse:
|
||||
"""
|
||||
A geometric Ellipse on a 2D surface
|
||||
|
||||
>>> Ellipse(5, 10)
|
||||
Ellipse(major_radius=5, minor_radius=10)
|
||||
>>> Ellipse(5, 10) is Ellipse(5, 10)
|
||||
False
|
||||
>>> Ellipse(5, 10) == Ellipse(5, 10)
|
||||
True
|
||||
"""
|
||||
|
||||
major_radius: float
|
||||
minor_radius: float
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
"""
|
||||
>>> Ellipse(5, 10).area
|
||||
157.07963267948966
|
||||
"""
|
||||
return math.pi * self.major_radius * self.minor_radius
|
||||
|
||||
@property
|
||||
def perimeter(self) -> float:
|
||||
"""
|
||||
>>> Ellipse(5, 10).perimeter
|
||||
47.12388980384689
|
||||
"""
|
||||
return math.pi * (self.major_radius + self.minor_radius)
|
||||
|
||||
|
||||
class Circle(Ellipse):
|
||||
"""
|
||||
A geometric Circle on a 2D surface
|
||||
|
||||
>>> Circle(5)
|
||||
Circle(radius=5)
|
||||
>>> Circle(5) is Circle(5)
|
||||
False
|
||||
>>> Circle(5) == Circle(5)
|
||||
True
|
||||
>>> Circle(5).area
|
||||
78.53981633974483
|
||||
>>> Circle(5).perimeter
|
||||
31.41592653589793
|
||||
"""
|
||||
|
||||
def __init__(self, radius: float) -> None:
|
||||
super().__init__(radius, radius)
|
||||
self.radius = radius
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Circle(radius={self.radius})"
|
||||
|
||||
@property
|
||||
def diameter(self) -> float:
|
||||
"""
|
||||
>>> Circle(5).diameter
|
||||
10
|
||||
"""
|
||||
return self.radius * 2
|
||||
|
||||
def max_parts(self, num_cuts: float) -> float:
|
||||
"""
|
||||
Return the maximum number of parts that circle can be divided into if cut
|
||||
'num_cuts' times.
|
||||
|
||||
>>> circle = Circle(5)
|
||||
>>> circle.max_parts(0)
|
||||
1.0
|
||||
>>> circle.max_parts(7)
|
||||
29.0
|
||||
>>> circle.max_parts(54)
|
||||
1486.0
|
||||
>>> circle.max_parts(22.5)
|
||||
265.375
|
||||
>>> circle.max_parts(-222)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: num_cuts must be a positive numeric value.
|
||||
>>> circle.max_parts("-222")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: num_cuts must be a positive numeric value.
|
||||
"""
|
||||
if not isinstance(num_cuts, (int, float)) or num_cuts < 0:
|
||||
raise TypeError("num_cuts must be a positive numeric value.")
|
||||
return (num_cuts + 2 + num_cuts**2) * 0.5
|
||||
|
||||
|
||||
@dataclass
|
||||
class Polygon:
|
||||
"""
|
||||
An abstract class which represents Polygon on a 2D surface.
|
||||
|
||||
>>> Polygon()
|
||||
Polygon(sides=[])
|
||||
"""
|
||||
|
||||
sides: list[Side] = field(default_factory=list)
|
||||
|
||||
def add_side(self, side: Side) -> Self:
|
||||
"""
|
||||
>>> Polygon().add_side(Side(5))
|
||||
Polygon(sides=[Side(length=5, angle=Angle(degrees=90), next_side=None)])
|
||||
"""
|
||||
self.sides.append(side)
|
||||
return self
|
||||
|
||||
def get_side(self, index: int) -> Side:
|
||||
"""
|
||||
>>> Polygon().get_side(0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> Polygon().add_side(Side(5)).get_side(-1)
|
||||
Side(length=5, angle=Angle(degrees=90), next_side=None)
|
||||
"""
|
||||
return self.sides[index]
|
||||
|
||||
def set_side(self, index: int, side: Side) -> Self:
|
||||
"""
|
||||
>>> Polygon().set_side(0, Side(5))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list assignment index out of range
|
||||
>>> Polygon().add_side(Side(5)).set_side(0, Side(10))
|
||||
Polygon(sides=[Side(length=10, angle=Angle(degrees=90), next_side=None)])
|
||||
"""
|
||||
self.sides[index] = side
|
||||
return self
|
||||
|
||||
|
||||
class Rectangle(Polygon):
|
||||
"""
|
||||
A geometric rectangle on a 2D surface.
|
||||
|
||||
>>> rectangle_one = Rectangle(5, 10)
|
||||
>>> rectangle_one.perimeter()
|
||||
30
|
||||
>>> rectangle_one.area()
|
||||
50
|
||||
"""
|
||||
|
||||
def __init__(self, short_side_length: float, long_side_length: float) -> None:
|
||||
super().__init__()
|
||||
self.short_side_length = short_side_length
|
||||
self.long_side_length = long_side_length
|
||||
self.post_init()
|
||||
|
||||
def post_init(self) -> None:
|
||||
"""
|
||||
>>> Rectangle(5, 10) # doctest: +NORMALIZE_WHITESPACE
|
||||
Rectangle(sides=[Side(length=5, angle=Angle(degrees=90), next_side=None),
|
||||
Side(length=10, angle=Angle(degrees=90), next_side=None)])
|
||||
"""
|
||||
self.short_side = Side(self.short_side_length)
|
||||
self.long_side = Side(self.long_side_length)
|
||||
super().add_side(self.short_side)
|
||||
super().add_side(self.long_side)
|
||||
|
||||
def perimeter(self) -> float:
|
||||
return (self.short_side.length + self.long_side.length) * 2
|
||||
|
||||
def area(self) -> float:
|
||||
return self.short_side.length * self.long_side.length
|
||||
|
||||
|
||||
@dataclass
|
||||
class Square(Rectangle):
|
||||
"""
|
||||
a structure which represents a
|
||||
geometrical square on a 2D surface
|
||||
>>> square_one = Square(5)
|
||||
>>> square_one.perimeter()
|
||||
20
|
||||
>>> square_one.area()
|
||||
25
|
||||
"""
|
||||
|
||||
def __init__(self, side_length: float) -> None:
|
||||
super().__init__(side_length, side_length)
|
||||
|
||||
def perimeter(self) -> float:
|
||||
return super().perimeter()
|
||||
|
||||
def area(self) -> float:
|
||||
return super().area()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
__import__("doctest").testmod()
|
Loading…
Reference in New Issue
Block a user