summaryrefslogtreecommitdiffstats
path: root/aoc
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@glootie.lan.tomvanderlee.com>2023-11-19 16:55:03 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2025-12-01 09:53:01 +0100
commitd7e30321ae6ae4c82a8ab7455f6ce33afd719c67 (patch)
treee873d640f909ae3e247adc7661b7d954c8af3a26 /aoc
download2025-d7e30321ae6ae4c82a8ab7455f6ce33afd719c67.tar.gz
2025-d7e30321ae6ae4c82a8ab7455f6ce33afd719c67.tar.bz2
2025-d7e30321ae6ae4c82a8ab7455f6ce33afd719c67.zip
Initial commit
Diffstat (limited to 'aoc')
-rw-r--r--aoc/__init__.py43
-rw-r--r--aoc/__main__.py72
-rw-r--r--aoc/datastructures.py98
-rw-r--r--aoc/decorators.py29
-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.py112
-rw-r--r--aoc/utils.py6
12 files changed, 461 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..596abee
--- /dev/null
+++ b/aoc/__main__.py
@@ -0,0 +1,72 @@
1# -*- coding: utf-8 -*-
2import enum
3import typing
4import importlib
5import os
6from pathlib import Path
7from shutil import copytree
8from time import perf_counter
9from typing import List, Callable
10import typer
11
12app = typer.Typer()
13
14
15class AssignmentPart(str, enum.Enum):
16 One = "1"
17 Two = "2"
18
19
20def day(assignment_part: str) -> str:
21 return AssignmentPart(assignment_part).name
22
23
24def kwargs(kwarg: str) -> List:
25 return kwarg.split("=")
26
27
28@app.command()
29def run(
30 day: str,
31 part: str = typer.Option(
32 "1", "--part", help="Assignment part. Defaults to 'One'.", show_choices=True
33 ),
34 example: bool = typer.Option(False, "--example", help="Use an example input file"),
35 kwargs: List[str] = typer.Argument(None),
36):
37 assignment_day = importlib.import_module(day)
38
39 Assignment = getattr(assignment_day, f"Assignment{AssignmentPart(part).name}")
40 assignment = Assignment(
41 path=os.path.dirname(assignment_day.__file__), **dict(kwargs)
42 )
43
44 start = perf_counter()
45 typer.echo(
46 assignment.run(
47 input=assignment.read_input(example=example),
48 )
49 )
50 end = perf_counter()
51 delta = end - start
52 typer.secho(
53 f'\n{(delta if delta > 1 else delta * 1000):.3f}{"s" if delta > 1 else "ms"}',
54 fg="green",
55 )
56
57
58@app.command()
59def new(day: str):
60 self = importlib.import_module(".", package="aoc")
61 path = Path(self.__file__)
62 template = path.parent.joinpath("template")
63 target = path.parent.parent.joinpath(day)
64
65 try:
66 copytree(template, target)
67 except FileExistsError:
68 typer.secho(f"{day} already exists", fg="red")
69
70
71if __name__ == "__main__":
72 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..98246e6
--- /dev/null
+++ b/aoc/decorators.py
@@ -0,0 +1,29 @@
1# -*- coding: utf-8 -*-
2from functools import wraps
3