summaryrefslogtreecommitdiffstats
path: root/aoc
diff options
context:
space:
mode:
Diffstat (limited to 'aoc')
-rw-r--r--aoc/__init__.py43
-rw-r--r--aoc/__main__.py66
-rw-r--r--aoc/datastructures.py98
-rw-r--r--aoc/decorators.py30
-rw-r--r--aoc/mixins.py77
-rw-r--r--aoc/template/__init__.py16
-rw-r--r--aoc/template/example.txt0
-rw-r--r--aoc/template/input.txt0
-rw-r--r--aoc/test_init.py8
-rw-r--r--aoc/tests/__init__.py0
-rw-r--r--aoc/tests/test_datastructures.py79
-rw-r--r--aoc/utils.py6
12 files changed, 423 insertions, 0 deletions
diff --git a/aoc/__init__.py b/aoc/__init__.py
new file mode 100644
index 0000000..7a089f6
--- /dev/null
+++ b/aoc/__init__.py
@@ -0,0 +1,43 @@
1# -*- coding: utf-8 -*-
2import os
3from abc import ABC
4from typing import Generator, Any, Iterator, Dict, TypeVar, Generic
5
6T = TypeVar("T")
7I = TypeVar("I")
8
9
10class BaseAssignment(Generic[T, I], ABC):
11 example_result: T = NotImplemented
12 example_kwargs: Dict = {}
13
14 def __init__(self, path):
15 self.path = path
16
17 def __str__(self):
18 return f"{self.__module__}.{self.__class__.__name__}"
19
20 def parse_item(self, item: str) -> I:
21 return item
22
23 @property
24 def part(self) -> int:
25 return 1 if self.__class__.__name__.endswith("One") else 2
26
27 def read_input(self, example=False) -> Iterator[I]:
28 file = f"{self.path}/input.txt"
29
30 if example or not os.path.isfile(file):
31 for file in [
32 f"{self.path}/example_part_{self.part}.txt",
33 f"{self.path}/example.txt",
34 ]:
35 if os.path.exists(file):
36 break
37
38 with open(file, "r") as input_file:
39 for line in input_file.readlines():
40 yield self.parse_item(line.strip("\n"))
41
42 def run(self, input: Iterator[I]) -> T:
43 raise NotImplementedError("Please implement run")
diff --git a/aoc/__main__.py b/aoc/__main__.py
new file mode 100644
index 0000000..9bbaed9
--- /dev/null
+++ b/aoc/__main__.py
@@ -0,0 +1,66 @@
1import enum
2import typing
3import importlib
4import os
5from pathlib import Path
6from shutil import copytree
7from time import perf_counter
8from typing import List, Callable
9import typer
10
11app = typer.Typer()
12
13
14class AssignmentPart(str, enum.Enum):
15 One = "1"
16 Two = "2"
17
18
19def day(assignment_part: str) -> str:
20 return assignment_part
21
22
23def kwargs(kwarg: str) -> List:
24 return kwarg.split("=")
25
26
27@app.command()
28def run(day: str,
29 part: str = typer.Option("One", '--part', help="Assignment part. Defaults to 'One'.", show_choices=True),
30 example: bool = typer.Option(False, '--example', help="Use an example input file"),
31 kwargs: List[str] = typer.Argument(None)
32 ):
33
34 assignment_day = importlib.import_module(day)
35
36 Assignment = getattr(assignment_day, f"Assignment{part}")
37 assignment = Assignment(
38 path=os.path.dirname(assignment_day.__file__), **dict(kwargs)
39 )
40
41 start = perf_counter()
42 typer.echo(
43 assignment.run(
44 input=assignment.read_input(example=example),
45 )
46 )
47 end = perf_counter()
48 delta = end - start
49 typer.secho(f'\n{(delta if delta > 1 else delta * 1000):.3f}{"s" if delta > 1 else "ms"}', fg="green")
50
51
52@app.command()
53def new(day: str):
54 self = importlib.import_module(".", package="aoc")
55 path = Path(self.__file__)
56 template = path.parent.joinpath("template")
57 target = path.parent.parent.joinpath(day)
58
59 try:
60 copytree(template, target)
61 except FileExistsError:
62 typer.secho(f"{day} already exists", fg="red")
63
64
65if __name__ == "__main__":
66 app()
diff --git a/aoc/datastructures.py b/aoc/datastructures.py
new file mode 100644
index 0000000..1f141e2
--- /dev/null
+++ b/aoc/datastructures.py
@@ -0,0 +1,98 @@
1# -*- coding: utf-8 -*-
2from collections import namedtuple
3from typing import Iterator
4
5
6class Coordinate(namedtuple("Coordinate", ["x", "y"])):
7 def __sub__(self, other: "Coordinate") -> "Coordinate":
8 return Coordinate(self.x - other.x, self.y - other.y)
9
10 def __add__(self, other: "Coordinate") -> "Coordinate":
11 return Coordinate(self.x + other.x, self.y + other.y)
12
13 def manhattan_distance(self, other: "Coordinate") -> int:
14 return abs(self.x - other.x) + abs(self.y - other.y)
15
16 @property
17 def polarity(self) -> "Coordinate":
18 try:
19 px = abs(self.x) / self.x
20 except ZeroDivisionError:
21 px = 0
22
23 try:
24 py = abs(self.y) / self.y
25 except ZeroDivisionError:
26 py = 0
27
28 return Coordinate(
29 px,
30 py,
31 )
32
33 def neighbours(self, no_diagonal: bool = False) -> Iterator["Coordinate"]:
34 if no_diagonal:
35 yield self + Coordinate(-1, 0)
36 yield self + Coordinate(1, 0)
37 yield self + Coordinate(0, -1)
38 yield self + Coordinate(0, 1)
39 else:
40 for dy in range(self.y - 1, self.y + 2):
41 for dx in range(self.x - 1, self.x + 2):
42 if dy == self.y and dx == self.x:
43 continue
44
45 yield Coordinate(dx, dy)
46
47
48class Coordinate3(namedtuple("Coordinate3", ["x", "y", "z"])):
49 def __sub__(self, other: "Coordinate3") -> "Coordinate3":
50 return Coordinate3(self.x - other.x, self.y - other.y, self.z - other.z)
51
52 def __add__(self, other: "Coordinate3") -> "Coordinate3":
53 return Coordinate3(self.x + other.x, self.y + other.y, self.z + other.z)
54
55 def manhattan_distance(self, other: "Coordinate3") -> int:
56 return abs(self.x - other.x) + abs(self.y - other.y) + abs(self.z - other.z)
57
58 @property
59 def polarity(self) -> "Coordinate3":
60 try:
61 px = abs(self.x) / self.x
62 except ZeroDivisionError:
63 px = 0
64
65 try:
66 py = abs(self.y) / self.y
67 except ZeroDivisionError:
68 py = 0
69
70 try:
71 pz = abs(self.z) / self.z
72 except ZeroDivisionError:
73 pz = 0
74
75 return Coordinate3(
76 px,
77 py,
78 pz,
79 )
80
81 def neighbours(self, no_diagonal: bool = False) -> Iterator["Coordinate3"]:
82 if no_diagonal:
83 yield self + Coordinate3(-1, 0, 0)
84 yield self + Coordinate3(1, 0, 0)
85 yield self + Coordinate3(0, -1, 0)
86 yield self + Coordinate3(0, 1, 0)
87 yield self + Coordinate3(0, 0, -1)
88 yield self + Coordinate3(0, 0, 1)
89 else:
90 for dz in range(self.z - 1, self.z + 2):
91 for dy in range(self.y - 1, self.y + 2):
92 for dx in range(self.x - 1, self.x + 2):
93 coordinate = Coordinate3(dx, dy, dz)
94
95 if dy == self.y and dx == self.x and dz == self.z:
96 continue
97
98 yield coordinate
diff --git a/aoc/decorators.py b/aoc/decorators.py
new file mode 100644
index 0000000..42603c2
--- /dev/null
+++ b/aoc/decorators.py
@@ -0,0 +1,30 @@
1# -*- coding: utf-8 -*-
2from functools import wraps
3from typing import Iterator, TypeVar, Callable, List
4
5T = TypeVar("T")
6I = TypeVar("I")
7
8AssignmentRun = Callable[[Iterator[I], ...], Iterator[T]]
9AssignmentRunList = Callable[[List[I], ...], Iterator[T]]
10
11
12def infinite_generator(func: AssignmentRun) -> AssignmentRun:
13 @wraps(func)
14 def wrapper(*args, **kwargs):
15 items = list(func(*args, **kwargs))
16