diff --git a/DIRECTORY.md b/DIRECTORY.md index 56ac6b94994c..a6800acdac4a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -425,6 +425,7 @@ * [Climbing Stairs](dynamic_programming/climbing_stairs.py) * [Combination Sum Iv](dynamic_programming/combination_sum_iv.py) * [Edit Distance](dynamic_programming/edit_distance.py) + * [Egg Dropping](dynamic_programming/egg_dropping.py) * [Factorial](dynamic_programming/factorial.py) * [Fast Fibonacci](dynamic_programming/fast_fibonacci.py) * [Fibonacci](dynamic_programming/fibonacci.py) @@ -542,6 +543,7 @@ * Tests * [Test Graham Scan](geometry/tests/test_graham_scan.py) * [Test Jarvis March](geometry/tests/test_jarvis_march.py) + * [Triangle](geometry/triangle.py) ## [Graphics](graphics) * [Bezier Curve](graphics/bezier_curve.py) @@ -591,6 +593,7 @@ * [Graphs Floyd Warshall](graphs/graphs_floyd_warshall.py) * [Greedy Best First](graphs/greedy_best_first.py) * [Greedy Min Vertex Cover](graphs/greedy_min_vertex_cover.py) + * [Hopcroft Karp](graphs/hopcroft_karp.py) * [Johnson](graphs/johnson.py) * [Kahns Algorithm Long](graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](graphs/kahns_algorithm_topo.py) diff --git a/geometry/triangle.py b/geometry/triangle.py new file mode 100644 index 000000000000..742e9088c760 --- /dev/null +++ b/geometry/triangle.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import math +from dataclasses import dataclass, field + +from numpy import array, linalg + +# Define a Point on a 2D normalized orthogonal euclidean grid +# https://mathworld.wolfram.com/Circumcenter.html +# https://mathworld.wolfram.com/Incenter.html +# https://mathworld.wolfram.com/Orthocenter.html + + +@dataclass +class Point: + """ + A point defined by 2 floats representing a length on a normalized + orthogonal coordinate system + default coordinate is the origin + + >>> Point(-1.0, 0.0) + Point(x=-1.0, y=0.0) + + """ + + x: float = 0.0 + y: float = 0.0 + + def __post_init__(self) -> None: + if not isinstance(self.x, (int, float)): + raise TypeError("x must be an int or float numeric value") + if not isinstance(self.y, (int, float)): + raise TypeError("y must be an int or float numeric value") + + +@dataclass +class Triangle: + """ + A 3 point Triangle on a 2D normalized orthogonal euclidean grid + + >>> p1 = Point(-1.0,0.0) + >>> p2 = Point(1.0,0.0) + >>> p3 = Point(0.0,1.0) + >>> Triangle(p1, p2, p3) # doctest: +NORMALIZE_WHITESPACE + Triangle(v1=Point(x=-1.0, y=0.0), v2=Point(x=1.0, y=0.0), v3=Point(x=0.0, y=1.0), + circum=Point(x=0.0, y=0.0), incen=Point(x=0.0, y=0.0), + ortho=Point(x=0.0, y=0.0)) + """ + + v1: Point = field(default_factory=Point) + v2: Point = field(default_factory=Point) + v3: Point = field(default_factory=Point) + circum: Point = field(default_factory=Point) + incen: Point = field(default_factory=Point) + ortho: Point = field(default_factory=Point) + + def __post_init__(self) -> None: + # Check for valid arguments + if ( + not isinstance(self.v1, Point) + or not isinstance(self.v2, Point) + or not isinstance(self.v3, Point) + ): + raise TypeError("All 3 arguments should be Point Objects") + # Check for 3 unique points + if self.v1 in (self.v2, self.v3): + raise TypeError("All 3 arguments should be unique") + # Check for linearity + if self.v1.x == self.v2.x and self.v3.y == self.v2.y: + raise TypeError("One or more arguments are redundant") + m = (self.v1.y - self.v2.y) / (self.v1.x - self.v2.x) + yb = self.v1.y - m * self.v1.x + if self.v3.y == m * self.v3.x + yb: + raise TypeError("One or more arguments are redundant") + + # Circumcenter + @property + def circumcenter(self) -> Point: + m_0 = array( + [ + [self.v1.x, self.v1.y, 1], + [self.v2.x, self.v2.y, 1], + [self.v3.x, self.v3.y, 1], + ] + ) + m_1 = array( + [ + [self.v1.x**2 + self.v1.y**2, self.v1.y, 1], + [self.v2.x**2 + self.v2.y**2, self.v2.y, 1], + [self.v3.x**2 + self.v3.y**2, self.v3.y, 1], + ] + ) + m_2 = array( + [ + [self.v1.x**2 + self.v1.y**2, self.v1.x, 1], + [self.v2.x**2 + self.v2.y**2, self.v2.x, 1], + [self.v3.x**2 + self.v3.y**2, self.v3.x, 1], + ] + ) + a = linalg.det(m_0) + bx = -linalg.det(m_1) + by = linalg.det(m_2) + self.circum.x = -bx / (2 * a) + self.circum.y = -by / (2 * a) + return self.circum + + # Incenter + @property + def incenter(self) -> Point: + a = math.sqrt((self.v2.x - self.v3.x) ** 2 + (self.v2.y - self.v3.y) ** 2) + b = math.sqrt((self.v1.x - self.v3.x) ** 2 + (self.v1.y - self.v3.y) ** 2) + c = math.sqrt((self.v1.x - self.v2.x) ** 2 + (self.v1.y - self.v2.y) ** 2) + self.incen.x = (a * self.v1.x + b * self.v2.x + c * self.v3.x) / (a + b + c) + self.incen.y = (a * self.v1.y + b * self.v2.y + c * self.v3.y) / (a + b + c) + return self.incen + + # Orthocenter + @property + def orthocenter(self) -> Point: + inv_m1 = -1 / ((self.v3.y - self.v1.y) / (self.v3.x - self.v1.x)) + inv_m2 = -1 / ((self.v3.y - self.v2.y) / (self.v3.x - self.v2.x)) + m = array([[inv_m2, -1], [inv_m1, -1]]) + b = array([[inv_m2 * self.v1.x - self.v1.y], [inv_m1 * self.v2.x - self.v2.y]]) + soln = linalg.solve(m, b) + self.ortho.x = soln[0][0] + self.ortho.y = soln[1][0] + return self.ortho + + +if __name__ == "__main__": + __import__("doctest").testmod()