absfuyu 4.1.1__py3-none-any.whl → 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +4 -4
- absfuyu/__main__.py +13 -1
- absfuyu/cli/__init__.py +4 -2
- absfuyu/cli/color.py +7 -0
- absfuyu/cli/do_group.py +9 -91
- absfuyu/cli/tool_group.py +136 -0
- absfuyu/config/__init__.py +17 -34
- absfuyu/core/__init__.py +49 -0
- absfuyu/core/baseclass.py +299 -0
- absfuyu/core/baseclass2.py +165 -0
- absfuyu/core/decorator.py +67 -0
- absfuyu/core/docstring.py +163 -0
- absfuyu/core/dummy_cli.py +67 -0
- absfuyu/core/dummy_func.py +47 -0
- absfuyu/dxt/__init__.py +42 -0
- absfuyu/dxt/dictext.py +201 -0
- absfuyu/dxt/dxt_support.py +79 -0
- absfuyu/dxt/intext.py +586 -0
- absfuyu/dxt/listext.py +508 -0
- absfuyu/dxt/strext.py +530 -0
- absfuyu/{extensions → extra}/__init__.py +3 -2
- absfuyu/extra/beautiful.py +251 -0
- absfuyu/{extensions/extra → extra}/data_analysis.py +51 -82
- absfuyu/fun/__init__.py +110 -135
- absfuyu/fun/tarot.py +9 -17
- absfuyu/game/__init__.py +6 -0
- absfuyu/game/game_stat.py +6 -0
- absfuyu/game/sudoku.py +7 -1
- absfuyu/game/tictactoe.py +12 -5
- absfuyu/game/wordle.py +14 -8
- absfuyu/general/__init__.py +6 -79
- absfuyu/general/content.py +22 -36
- absfuyu/general/generator.py +17 -42
- absfuyu/general/human.py +108 -228
- absfuyu/general/shape.py +1334 -0
- absfuyu/logger.py +8 -13
- absfuyu/pkg_data/__init__.py +137 -99
- absfuyu/pkg_data/deprecated.py +133 -0
- absfuyu/pkg_data/passwordlib_lzma.pkl +0 -0
- absfuyu/sort.py +6 -130
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +44 -22
- absfuyu/tools/converter.py +82 -50
- absfuyu/tools/keygen.py +25 -30
- absfuyu/tools/obfuscator.py +246 -112
- absfuyu/tools/passwordlib.py +330 -0
- absfuyu/tools/shutdownizer.py +287 -0
- absfuyu/tools/web.py +2 -9
- absfuyu/util/__init__.py +15 -15
- absfuyu/util/api.py +10 -15
- absfuyu/util/json_method.py +7 -24
- absfuyu/util/lunar.py +3 -9
- absfuyu/util/path.py +22 -27
- absfuyu/util/performance.py +43 -67
- absfuyu/util/shorten_number.py +65 -14
- absfuyu/util/zipped.py +9 -15
- absfuyu-5.0.0.dist-info/METADATA +143 -0
- absfuyu-5.0.0.dist-info/RECORD +68 -0
- absfuyu/core.py +0 -57
- absfuyu/everything.py +0 -32
- absfuyu/extensions/beautiful.py +0 -188
- absfuyu/extensions/dev/__init__.py +0 -244
- absfuyu/extensions/dev/password_hash.py +0 -80
- absfuyu/extensions/dev/passwordlib.py +0 -258
- absfuyu/extensions/dev/project_starter.py +0 -60
- absfuyu/extensions/dev/shutdownizer.py +0 -156
- absfuyu/extensions/extra/__init__.py +0 -24
- absfuyu/fun/WGS.py +0 -134
- absfuyu/general/data_extension.py +0 -1796
- absfuyu/tools/stats.py +0 -226
- absfuyu/util/pkl.py +0 -67
- absfuyu-4.1.1.dist-info/METADATA +0 -121
- absfuyu-4.1.1.dist-info/RECORD +0 -61
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
absfuyu/general/shape.py
ADDED
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Shape
|
|
3
|
+
--------------
|
|
4
|
+
Shapes
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 19/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Polygon
|
|
14
|
+
"Triangle",
|
|
15
|
+
"Circle",
|
|
16
|
+
"Square",
|
|
17
|
+
"Rectangle",
|
|
18
|
+
"Pentagon",
|
|
19
|
+
"Hexagon",
|
|
20
|
+
"Parallelogram",
|
|
21
|
+
"Rhombus",
|
|
22
|
+
"Trapezoid",
|
|
23
|
+
# 3D Shape
|
|
24
|
+
"Cube",
|
|
25
|
+
"Cuboid",
|
|
26
|
+
"Sphere",
|
|
27
|
+
"HemiSphere",
|
|
28
|
+
"Cylinder",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Library
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
import math
|
|
35
|
+
from abc import ABC, abstractmethod
|
|
36
|
+
from typing import ClassVar, Self
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from typing import override # type: ignore
|
|
40
|
+
except ImportError:
|
|
41
|
+
from absfuyu.core.decorator import dummy_decorator as override
|
|
42
|
+
|
|
43
|
+
from absfuyu.core import BaseClass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Class
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
class Shape(BaseClass):
|
|
49
|
+
"""Shape base class"""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Polygon(ABC, Shape):
|
|
55
|
+
"""2D Shape class base"""
|
|
56
|
+
|
|
57
|
+
POLYGON_LIST: ClassVar[list[str]] = []
|
|
58
|
+
|
|
59
|
+
def __init__(self, num_of_sides: int) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Initialize a polygon with number of sides.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
num_of_sides : int
|
|
66
|
+
Number of sides of the polygon.
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
------
|
|
70
|
+
ValueError
|
|
71
|
+
If the number of sides smaller than 3.
|
|
72
|
+
"""
|
|
73
|
+
if num_of_sides <= 2:
|
|
74
|
+
raise ValueError("Number of sides must be larger than 2.")
|
|
75
|
+
|
|
76
|
+
self._num_of_sides = num_of_sides
|
|
77
|
+
|
|
78
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
79
|
+
super().__init_subclass__(*args, **kwargs)
|
|
80
|
+
# cls.POLYGON_LIST.append(cls) # append type
|
|
81
|
+
cls.POLYGON_LIST.append(cls.__name__) # append name
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def perimeter(self):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def area(self):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class EqualSidesPolygon(Polygon):
|
|
93
|
+
"""
|
|
94
|
+
Base class for polygons with equal side length
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, side: int | float, num_of_sides: int) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Initialize a polygon with equal side length.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
side : int | float
|
|
104
|
+
Length of each side.
|
|
105
|
+
|
|
106
|
+
num_of_sides : int
|
|
107
|
+
Number of sides of the polygon.
|
|
108
|
+
|
|
109
|
+
Raises
|
|
110
|
+
------
|
|
111
|
+
ValueError
|
|
112
|
+
If the side is not positive or number of sides smaller than 3.
|
|
113
|
+
"""
|
|
114
|
+
super().__init__(num_of_sides=num_of_sides)
|
|
115
|
+
|
|
116
|
+
if side <= 0:
|
|
117
|
+
raise ValueError("Side length must be a positive number.")
|
|
118
|
+
|
|
119
|
+
self.side = side
|
|
120
|
+
|
|
121
|
+
@override
|
|
122
|
+
def perimeter(self) -> int | float:
|
|
123
|
+
"""
|
|
124
|
+
Calculate the perimeter of the polygon.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
int | float
|
|
129
|
+
The perimeter of the polygon.
|
|
130
|
+
"""
|
|
131
|
+
return self.side * self._num_of_sides
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
@override
|
|
135
|
+
def area(self):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def interior_angle(self) -> float:
|
|
139
|
+
"""
|
|
140
|
+
Calculate the interior angle of the polygon.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
float
|
|
145
|
+
The interior angle in degrees.
|
|
146
|
+
"""
|
|
147
|
+
return (self._num_of_sides - 2) * 180 / self._num_of_sides
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ThreeDimensionShape(ABC, Shape):
|
|
151
|
+
SHAPE_LIST: ClassVar[list[str]] = []
|
|
152
|
+
|
|
153
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
154
|
+
super().__init_subclass__(*args, **kwargs)
|
|
155
|
+
# cls.SHAPE_LIST.append(cls) # append type
|
|
156
|
+
cls.SHAPE_LIST.append(cls.__name__) # append name
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def surface_area(self):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def volume(self):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Class - Polygon
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
class Triangle(Polygon):
|
|
170
|
+
def __init__(
|
|
171
|
+
self,
|
|
172
|
+
a: int | float,
|
|
173
|
+
b: int | float,
|
|
174
|
+
c: int | float,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Initializes a Triangle instance with three sides.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
a : int | float
|
|
182
|
+
The length of the first side.
|
|
183
|
+
|
|
184
|
+
b : int | float
|
|
185
|
+
The length of the second side.
|
|
186
|
+
|
|
187
|
+
c : int | float
|
|
188
|
+
The length of the third side.
|
|
189
|
+
|
|
190
|
+
Raises
|
|
191
|
+
------
|
|
192
|
+
ValueError
|
|
193
|
+
If any side length is not positive or if the sides do not form a valid triangle.
|
|
194
|
+
"""
|
|
195
|
+
super().__init__(num_of_sides=3)
|
|
196
|
+
|
|
197
|
+
if a <= 0 or b <= 0 or c <= 0:
|
|
198
|
+
raise ValueError("Side lengths must be positive.")
|
|
199
|
+
|
|
200
|
+
# Check for triangle inequality theorem
|
|
201
|
+
if (a + b <= c) or (a + c <= b) or (b + c <= a):
|
|
202
|
+
raise ValueError("The provided lengths do not form a valid triangle.")
|
|
203
|
+
|
|
204
|
+
self.a = a
|
|
205
|
+
self.b = b
|
|
206
|
+
self.c = c
|
|
207
|
+
|
|
208
|
+
@override
|
|
209
|
+
def perimeter(self) -> int | float:
|
|
210
|
+
"""
|
|
211
|
+
Calculates and returns the perimeter of the triangle.
|
|
212
|
+
"""
|
|
213
|
+
return self.a + self.b + self.c
|
|
214
|
+
|
|
215
|
+
@override
|
|
216
|
+
def area(self) -> int | float:
|
|
217
|
+
"""
|
|
218
|
+
Calculates and returns the area of the triangle using Heron's formula.
|
|
219
|
+
"""
|
|
220
|
+
s = self.perimeter() / 2 # Semi-perimeter
|
|
221
|
+
# Heron formula
|
|
222
|
+
res = math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
|
|
223
|
+
return res
|
|
224
|
+
|
|
225
|
+
def is_right_angled(self) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Checks if the triangle is a right-angled triangle
|
|
228
|
+
(one vertex has degree of 90) using the Pythagorean theorem.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
bool
|
|
233
|
+
``True`` if the triangle is right-angled, ``False`` otherwise.
|
|
234
|
+
"""
|
|
235
|
+
sides = sorted([self.a, self.b, self.c])
|
|
236
|
+
# return (
|
|
237
|
+
# sides[0] ** 2 + sides[1] ** 2 == sides[2] ** 2
|
|
238
|
+
# ) # Pytagorean formula
|
|
239
|
+
|
|
240
|
+
# Using ``==`` to compare floating-point numbers can be
|
|
241
|
+
# unreliable due to precision issues. Use a tolerance instead
|
|
242
|
+
return abs(sides[2] ** 2 - (sides[0] ** 2 + sides[1] ** 2)) < 1e-6
|
|
243
|
+
|
|
244
|
+
def is_equilateral(self) -> bool:
|
|
245
|
+
"""
|
|
246
|
+
Checks if the triangle is an equilateral triangle
|
|
247
|
+
(3 sides have the same length).
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
bool
|
|
252
|
+
``True`` if the triangle is equilateral, ``False`` otherwise.
|
|
253
|
+
"""
|
|
254
|
+
return self.a == self.b and self.b == self.c
|
|
255
|
+
|
|
256
|
+
def is_isosceles(self) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Checks if the triangle is an isosceles triangle
|
|
259
|
+
(at least two sides are equal).
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
bool
|
|
264
|
+
``True`` if the triangle is isosceles, ``False`` otherwise.
|
|
265
|
+
"""
|
|
266
|
+
return self.a == self.b or self.b == self.c or self.c == self.a
|
|
267
|
+
|
|
268
|
+
def triangle_type(self) -> str:
|
|
269
|
+
"""
|
|
270
|
+
Determines the type of triangle based on its sides.
|
|
271
|
+
|
|
272
|
+
Returns
|
|
273
|
+
-------
|
|
274
|
+
str
|
|
275
|
+
A string describing the type of triangle: ``"equilateral"``, ``"isosceles"``,
|
|
276
|
+
``"right-angled"``, or ``"scalene"`` if none of the other types apply.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
if self.is_equilateral():
|
|
280
|
+
return "equilateral"
|
|
281
|
+
elif self.is_isosceles():
|
|
282
|
+
if self.is_right_angled():
|
|
283
|
+
return "right-angled isosceles"
|
|
284
|
+
return "isosceles"
|
|
285
|
+
elif self.is_right_angled():
|
|
286
|
+
return "right-angled"
|
|
287
|
+
else:
|
|
288
|
+
return "scalene"
|
|
289
|
+
|
|
290
|
+
def is_acute(self) -> bool:
|
|
291
|
+
"""
|
|
292
|
+
Checks if the triangle is an acute triangle
|
|
293
|
+
(all angles less than 90 degrees).
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
bool
|
|
298
|
+
``True`` if the triangle is acute, ``False`` otherwise.
|
|
299
|
+
"""
|
|
300
|
+
sides = sorted([self.a, self.b, self.c])
|
|
301
|
+
return sides[0] ** 2 + sides[1] ** 2 > sides[2] ** 2
|
|
302
|
+
|
|
303
|
+
def is_obtuse(self) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Checks if the triangle is an obtuse triangle
|
|
306
|
+
(one angle greater than 90 degrees).
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
bool
|
|
311
|
+
``True`` if the triangle is obtuse, ``False`` otherwise.
|
|
312
|
+
"""
|
|
313
|
+
sides = sorted([self.a, self.b, self.c])
|
|
314
|
+
return sides[0] ** 2 + sides[1] ** 2 < sides[2] ** 2
|
|
315
|
+
|
|
316
|
+
def get_angles(self) -> tuple[float, float, float]:
|
|
317
|
+
"""
|
|
318
|
+
Calculates and returns the angles of the triangle in degrees.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
tuple[float, float, float]
|
|
323
|
+
A tuple containing the angles in degrees (angle_A, angle_B, angle_C).
|
|
324
|
+
"""
|
|
325
|
+
a, b, c = self.a, self.b, self.c
|
|
326
|
+
angle_A = math.degrees(math.acos((b**2 + c**2 - a**2) / (2 * b * c)))
|
|
327
|
+
angle_B = math.degrees(math.acos((a**2 + c**2 - b**2) / (2 * a * c)))
|
|
328
|
+
angle_C = math.degrees(math.acos((a**2 + b**2 - c**2) / (2 * a * b)))
|
|
329
|
+
return angle_A, angle_B, angle_C
|
|
330
|
+
|
|
331
|
+
def scale(self, factor: int | float) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Scales the triangle by a given factor, changing the lengths of all sides.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
factor : int | float
|
|
338
|
+
The scaling factor. Must be positive.
|
|
339
|
+
|
|
340
|
+
Raises
|
|
341
|
+
------
|
|
342
|
+
ValueError
|
|
343
|
+
If the scaling factor is not positive.
|
|
344
|
+
"""
|
|
345
|
+
if factor <= 0:
|
|
346
|
+
raise ValueError("Scaling factor must be positive.")
|
|
347
|
+
self.a *= factor
|
|
348
|
+
self.b *= factor
|
|
349
|
+
self.c *= factor
|
|
350
|
+
|
|
351
|
+
def is_similar(self, other: Self) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Checks if this triangle is similar to another triangle.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
other : Triangle
|
|
358
|
+
The other triangle to compare to.
|
|
359
|
+
|
|
360
|
+
Returns
|
|
361
|
+
-------
|
|
362
|
+
bool
|
|
363
|
+
``True`` if the triangles are similar, ``False`` otherwise.
|
|
364
|
+
"""
|
|
365
|
+
ratios = sorted([self.a / other.a, self.b / other.b, self.c / other.c])
|
|
366
|
+
return abs(ratios[0] - ratios[2]) < 1e-6
|
|
367
|
+
|
|
368
|
+
def vis(self) -> str:
|
|
369
|
+
"""Visualization of Triangle"""
|
|
370
|
+
out = """
|
|
371
|
+
A
|
|
372
|
+
/\\
|
|
373
|
+
c /....\\b
|
|
374
|
+
B/........\\C
|
|
375
|
+
a
|
|
376
|
+
"""
|
|
377
|
+
return out
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class Circle(Polygon):
|
|
381
|
+
def __init__(self, radius: int | float) -> None:
|
|
382
|
+
"""
|
|
383
|
+
Initializes a Circle instance with a specified radius.
|
|
384
|
+
|
|
385
|
+
Parameters
|
|
386
|
+
----------
|
|
387
|
+
radius : int | float
|
|
388
|
+
The radius of the circle. Must be positive.
|
|
389
|
+
|
|
390
|
+
Raises
|
|
391
|
+
------
|
|
392
|
+
ValueError
|
|
393
|
+
If the radius is not positive.
|
|
394
|
+
"""
|
|
395
|
+
if radius <= 0:
|
|
396
|
+
raise ValueError("Radius must be a positive number.")
|
|
397
|
+
self.radius = radius
|
|
398
|
+
|
|
399
|
+
def __eq__(self, other: object) -> bool:
|
|
400
|
+
if not isinstance(other, Circle):
|
|
401
|
+
raise NotImplementedError("Not supported")
|
|
402
|
+
return math.isclose(self.radius, other.radius)
|
|
403
|
+
|
|
404
|
+
def __lt__(self, other: object) -> bool:
|
|
405
|
+
if not isinstance(other, Circle):
|
|
406
|
+
raise NotImplementedError("Not supported")
|
|
407
|
+
return self.radius < other.radius
|
|
408
|
+
|
|
409
|
+
def __le__(self, other: object) -> bool:
|
|
410
|
+
if not isinstance(other, Circle):
|
|
411
|
+
raise NotImplementedError("Not supported")
|
|
412
|
+
return self.radius <= other.radius
|
|
413
|
+
|
|
414
|
+
def diameter(self) -> int | float:
|
|
415
|
+
"""Returns the diameter of the circle."""
|
|
416
|
+
return 2 * self.radius
|
|
417
|
+
|
|
418
|
+
@override
|
|
419
|
+
def perimeter(self) -> float:
|
|
420
|
+
"""Returns the circumference of the circle."""
|
|
421
|
+
return 2 * math.pi * self.radius
|
|
422
|
+
|
|
423
|
+
def circumference(self) -> float:
|
|
424
|
+
"""Returns the circumference of the circle."""
|
|
425
|
+
return self.perimeter() # type: ignore
|
|
426
|
+
|
|
427
|
+
@override
|
|
428
|
+
def area(self) -> float:
|
|
429
|
+
"""Returns the area of the circle."""
|
|
430
|
+
return math.pi * self.radius**2
|
|
431
|
+
|
|
432
|
+
def scale(self, factor: int | float) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Scales the circle by a given factor, changing its radius.
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
factor : int | float
|
|
439
|
+
The scaling factor. Must be positive.
|
|
440
|
+
|
|
441
|
+
Raises
|
|
442
|
+
------
|
|
443
|
+
ValueError
|
|
444
|
+
If the scaling factor is not positive.
|
|
445
|
+
"""
|
|
446
|
+
if factor <= 0:
|
|
447
|
+
raise ValueError("Scaling factor must be positive.")
|
|
448
|
+
|
|
449
|
+
self.radius *= factor
|
|
450
|
+
|
|
451
|
+
@classmethod
|
|
452
|
+
def from_area(cls, area: int | float) -> Self:
|
|
453
|
+
"""
|
|
454
|
+
Creates a Circle instance from its area.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
area : int | float
|
|
459
|
+
The area of the circle. Must be positive.
|
|
460
|
+
|
|
461
|
+
Raises
|
|
462
|
+
------
|
|
463
|
+
ValueError
|
|
464
|
+
If the area is not positive.
|
|
465
|
+
"""
|
|
466
|
+
if area <= 0:
|
|
467
|
+
raise ValueError("Area must be positive.")
|
|
468
|
+
|
|
469
|
+
radius = math.sqrt(area / math.pi)
|
|
470
|
+
return cls(radius)
|
|
471
|
+
|
|
472
|
+
@classmethod
|
|
473
|
+
def from_circumference(cls, circumference: int | float) -> Self:
|
|
474
|
+
"""
|
|
475
|
+
Creates a Circle instance from its circumference.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
circumference : int | float
|
|
480
|
+
The circumference of the circle. Must be positive.
|
|
481
|
+
|
|
482
|
+
Raises
|
|
483
|
+
------
|
|
484
|
+
ValueError
|
|
485
|
+
If the circumference is not positive.
|
|
486
|
+
"""
|
|
487
|
+
if circumference <= 0:
|
|
488
|
+
raise ValueError("Circumference must be positive.")
|
|
489
|
+
|
|
490
|
+
radius = circumference / (2 * math.pi)
|
|
491
|
+
return cls(radius)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class Square(EqualSidesPolygon):
|
|
495
|
+
def __init__(self, side: int | float) -> None:
|
|
496
|
+
"""
|
|
497
|
+
Initializes a Square instance with specified side.
|
|
498
|
+
|
|
499
|
+
Parameters
|
|
500
|
+
----------
|
|
501
|
+
side : int | float
|
|
502
|
+
The length of the rectangle. Must be positive.
|
|
503
|
+
|
|
504
|
+
Raises
|
|
505
|
+
------
|
|
506
|
+
ValueError
|
|
507
|
+
If the side is not positive.
|
|
508
|
+
"""
|
|
509
|
+
super().__init__(side=side, num_of_sides=4)
|
|
510
|
+
|
|
511
|
+
@override
|
|
512
|
+
def area(self) -> int | float:
|
|
513
|
+
"""Calculate the area of the square."""
|
|
514
|
+
return self.side**2
|
|
515
|
+
|
|
516
|
+
def diagonal(self) -> float:
|
|
517
|
+
"""Calculate the length of the diagonal of the square."""
|
|
518
|
+
return math.sqrt(2) * self.side
|
|
519
|
+
|
|
520
|
+
@classmethod
|
|
521
|
+
def from_perimeter(cls, perimeter: int | float) -> Self:
|
|
522
|
+
"""Create a Square instance from its perimeter."""
|
|
523
|
+
if perimeter <= 0:
|
|
524
|
+
raise ValueError("Perimeter must be a positive number.")
|
|
525
|
+
return cls(perimeter / 4)
|
|
526
|
+
|
|
527
|
+
@classmethod
|
|
528
|
+
def from_area(cls, area: int | float) -> Self:
|
|
529
|
+
"""Create a Square instance from its area."""
|
|
530
|
+
if area <= 0:
|
|
531
|
+
raise ValueError("Area must be a positive number.")
|
|
532
|
+
return cls(math.sqrt(area))
|
|
533
|
+
|
|
534
|
+
def scale(self, factor: int | float) -> None:
|
|
535
|
+
"""
|
|
536
|
+
Scales the square by a given factor.
|
|
537
|
+
|
|
538
|
+
Parameters
|
|
539
|
+
----------
|
|
540
|
+
factor : int | float
|
|
541
|
+
The scaling factor. Must be positive.
|
|
542
|
+
|
|
543
|
+
Raises
|
|
544
|
+
------
|
|
545
|
+
ValueError
|
|
546
|
+
If the scaling factor is not positive.
|
|
547
|
+
"""
|
|
548
|
+
if factor <= 0:
|
|
549
|
+
raise ValueError("Scaling factor must be positive.")
|
|
550
|
+
|
|
551
|
+
self.side *= factor
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class Rectangle(Polygon):
|
|
555
|
+
def __init__(self, length: int | float, width: int | float) -> None:
|
|
556
|
+
"""
|
|
557
|
+
Initializes a Rectangle instance with specified length and width.
|
|
558
|
+
|
|
559
|
+
Parameters
|
|
560
|
+
----------
|
|
561
|
+
length : int | float
|
|
562
|
+
The length of the rectangle. Must be positive.
|
|
563
|
+
width : int | float
|
|
564
|
+
The width of the rectangle. Must be positive.
|
|
565
|
+
|
|
566
|
+
Raises
|
|
567
|
+
------
|
|
568
|
+
ValueError
|
|
569
|
+
If either length or width is not positive.
|
|
570
|
+
"""
|
|
571
|
+
super().__init__(num_of_sides=4)
|
|
572
|
+
|
|
573
|
+
if length <= 0 or width <= 0:
|
|
574
|
+
raise ValueError("Length and width must be positive numbers.")
|
|
575
|
+
|
|
576
|
+
self.length = max(length, width)
|
|
577
|
+
self.width = min(length, width)
|
|
578
|
+
|
|
579
|
+
@override
|
|
580
|
+
def perimeter(self) -> int | float:
|
|
581
|
+
"""Calculates and returns the perimeter of the rectangle."""
|
|
582
|
+
return 2 * (self.length + self.width)
|
|
583
|
+
|
|
584
|
+
@override
|
|
585
|
+
def area(self) -> int | float:
|
|
586
|
+
"""Calculates and returns the area of the rectangle."""
|
|
587
|
+
return self.length * self.width
|
|
588
|
+
|
|
589
|
+
def diagonal(self) -> float:
|
|
590
|
+
"""Calculates and returns the length of the diagonal of the rectangle."""
|
|
591
|
+
return math.sqrt(self.length**2 + self.width**2)
|
|
592
|
+
|
|
593
|
+
def is_square(self) -> bool:
|
|
594
|
+
"""Checks if the rectangle is a square (length equals width)."""
|
|
595
|
+
return self.length == self.width
|
|
596
|
+
|
|
597
|
+
def scale(self, factor: int | float) -> None:
|
|
598
|
+
"""
|
|
599
|
+
Scales the rectangle by a given factor.
|
|
600
|
+
|
|
601
|
+
Parameters
|
|
602
|
+
----------
|
|
603
|
+
factor : int | float
|
|
604
|
+
The scaling factor. Must be positive.
|
|
605
|
+
|
|
606
|
+
Raises
|
|
607
|
+
------
|
|
608
|
+
ValueError
|
|
609
|
+
If the scaling factor is not positive.
|
|
610
|
+
"""
|
|
611
|
+
if factor <= 0:
|
|
612
|
+
raise ValueError("Scaling factor must be positive.")
|
|
613
|
+
|
|
614
|
+
self.length *= factor
|
|
615
|
+
self.width *= factor
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
class Pentagon(EqualSidesPolygon):
|
|
619
|
+
def __init__(self, side: int | float) -> None:
|
|
620
|
+
"""
|
|
621
|
+
Initializes a Pentagon instance with a specified side length.
|
|
622
|
+
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
side : int | float
|
|
626
|
+
The length of one side of the pentagon. Must be positive.
|
|
627
|
+
|
|
628
|
+
Raises
|
|
629
|
+
------
|
|
630
|
+
ValueError
|
|
631
|
+
If the side length is not positive.
|
|
632
|
+
"""
|
|
633
|
+
super().__init__(side=side, num_of_sides=5)
|
|
634
|
+
|
|
635
|
+
@override
|
|
636
|
+
def area(self) -> float:
|
|
637
|
+
"""Calculates and returns the area of the pentagon using a specific formula."""
|
|
638
|
+
res = 0.25 * math.sqrt(5 * (5 + 2 * math.sqrt(5))) * self.side**2
|
|
639
|
+
return res
|
|
640
|
+
|
|
641
|
+
def apothem(self) -> float:
|
|
642
|
+
"""Calculates and returns the apothem of the pentagon."""
|
|
643
|
+
res = (self.side / 2) / math.tan(36 * math.pi / 180)
|
|
644
|
+
return res
|
|
645
|
+
|
|
646
|
+
def area2(self) -> float:
|
|
647
|
+
"""Calculates the area using the apothem and perimeter."""
|
|
648
|
+
res = 0.5 * self.perimeter() * self.apothem()
|
|
649
|
+
return res # type: ignore
|
|
650
|
+
|
|
651
|
+
@classmethod
|
|
652
|
+
def from_area(cls, area: int | float) -> Self:
|
|
653
|
+
"""
|
|
654
|
+
Creates a Pentagon instance from its area.
|
|
655
|
+
|
|
656
|
+
Parameters
|
|
657
|
+
----------
|
|
658
|
+
area : int | float
|
|
659
|
+
The area of the pentagon. Must be positive.
|
|
660
|
+
|
|
661
|
+
Raises
|
|
662
|
+
------
|
|
663
|
+
ValueError
|
|
664
|
+
If the area is not positive.
|
|
665
|
+
"""
|
|
666
|
+
if area <= 0:
|
|
667
|
+
raise ValueError("Area must be positive.")
|
|
668
|
+
|
|
669
|
+
# Calculate side length from area
|
|
670
|
+
side = math.sqrt((4 * area)) / math.sqrt(math.sqrt(5 * (5 + 2 * math.sqrt(5))))
|
|
671
|
+
return cls(side)
|
|
672
|
+
|
|
673
|
+
@classmethod
|
|
674
|
+
def from_perimeter(cls, perimeter: int | float) -> Self:
|
|
675
|
+
"""
|
|
676
|
+
Creates a Pentagon instance from its perimeter.
|
|
677
|
+
|
|
678
|
+
Parameters
|
|
679
|
+
----------
|
|
680
|
+
perimeter : int | float
|
|
681
|
+
The perimeter of the pentagon. Must be positive.
|
|
682
|
+
|
|
683
|
+
Raises
|
|
684
|
+
------
|
|
685
|
+
ValueError
|
|
686
|
+
If the perimeter is not positive.
|
|
687
|
+
"""
|
|
688
|
+
if perimeter <= 0:
|
|
689
|
+
raise ValueError("Perimeter must be positive.")
|
|
690
|
+
|
|
691
|
+
# Calculate side length from perimeter
|
|
692
|
+
side = perimeter / 5
|
|
693
|
+
return cls(side)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
class Hexagon(EqualSidesPolygon):
|
|
697
|
+
def __init__(self, side: int | float) -> None:
|
|
698
|
+
"""
|
|
699
|
+
Initializes a Hexagon instance with a specified side length.
|
|
700
|
+
|
|
701
|
+
Parameters
|
|
702
|
+
----------
|
|
703
|
+
side : int | float
|
|
704
|
+
The length of one side of the hexagon. Must be positive.
|
|
705
|
+
|
|
706
|
+
Raises
|
|
707
|
+
------
|
|
708
|
+
ValueError
|
|
709
|
+
If the side length is not positive.
|
|
710
|
+
"""
|
|
711
|
+
super().__init__(side=side, num_of_sides=6)
|
|
712
|
+
|
|
713
|
+
@override
|
|
714
|
+
def area(self) -> float:
|
|
715
|
+
"""Calculates and returns the area of the hexagon."""
|
|
716
|
+
res = self.side**2 * (3 * math.sqrt(3) / 2)
|
|
717
|
+
return res
|
|
718
|
+
|
|
719
|
+
def apothem(self) -> float:
|
|
720
|
+
"""Calculates and returns the apothem of the hexagon."""
|
|
721
|
+
return self.side / (2 * math.tan(math.pi / 6))
|
|
722
|
+
|
|
723
|
+
def area2(self) -> float:
|
|
724
|
+
"""Calculates the area using the apothem and perimeter."""
|
|
725
|
+
return 0.5 * self.perimeter() * self.apothem() # type: ignore
|
|
726
|
+
|
|
727
|
+
@classmethod
|
|
728
|
+
def from_area(cls, area: int | float) -> Self:
|
|
729
|
+
"""
|
|
730
|
+
Creates a Hexagon instance from its area.
|
|
731
|
+
|
|
732
|
+
Parameters
|
|
733
|
+
----------
|
|
734
|
+
area : int | float
|
|
735
|
+
The area of the hexagon. Must be positive.
|
|
736
|
+
|
|
737
|
+
Raises
|
|
738
|
+
------
|
|
739
|
+
ValueError
|
|
740
|
+
If the area is not positive.
|
|
741
|
+
"""
|
|
742
|
+
if area <= 0:
|
|
743
|
+
raise ValueError("Area must be positive.")
|
|
744
|
+
|
|
745
|
+
# Calculate side length from area
|
|
746
|
+
side = math.sqrt((2 * area) / (3 * math.sqrt(3)))
|
|
747
|
+
return cls(side)
|
|
748
|
+
|
|
749
|
+
@classmethod
|
|
750
|
+
def from_perimeter(cls, perimeter: int | float) -> Self:
|
|
751
|
+
"""
|
|
752
|
+
Creates a Hexagon instance from its perimeter.
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
perimeter : int | float
|
|
757
|
+
The perimeter of the hexagon. Must be positive.
|
|
758
|
+
|
|
759
|
+
Raises
|
|
760
|
+
------
|
|
761
|
+
ValueError
|
|
762
|
+
If the perimeter is not positive.
|
|
763
|
+
"""
|
|
764
|
+
if perimeter <= 0:
|
|
765
|
+
raise ValueError("Perimeter must be positive.")
|
|
766
|
+
|
|
767
|
+
# Calculate side length from perimeter
|
|
768
|
+
side = perimeter / 6
|
|
769
|
+
return cls(side)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
class Parallelogram(Polygon):
|
|
773
|
+
def __init__(
|
|
774
|
+
self,
|
|
775
|
+
base: int | float,
|
|
776
|
+
height: int | float,
|
|
777
|
+
*,
|
|
778
|
+
a: int | float | None = None,
|
|
779
|
+
phi: int | float | None = None,
|
|
780
|
+
) -> None:
|
|
781
|
+
"""
|
|
782
|
+
Initializes a Parallelogram instance with specified dimensions.
|
|
783
|
+
|
|
784
|
+
Parameters
|
|
785
|
+
----------
|
|
786
|
+
base : int | float
|
|
787
|
+
The length of the base of the parallelogram. Must be positive.
|
|
788
|
+
|
|
789
|
+
height : int | float
|
|
790
|
+
The height of the parallelogram. Must be positive.
|
|
791
|
+
|
|
792
|
+
a : int | float | None
|
|
793
|
+
The length of one side of the parallelogram. Must be positive if provided.
|
|
794
|
+
|
|
795
|
+
phi : int | float | None
|
|
796
|
+
The angle in degrees opposite the base, adjacent to height. Must be between 0 and 180 if provided.
|
|
797
|
+
|
|
798
|
+
Raises
|
|
799
|
+
------
|
|
800
|
+
ValueError
|
|
801
|
+
If base or height is not positive, or if angle is not in valid range.
|
|
802
|
+
"""
|
|
803
|
+
super().__init__(num_of_sides=4)
|
|
804
|
+
|
|
805
|
+
if base <= 0:
|
|
806
|
+
raise ValueError("Base must be a positive number.")
|
|
807
|
+
|
|
808
|
+
if height <= 0:
|
|
809
|
+
raise ValueError("Height must be a positive number.")
|
|
810
|
+
|
|
811
|
+
if a is not None and a <= 0:
|
|
812
|
+
raise ValueError("Side 'a' must be a positive number.")
|
|
813
|
+
|
|
814
|
+
if phi is not None and (phi <= 0 or phi >= 180):
|
|
815
|
+
raise ValueError("Angle 'phi' must be between 0 and 180 degrees.")
|
|
816
|
+
|
|
817
|
+
self.base = base
|
|
818
|
+
self.height = height
|
|
819
|
+
self.a = a
|
|
820
|
+
self.phi = phi
|
|
821
|
+
|
|
822
|
+
@override
|
|
823
|
+
def perimeter(self) -> int | float:
|
|
824
|
+
"""
|
|
825
|
+
Calculates and returns the perimeter of the parallelogram.
|
|
826
|
+
|
|
827
|
+
Raises
|
|
828
|
+
------
|
|
829
|
+
ValueError
|
|
830
|
+
If neither side ``a`` nor angle ``phi`` is provided.
|
|
831
|
+
"""
|
|
832
|
+
if self.a is not None:
|
|
833
|
+
return 2 * (self.base + self.a)
|
|
834
|
+
if self.phi is not None:
|
|
835
|
+
side_b = self.height / math.sin(math.radians(self.phi))
|
|
836
|
+
return 2 * (self.base + side_b)
|
|
837
|
+
# return 2 * (self.base + self.height * math.cos(self.phi * math.pi / 180))
|
|
838
|
+
raise ValueError("Side a or phi must be provided")
|
|
839
|
+
|
|
840
|
+
@override
|
|
841
|
+
def area(self) -> int | float:
|
|
842
|
+
"""Calculates and returns the area of the parallelogram."""
|
|
843
|
+
return self.base * self.height
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
class Rhombus(Polygon):
|
|
847
|
+
def __init__(self, d1: int | float, d2: int | float) -> None:
|
|
848
|
+
"""
|
|
849
|
+
Initializes a Rhombus instance with specified diagonal lengths.
|
|
850
|
+
|
|
851
|
+
Parameters
|
|
852
|
+
----------
|
|
853
|
+
d1 : int | float
|
|
854
|
+
The length of the first diagonal. Must be positive.
|
|
855
|
+
|
|
856
|
+
d2 : int | float
|
|
857
|
+
The length of the second diagonal. Must be positive.
|
|
858
|
+
|
|
859
|
+
Raises
|
|
860
|
+
------
|
|
861
|
+
ValueError
|
|
862
|
+
If either diagonal length is not positive.
|
|
863
|
+
"""
|
|
864
|
+
super().__init__(num_of_sides=4)
|
|
865
|
+
|
|
866
|
+
if d1 <= 0:
|
|
867
|
+
raise ValueError("Diagonal d1 must be a positive number.")
|
|
868
|
+
|
|
869
|
+
if d2 <= 0:
|
|
870
|
+
raise ValueError("Diagonal d2 must be a positive number.")
|
|
871
|
+
|
|
872
|
+
self.d1 = d1
|
|
873
|
+
self.d2 = d2
|
|
874
|
+
|
|
875
|
+
@override
|
|
876
|
+
def perimeter(self) -> float:
|
|
877
|
+
"""Calculates and returns the perimeter of the rhombus."""
|
|
878
|
+
return 2 * math.sqrt(self.d1**2 + self.d2**2)
|
|
879
|
+
|
|
880
|
+
@override
|
|
881
|
+
def area(self) -> float:
|
|
882
|
+
"""Calculates and returns the area of the rhombus."""
|
|
883
|
+
return (self.d1 * self.d2) / 2
|
|
884
|
+
|
|
885
|
+
def side(self) -> float:
|
|
886
|
+
"""Calculates and returns the length of one side of the rhombus."""
|
|
887
|
+
return self.perimeter() / self._num_of_sides # type: ignore
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
class Trapezoid(Polygon):
|
|
891
|
+
def __init__(
|
|
892
|
+
self,
|
|
893
|
+
a: int | float,
|
|
894
|
+
b: int | float,
|
|
895
|
+
c: int | float | None = None,
|
|
896
|
+
d: int | float | None = None,
|
|
897
|
+
h: int | float | None = None,
|
|
898
|
+
) -> None:
|
|
899
|
+
"""
|
|
900
|
+
Initializes a Trapezoid instance with specified dimensions.
|
|
901
|
+
|
|
902
|
+
Parameters
|
|
903
|
+
----------
|
|
904
|
+
a : int | float
|
|
905
|
+
The length of base 1. Must be positive.
|
|
906
|
+
|
|
907
|
+
b : int | float
|
|
908
|
+
The length of base 2. Must be positive.
|
|
909
|
+
|
|
910
|
+
c : int | float | None
|
|
911
|
+
The length of side 1. Must be positive if provided.
|
|
912
|
+
|
|
913
|
+
d : int | float | None
|
|
914
|
+
The length of side 2. Must be positive if provided.
|
|
915
|
+
|
|
916
|
+
h : int | float | None
|
|
917
|
+
The height of the trapezoid. Must be positive if provided.
|
|
918
|
+
|
|
919
|
+
Raises
|
|
920
|
+
------
|
|
921
|
+
ValueError
|
|
922
|
+
If base lengths or height are not positive, or if both sides are not provided.
|
|
923
|
+
"""
|
|
924
|
+
super().__init__(num_of_sides=4)
|
|
925
|
+
|
|
926
|
+
if a <= 0:
|
|
927
|
+
raise ValueError("Base 'a' must be a positive number.")
|
|
928
|
+
|
|
929
|
+
if b <= 0:
|
|
930
|
+
raise ValueError("Base 'b' must be a positive number.")
|
|
931
|
+
|
|
932
|
+
if c is not None and c <= 0:
|
|
933
|
+
raise ValueError("Side 'c' must be a positive number.")
|
|
934
|
+
|
|
935
|
+
if d is not None and d <= 0:
|
|
936
|
+
raise ValueError("Side 'd' must be a positive number.")
|
|
937
|
+
|
|
938
|
+
if h is not None and h <= 0:
|
|
939
|
+
raise ValueError("Height 'h' must be a positive number.")
|
|
940
|
+
|
|
941
|
+
self.a = a
|
|
942
|
+
self.b = b
|
|
943
|
+
self.c = c
|
|
944
|
+
self.d = d
|
|
945
|
+
self.h = h
|
|
946
|
+
|
|
947
|
+
@override
|
|
948
|
+
def perimeter(self) -> int | float:
|
|
949
|
+
"""Calculates and returns the perimeter of the trapezoid."""
|
|
950
|
+
if self.c is not None and self.d is not None:
|
|
951
|
+
return sum([self.a, self.b, self.c, self.d])
|
|
952
|
+
raise ValueError("'c' and 'd' must be provided to calculate perimeter.")
|
|
953
|
+
|
|
954
|
+
@override
|
|
955
|
+
def area(self) -> float:
|
|
956
|
+
"""Calculates and returns the area of the trapezoid."""
|
|
957
|
+
if self.h is not None:
|
|
958
|
+
return 0.5 * (self.a + self.b) * self.h
|
|
959
|
+
raise ValueError("'h' must be provided to calculate area.")
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
# Class - 3D Shape
|
|
963
|
+
# ---------------------------------------------------------------------------
|
|
964
|
+
class Cube(ThreeDimensionShape):
|
|
965
|
+
def __init__(self, side: int | float) -> None:
|
|
966
|
+
"""
|
|
967
|
+
Initializes a Cube instance with a specified side length.
|
|
968
|
+
|
|
969
|
+
Parameters
|
|
970
|
+
----------
|
|
971
|
+
side : int | float
|
|
972
|
+
The length of one side of the cube. Must be positive.
|
|
973
|
+
|
|
974
|
+
Raises
|
|
975
|
+
------
|
|
976
|
+
ValueError
|
|
977
|
+
If the side length is not positive.
|
|
978
|
+
"""
|
|
979
|
+
if side <= 0:
|
|
980
|
+
raise ValueError("Side length must be a positive number.")
|
|
981
|
+
|
|
982
|
+
self.side = side
|
|
983
|
+
|
|
984
|
+
@override
|
|
985
|
+
def surface_area(self) -> int | float:
|
|
986
|
+
"""Calculates and returns the surface area of the cube."""
|
|
987
|
+
return 6 * self.side**2
|
|
988
|
+
|
|
989
|
+
def surface_area_side(self) -> int | float:
|
|
990
|
+
"""Calculates and returns the surface area (side only) of the cube."""
|
|
991
|
+
return 4 * self.side**2
|
|
992
|
+
|
|
993
|
+
@override
|
|
994
|
+
def volume(self) -> int | float:
|
|
995
|
+
"""Calculates and returns the volume of the cube."""
|
|
996
|
+
return self.side**3
|
|
997
|
+
|
|
998
|
+
def face_area(self) -> int | float:
|
|
999
|
+
"""Calculates and returns the area of one face of the cube."""
|
|
1000
|
+
return self.side**2
|
|
1001
|
+
|
|
1002
|
+
def diagonal(self) -> float:
|
|
1003
|
+
"""Calculates and returns the length of the space diagonal of the cube."""
|
|
1004
|
+
return self.side * math.sqrt(3)
|
|
1005
|
+
|
|
1006
|
+
def scale(self, factor: int | float) -> None:
|
|
1007
|
+
"""Scales the cube by a given factor."""
|
|
1008
|
+
if factor <= 0:
|
|
1009
|
+
raise ValueError("Scaling factor must be positive.")
|
|
1010
|
+
|
|
1011
|
+
self.side *= factor
|
|
1012
|
+
|
|
1013
|
+
@classmethod
|
|
1014
|
+
def from_surface_area(cls, surface_area: int | float) -> Self:
|
|
1015
|
+
"""
|
|
1016
|
+
Creates a Cube instance from its surface area.
|
|
1017
|
+
|
|
1018
|
+
Parameters
|
|
1019
|
+
----------
|
|
1020
|
+
surface_area : int | float
|
|
1021
|
+
The surface area of the cube. Must be positive.
|
|
1022
|
+
|
|
1023
|
+
Raises
|
|
1024
|
+
------
|
|
1025
|
+
ValueError
|
|
1026
|
+
If surface area is not positive.
|
|
1027
|
+
"""
|
|
1028
|
+
if surface_area <= 0:
|
|
1029
|
+
raise ValueError("Surface area must be positive.")
|
|
1030
|
+
|
|
1031
|
+
# Calculate side length from surface area
|
|
1032
|
+
side = math.sqrt(surface_area / 6)
|
|
1033
|
+
return cls(side)
|
|
1034
|
+
|
|
1035
|
+
@classmethod
|
|
1036
|
+
def from_volume(cls, volume: int | float) -> Self:
|
|
1037
|
+
"""
|
|
1038
|
+
Creates a Cube instance from its volume.
|
|
1039
|
+
|
|
1040
|
+
Parameters
|
|
1041
|
+
----------
|
|
1042
|
+
volume : int | float
|
|
1043
|
+
The volume of the cube. Must be positive.
|
|
1044
|
+
|
|
1045
|
+
Raises
|
|
1046
|
+
------
|
|
1047
|
+
ValueError
|
|
1048
|
+
If volume is not positive.
|
|
1049
|
+
"""
|
|
1050
|
+
if volume <= 0:
|
|
1051
|
+
raise ValueError("Volume must be positive.")
|
|
1052
|
+
|
|
1053
|
+
# Calculate side length from volume
|
|
1054
|
+
side = volume ** (1 / 3)
|
|
1055
|
+
return cls(side)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
class Cuboid(ThreeDimensionShape):
|
|
1059
|
+
def __init__(
|
|
1060
|
+
self,
|
|
1061
|
+
length: int | float,
|
|
1062
|
+
width: int | float,
|
|
1063
|
+
height: int | float,
|
|
1064
|
+
) -> None:
|
|
1065
|
+
"""
|
|
1066
|
+
Initializes a Cuboid instance with specified dimensions.
|
|
1067
|
+
|
|
1068
|
+
Parameters
|
|
1069
|
+
----------
|
|
1070
|
+
length : int | float
|
|
1071
|
+
The length of the cuboid. Must be positive.
|
|
1072
|
+
|
|
1073
|
+
width : int | float
|
|
1074
|
+
The width of the cuboid. Must be positive.
|
|
1075
|
+
|
|
1076
|
+
height : int | float
|
|
1077
|
+
The height of the cuboid. Must be positive.
|
|
1078
|
+
|
|
1079
|
+
Raises
|
|
1080
|
+
------
|
|
1081
|
+
ValueError
|
|
1082
|
+
If any dimension is not positive.
|
|
1083
|
+
"""
|
|
1084
|
+
if length <= 0:
|
|
1085
|
+
raise ValueError("Length must be a positive number.")
|
|
1086
|
+
|
|
1087
|
+
if width <= 0:
|
|
1088
|
+
raise ValueError("Width must be a positive number.")
|
|
1089
|
+
|
|
1090
|
+
if height <= 0:
|
|
1091
|
+
raise ValueError("Height must be a positive number.")
|
|
1092
|
+
|
|
1093
|
+
self.length = length
|
|
1094
|
+
self.width = width
|
|
1095
|
+
self.height = height
|
|
1096
|
+
|
|
1097
|
+
@override
|
|
1098
|
+
def surface_area(self) -> int | float:
|
|
1099
|
+
"""Calculates and returns the surface area of the cuboid."""
|
|
1100
|
+
res = 2 * (
|
|
1101
|
+
self.length * self.width
|
|
1102
|
+
+ self.width * self.height
|
|
1103
|
+
+ self.length * self.height
|
|
1104
|
+
)
|
|
1105
|
+
return res
|
|
1106
|
+
|
|
1107
|
+
def surface_area_side(self) -> int | float:
|
|
1108
|
+
"""Calculates and returns the surface area of the sides of the cuboid."""
|
|
1109
|
+
res = 2 * self.height * (self.length + self.width)
|
|
1110
|
+
return res
|
|
1111
|
+
|
|
1112
|
+
@override
|
|
1113
|
+
def volume(self) -> int | float:
|
|
1114
|
+
"""Calculates and returns the volume of the cuboid."""
|
|
1115
|
+
return self.length * self.width * self.height
|
|
1116
|
+
|
|
1117
|
+
def diagonal(self) -> float:
|
|
1118
|
+
"""Calculates and returns the length of the space diagonal of the cuboid."""
|
|
1119
|
+
return math.sqrt(self.length**2 + self.width**2 + self.height**2)
|
|
1120
|
+
|
|
1121
|
+
def scale(self, factor: int | float) -> None:
|
|
1122
|
+
"""Scales the dimensions of the cuboid by a given factor."""
|
|
1123
|
+
if factor <= 0:
|
|
1124
|
+
raise ValueError("Scaling factor must be positive.")
|
|
1125
|
+
|
|
1126
|
+
self.length *= factor
|
|
1127
|
+
self.width *= factor
|
|
1128
|
+
self.height *= factor
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
class Sphere(ThreeDimensionShape):
|
|
1132
|
+
def __init__(self, radius: int | float) -> None:
|
|
1133
|
+
"""
|
|
1134
|
+
Initializes a Sphere instance with the specified radius.
|
|
1135
|
+
|
|
1136
|
+
Parameters
|
|
1137
|
+
----------
|
|
1138
|
+
radius : int | float
|
|
1139
|
+
The radius of the sphere. Must be positive.
|
|
1140
|
+
|
|
1141
|
+
Raises
|
|
1142
|
+
------
|
|
1143
|
+
ValueError
|
|
1144
|
+
If radius is not positive.
|
|
1145
|
+
"""
|
|
1146
|
+
if radius <= 0:
|
|
1147
|
+
raise ValueError("Radius must be a positive number.")
|
|
1148
|
+
|
|
1149
|
+
self.radius = radius
|
|
1150
|
+
|
|
1151
|
+
@override
|
|
1152
|
+
def surface_area(self) -> float:
|
|
1153
|
+
"""Calculates and returns the surface area of the sphere."""
|
|
1154
|
+
return 4 * math.pi * self.radius**2
|
|
1155
|
+
|
|
1156
|
+
@override
|
|
1157
|
+
def volume(self) -> float:
|
|
1158
|
+
"""Calculates and returns the volume of the sphere."""
|
|
1159
|
+
return (4 / 3) * math.pi * self.radius**3
|
|
1160
|
+
|
|
1161
|
+
@classmethod
|
|
1162
|
+
def from_volume(cls, volume: int | float) -> Self:
|
|
1163
|
+
"""
|
|
1164
|
+
Creates a Sphere instance from its volume.
|
|
1165
|
+
|
|
1166
|
+
Parameters
|
|
1167
|
+
----------
|
|
1168
|
+
volume : int | float
|
|
1169
|
+
The volume of the sphere. Must be positive.
|
|
1170
|
+
|
|
1171
|
+
Raises
|
|
1172
|
+
------
|
|
1173
|
+
ValueError
|
|
1174
|
+
If volume is not positive.
|
|
1175
|
+
"""
|
|
1176
|
+
if volume <= 0:
|
|
1177
|
+
raise ValueError("Volume must be a positive number.")
|
|
1178
|
+
|
|
1179
|
+
radius = ((3 * volume) / (4 * math.pi)) ** (1 / 3)
|
|
1180
|
+
return cls(radius)
|
|
1181
|
+
|
|
1182
|
+
@classmethod
|
|
1183
|
+
def from_surface_area(cls, surface_area: int | float) -> Self:
|
|
1184
|
+
"""
|
|
1185
|
+
Creates a Sphere instance from its surface area.
|
|
1186
|
+
|
|
1187
|
+
Parameters
|
|
1188
|
+
----------
|
|
1189
|
+
surface_area : int | float
|
|
1190
|
+
The surface area of the sphere. Must be positive.
|
|
1191
|
+
|
|
1192
|
+
Raises
|
|
1193
|
+
------
|
|
1194
|
+
ValueError
|
|
1195
|
+
If surface area is not positive.
|
|
1196
|
+
"""
|
|
1197
|
+
if surface_area <= 0:
|
|
1198
|
+
raise ValueError("Surface area must be a positive number.")
|
|
1199
|
+
|
|
1200
|
+
radius = math.sqrt(surface_area / (4 * math.pi))
|
|
1201
|
+
return cls(radius)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
class HemiSphere(ThreeDimensionShape):
|
|
1205
|
+
def __init__(self, radius: int | float) -> None:
|
|
1206
|
+
"""
|
|
1207
|
+
Initializes a HemiSphere instance with the specified radius.
|
|
1208
|
+
|
|
1209
|
+
Parameters
|
|
1210
|
+
----------
|
|
1211
|
+
radius : int | float
|
|
1212
|
+
The radius of the hemisphere. Must be positive.
|
|
1213
|
+
|
|
1214
|
+
Raises
|
|
1215
|
+
------
|
|
1216
|
+
ValueError
|
|
1217
|
+
If the radius is not positive.
|
|
1218
|
+
"""
|
|
1219
|
+
if radius <= 0:
|
|
1220
|
+
raise ValueError("Radius must be a positive number.")
|
|
1221
|
+
|
|
1222
|
+
self.radius = radius
|
|
1223
|
+
|
|
1224
|
+
@override
|
|
1225
|
+
def surface_area(self) -> float:
|
|
1226
|
+
"""
|
|
1227
|
+
Calculates and returns the total surface area of the hemisphere.
|
|
1228
|
+
"""
|
|
1229
|
+
return 3 * math.pi * self.radius**2
|
|
1230
|
+
|
|
1231
|
+
def surface_area_curved(self) -> float:
|
|
1232
|
+
"""
|
|
1233
|
+
Calculates and returns the curved surface area of the hemisphere.
|
|
1234
|
+
"""
|
|
1235
|
+
return 2 * math.pi * self.radius**2
|
|
1236
|
+
|
|
1237
|
+
def surface_area_base(self) -> float:
|
|
1238
|
+
"""
|
|
1239
|
+
Calculates and returns the area of the base (circular) of the hemisphere.
|
|
1240
|
+
"""
|
|
1241
|
+
return math.pi * self.radius**2
|
|
1242
|
+
|
|
1243
|
+
@override
|
|
1244
|
+
def volume(self) -> float:
|
|
1245
|
+
"""
|
|
1246
|
+
Calculates and returns the volume of the hemisphere.
|
|
1247
|
+
"""
|
|
1248
|
+
return (2 / 3) * math.pi * self.radius**3
|
|
1249
|
+
|
|
1250
|
+
@classmethod
|
|
1251
|
+
def from_volume(cls, volume: int | float) -> Self:
|
|
1252
|
+
"""
|
|
1253
|
+
Creates a HemiSphere instance from its volume.
|
|
1254
|
+
|
|
1255
|
+
Parameters
|
|
1256
|
+
----------
|
|
1257
|
+
volume : int | float
|
|
1258
|
+
The volume of the hemisphere. Must be positive.
|
|
1259
|
+
|
|
1260
|
+
Raises
|
|
1261
|
+
------
|
|
1262
|
+
ValueError
|
|
1263
|
+
If the volume is not positive.
|
|
1264
|
+
"""
|
|
1265
|
+
if volume <= 0:
|
|
1266
|
+
raise ValueError("Volume must be positive.")
|
|
1267
|
+
|
|
1268
|
+
radius = (3 * volume / (2 * math.pi)) ** (1 / 3)
|
|
1269
|
+
return cls(radius)
|
|
1270
|
+
|
|
1271
|
+
@classmethod
|
|
1272
|
+
def from_surface_area(cls, surface_area: int | float) -> Self:
|
|
1273
|
+
"""
|
|
1274
|
+
Creates a HemiSphere instance from its total surface area.
|
|
1275
|
+
|
|
1276
|
+
Parameters
|
|
1277
|
+
----------
|
|
1278
|
+
surface_area : int | float
|
|
1279
|
+
The total surface area of the hemisphere. Must be positive.
|
|
1280
|
+
|
|
1281
|
+
Raises
|
|
1282
|
+
------
|
|
1283
|
+
ValueError
|
|
1284
|
+
If the surface area is not positive.
|
|
1285
|
+
"""
|
|
1286
|
+
if surface_area <= 0:
|
|
1287
|
+
raise ValueError("Surface area must be positive.")
|
|
1288
|
+
|
|
1289
|
+
radius = math.sqrt(surface_area / (3 * math.pi))
|
|
1290
|
+
return cls(radius)
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
class Cylinder(ThreeDimensionShape):
|
|
1294
|
+
def __init__(self, radius: int | float, height: int | float) -> None:
|
|
1295
|
+
"""
|
|
1296
|
+
Initializes a Cylinder instance with the specified radius and height.
|
|
1297
|
+
|
|
1298
|
+
Parameters
|
|
1299
|
+
----------
|
|
1300
|
+
radius : int | float
|
|
1301
|
+
The radius of the cylinder. Must be positive.
|
|
1302
|
+
|
|
1303
|
+
height : int | float
|
|
1304
|
+
The height of the cylinder. Must be positive.
|
|
1305
|
+
|
|
1306
|
+
Raises
|
|
1307
|
+
------
|
|
1308
|
+
ValueError
|
|
1309
|
+
If radius or height is not positive.
|
|
1310
|
+
"""
|
|
1311
|
+
if radius <= 0:
|
|
1312
|
+
raise ValueError("Radius must be a positive number.")
|
|
1313
|
+
|
|
1314
|
+
if height <= 0:
|
|
1315
|
+
raise ValueError("Height must be a positive number.")
|
|
1316
|
+
|
|
1317
|
+
self.radius = radius
|
|
1318
|
+
self.height = height
|
|
1319
|
+
|
|
1320
|
+
@override
|
|
1321
|
+
def surface_area(self) -> float:
|
|
1322
|
+
"""Calculates and returns the total surface area of the cylinder."""
|
|
1323
|
+
res = 2 * math.pi * self.radius * (self.radius + self.height)
|
|
1324
|
+
return res
|
|
1325
|
+
|
|
1326
|
+
def surface_area_curved(self) -> float:
|
|
1327
|
+
"""Calculates and returns the curved surface area of the cylinder."""
|
|
1328
|
+
res = 2 * math.pi * self.radius * self.height
|
|
1329
|
+
return res
|
|
1330
|
+
|
|
1331
|
+
@override
|
|
1332
|
+
def volume(self) -> float:
|
|
1333
|
+
"""Calculates and returns the volume of the cylinder."""
|
|
1334
|
+
return math.pi * self.radius**2 * self.height
|