absfuyu 4.2.0__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.

Files changed (67) hide show
  1. absfuyu/__init__.py +4 -4
  2. absfuyu/__main__.py +13 -1
  3. absfuyu/cli/color.py +7 -0
  4. absfuyu/cli/do_group.py +0 -35
  5. absfuyu/cli/tool_group.py +5 -5
  6. absfuyu/config/__init__.py +17 -34
  7. absfuyu/core/__init__.py +49 -0
  8. absfuyu/core/baseclass.py +299 -0
  9. absfuyu/core/baseclass2.py +165 -0
  10. absfuyu/core/decorator.py +67 -0
  11. absfuyu/core/docstring.py +163 -0
  12. absfuyu/core/dummy_cli.py +67 -0
  13. absfuyu/core/dummy_func.py +47 -0
  14. absfuyu/dxt/__init__.py +42 -0
  15. absfuyu/dxt/dictext.py +201 -0
  16. absfuyu/dxt/dxt_support.py +79 -0
  17. absfuyu/dxt/intext.py +586 -0
  18. absfuyu/dxt/listext.py +508 -0
  19. absfuyu/dxt/strext.py +530 -0
  20. absfuyu/{extensions → extra}/__init__.py +2 -2
  21. absfuyu/extra/beautiful.py +251 -0
  22. absfuyu/{extensions → extra}/data_analysis.py +51 -82
  23. absfuyu/fun/__init__.py +110 -135
  24. absfuyu/fun/tarot.py +9 -17
  25. absfuyu/game/__init__.py +6 -0
  26. absfuyu/game/game_stat.py +6 -0
  27. absfuyu/game/sudoku.py +7 -1
  28. absfuyu/game/tictactoe.py +12 -5
  29. absfuyu/game/wordle.py +14 -8
  30. absfuyu/general/__init__.py +6 -79
  31. absfuyu/general/content.py +22 -36
  32. absfuyu/general/generator.py +17 -42
  33. absfuyu/general/human.py +108 -228
  34. absfuyu/general/shape.py +1334 -0
  35. absfuyu/logger.py +8 -13
  36. absfuyu/pkg_data/__init__.py +136 -99
  37. absfuyu/pkg_data/deprecated.py +133 -0
  38. absfuyu/sort.py +6 -130
  39. absfuyu/tools/__init__.py +2 -2
  40. absfuyu/tools/checksum.py +33 -22
  41. absfuyu/tools/converter.py +51 -48
  42. absfuyu/tools/keygen.py +25 -30
  43. absfuyu/tools/obfuscator.py +246 -112
  44. absfuyu/tools/passwordlib.py +99 -29
  45. absfuyu/tools/shutdownizer.py +68 -47
  46. absfuyu/tools/web.py +2 -9
  47. absfuyu/util/__init__.py +15 -15
  48. absfuyu/util/api.py +10 -15
  49. absfuyu/util/json_method.py +7 -24
  50. absfuyu/util/lunar.py +3 -9
  51. absfuyu/util/path.py +22 -27
  52. absfuyu/util/performance.py +43 -67
  53. absfuyu/util/shorten_number.py +65 -14
  54. absfuyu/util/zipped.py +9 -15
  55. {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/METADATA +41 -14
  56. absfuyu-5.0.0.dist-info/RECORD +68 -0
  57. absfuyu/core.py +0 -57
  58. absfuyu/everything.py +0 -32
  59. absfuyu/extensions/beautiful.py +0 -188
  60. absfuyu/fun/WGS.py +0 -134
  61. absfuyu/general/data_extension.py +0 -1796
  62. absfuyu/tools/stats.py +0 -226
  63. absfuyu/util/pkl.py +0 -67
  64. absfuyu-4.2.0.dist-info/RECORD +0 -59
  65. {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
  66. {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
  67. {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -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