summaryrefslogtreecommitdiffstats
path: root/utils.py
diff options
context:
space:
mode:
authorTomasz Kramkowski <tk@the-tk.com>2021-12-15 22:27:25 +0000
committerTomasz Kramkowski <tk@the-tk.com>2021-12-15 22:27:25 +0000
commitca06a82062340e2a00226ef561b453f70b20d17b (patch)
treecbdd784d8da9bbd62ebf18cd955abd65b9a1397c /utils.py
parent1d03c102f813f819697ea95f96eee6dbe0464be1 (diff)
downloadaoc2021-ca06a82062340e2a00226ef561b453f70b20d17b.tar.gz
aoc2021-ca06a82062340e2a00226ef561b453f70b20d17b.tar.xz
aoc2021-ca06a82062340e2a00226ef561b453f70b20d17b.zip
wip: Point2D and friends
Diffstat (limited to 'utils.py')
-rw-r--r--utils.py58
1 files changed, 57 insertions, 1 deletions
diff --git a/utils.py b/utils.py
index 91b90ff..83a54c1 100644
--- a/utils.py
+++ b/utils.py
@@ -1,8 +1,64 @@
from collections import deque
from collections.abc import Iterable, Iterator, Generator
+from dataclasses import dataclass
from itertools import islice
+from math import sqrt
from sys import argv
-from typing import TypeVar
+from typing import TypeVar, Generic, Union, Optional, cast
+
+Numeric = Union[int, float]
+
+T = TypeVar('T', bound=Numeric)
+@dataclass(frozen=True)
+class Point2D(Generic[T]):
+ x: T
+ y: T
+
+ def __str__(self) -> str:
+ return f'({self.x}, {self.y})'
+
+ def __add__(self, other: 'Point2D') -> Union['Point2D', bool]:
+ if isinstance(other, Point2D):
+ return Point2D(self.x + other.x, self.y + other.y)
+ return NotImplemented
+
+ def __sub__(self, other: 'Point2D') -> Union['Point2D', bool]:
+ if isinstance(other, Point2D):
+ return Point2D(self.x - other.x, self.y - other.y)
+ return NotImplemented
+
+ def __abs__(self) -> 'Point2D':
+ return Point2D(abs(self.x), abs(self.y))
+
+T = TypeVar('T', bound=Numeric)
+def norm_1(p: Point2D[T]) -> T:
+ return abs(p.x) + abs(p.y)
+
+def norm_2(p: Point2D) -> float:
+ return sqrt(p.x ** 2 + p.y ** 2)
+
+T = TypeVar('T', bound=Numeric)
+def norm_inf(p: Point2D[T]) -> T:
+ return max(abs(p.x), abs(p.y))
+
+T = TypeVar('T', bound=Numeric)
+def inbounds(p: Point2D[T], a: Point2D[T], b: Optional[Point2D[T]] = None) -> bool:
+ if b is None and isinstance(a.x, int):
+ b = a
+ a = cast(Point2D[T], Point2D(0, 0))
+ return p.x >= a.x and p.y >= a.y and p.x < b.x and p.y < b.y
+
+def adjacent(p: Point2D[int], diagonal: bool = True) \
+ -> Generator[Point2D[int], None, None]:
+ for dx in range(-1, 2):
+ for dy in range(-1, 2):
+ if dx == 0 and dy == 0: continue
+ if dx != 0 and dy != 0 and not adjacent: continue
+ yield Point2D(p.x + dx, p.y + dy)
+
+def adjacent_bounded(p: Point2D[int], bound: Point2D[int], diagonal: bool = True) \
+ -> filter[Point2D[int]]: # This has to return filter[...] not Generator[...] because ???
+ return filter(lambda p: inbounds(p, bound), adjacent(p, diagonal))
T = TypeVar('T')
def sliding_window(iterable: Iterable[T], n: int) -> Generator[tuple[T, ...], None, None]: