|
| 1 | +import math |
1 | 2 | import os |
2 | 3 | import platform |
3 | 4 | import sys |
@@ -111,3 +112,127 @@ def slugify(original: str) -> str: |
111 | 112 | slugified = slugified.replace(" ", "-") |
112 | 113 |
|
113 | 114 | return slugified |
| 115 | + |
| 116 | + |
| 117 | +class Vector(complex): |
| 118 | + """ |
| 119 | + Simple immutable 2D vector class based on the Python complex number type |
| 120 | +
|
| 121 | + Create and access - coordinates |
| 122 | +
|
| 123 | + >>> v = Vector(1, 2) |
| 124 | + >>> v.x, v.y |
| 125 | + (1.0, 2.0) |
| 126 | +
|
| 127 | + Create and access - angle and magnitude (length) |
| 128 | +
|
| 129 | + >>> v = Vector.polar(math.pi, 2) |
| 130 | + >>> v |
| 131 | + Vector(-2.0, 0.0) |
| 132 | + >>> v.magnitude # Length of the vector, alias for abs(v) |
| 133 | + 2.0 |
| 134 | + >>> v.radians |
| 135 | + 3.141592653589793 |
| 136 | + >>> v.degrees |
| 137 | + 180.0 |
| 138 | +
|
| 139 | + Arithmetic operations |
| 140 | +
|
| 141 | + >>> Vector(1, 1) + 2 |
| 142 | + Vector(3.0, 1.0) |
| 143 | + >>> Vector(0.1, 0.1) + Vector(0.2, 0.2) == Vector(0.3, 0.3) # Float tolerance 10 decimals |
| 144 | + True |
| 145 | + >>> Vector(2, 3) - Vector(1, 1) |
| 146 | + Vector(1.0, 2.0) |
| 147 | + >>> Vector(1, 1) * 2 |
| 148 | + Vector(2.0, 2.0) |
| 149 | + >>> round(Vector.polar(math.pi / 4, 1), 1) |
| 150 | + Vector(0.7, 0.7) |
| 151 | +
|
| 152 | + Get a new vector by adjusting one of the coordinates |
| 153 | + >>> v = Vector() |
| 154 | + >>> v.with_x(1) |
| 155 | + Vector(1.0, 0.0) |
| 156 | + >>> v.with_y(2) |
| 157 | + Vector(0.0, 2.0) |
| 158 | +
|
| 159 | + Get a new vector by adjusting angle or magnitude |
| 160 | +
|
| 161 | + >>> v = Vector(1, 2) |
| 162 | + >>> v = v.with_magnitude(4.47213595499958) # Twice as long |
| 163 | + >>> v.x, v.y |
| 164 | + (2.0, 4.0) |
| 165 | +
|
| 166 | + >>> v = Vector.polar(math.pi, 2) |
| 167 | + >>> v |
| 168 | + Vector(-2.0, 0.0) |
| 169 | + >>> v.with_radians(0) |
| 170 | + Vector(2.0, 0.0) |
| 171 | + >>> v.with_degrees(90) |
| 172 | + Vector(0.0, 2.0) |
| 173 | + """ |
| 174 | + abs_tol = 1e-10 |
| 175 | + |
| 176 | + x = complex.real |
| 177 | + y = complex.imag |
| 178 | + __add__ = lambda self, other: type(self)(complex.__add__(self, other)) |
| 179 | + __sub__ = lambda self, other: type(self)(complex.__sub__(self, other)) |
| 180 | + __mul__ = lambda self, other: type(self)(complex.__mul__(self, other)) |
| 181 | + __truediv__ = lambda self, other: type(self)(complex.__truediv__(self, other)) |
| 182 | + __len__ = lambda self: 2 |
| 183 | + __round__ = lambda self, ndigits=None: type(self)(round(self.x, ndigits), round(self.y, ndigits)) |
| 184 | + |
| 185 | + def __eq__(self, other): |
| 186 | + return ( |
| 187 | + math.isclose(self.x, other.x, abs_tol=self.abs_tol) and |
| 188 | + math.isclose(self.y, other.y, abs_tol=self.abs_tol) |
| 189 | + ) |
| 190 | + |
| 191 | + def __ne__(self, other): |
| 192 | + return not self.__eq__(other) |
| 193 | + |
| 194 | + def __iter__(self): |
| 195 | + return iter([self.x, self.y]) |
| 196 | + |
| 197 | + def __str__(self): |
| 198 | + return str(tuple(self)) |
| 199 | + |
| 200 | + def __repr__(self): |
| 201 | + return f"{type(self).__name__}{str(self)}" |
| 202 | + |
| 203 | + @classmethod |
| 204 | + def polar(cls, radians, magnitude): |
| 205 | + return cls( |
| 206 | + round(math.cos(radians) * magnitude, 10), |
| 207 | + round(math.sin(radians) * magnitude, 10), |
| 208 | + ) |
| 209 | + |
| 210 | + @property |
| 211 | + def magnitude(self): |
| 212 | + return abs(self) |
| 213 | + |
| 214 | + @property |
| 215 | + def degrees(self): |
| 216 | + return math.degrees(self.radians) |
| 217 | + |
| 218 | + @property |
| 219 | + def radians(self): |
| 220 | + return math.atan2(self.y, self.x) |
| 221 | + |
| 222 | + def with_x(self, value): |
| 223 | + return type(self)(value, self.y) |
| 224 | + |
| 225 | + def with_y(self, value): |
| 226 | + return type(self)(self.x, value) |
| 227 | + |
| 228 | + def with_magnitude(self, value): |
| 229 | + return self * value / abs(self) |
| 230 | + |
| 231 | + def with_radians(self, value): |
| 232 | + magnitude = abs(self) |
| 233 | + return type(self).polar(value, magnitude) |
| 234 | + |
| 235 | + def with_degrees(self, value): |
| 236 | + radians = math.radians(value) |
| 237 | + magnitude = abs(self) |
| 238 | + return type(self).polar(radians, magnitude) |
0 commit comments