summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--aoc/datastructures.py68
-rw-r--r--aoc/mixins.py20
-rw-r--r--aoc/tests/__init__.py0
-rw-r--r--aoc/tests/test_datastructures.py16
-rw-r--r--day17/__init__.py12
-rw-r--r--day18/__init__.py75
-rw-r--r--day18/example.txt13
-rw-r--r--day18/input.txt2057
-rw-r--r--day18/test_init.py24
9 files changed, 2279 insertions, 6 deletions
diff --git a/aoc/datastructures.py b/aoc/datastructures.py
index 81a68e4..244aa1e 100644
--- a/aoc/datastructures.py
+++ b/aoc/datastructures.py
@@ -1,5 +1,6 @@
1# -*- coding: utf-8 -*- 1# -*- coding: utf-8 -*-
2from collections import namedtuple 2from collections import namedtuple
3from typing import Iterator
3 4
4 5
5class Coordinate(namedtuple("Coordinate", ["x", "y"])): 6class Coordinate(namedtuple("Coordinate", ["x", "y"])):
@@ -28,3 +29,70 @@ class Coordinate(namedtuple("Coordinate", ["x", "y"])):
28 px, 29 px,
29 py, 30 py,
30 ) 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:
96 continue
97
98 yield coordinate
diff --git a/aoc/mixins.py b/aoc/mixins.py
index faeb3eb..5986b6e 100644
--- a/aoc/mixins.py
+++ b/aoc/mixins.py
@@ -55,3 +55,23 @@ class AStarMixin(Generic[Node]):
55 55
56 if neighbour not in open_nodes: 56 if neighbour not in open_nodes:
57 open_nodes.add(neighbour) 57 open_nodes.add(neighbour)
58
59
60class BreathFirstSearchMixin(Generic[Node]):
61 @staticmethod
62 def bfs(
63 start: Node, neighbours: Callable[[Node], Iterator[Node]]
64 ) -> Iterator[Node]:
65 queue = [start]
66 searched = set()
67
68 while len(queue):
69 item = queue.pop(0)
70
71 for neighbour in neighbours(item):
72 if neighbour not in searched and neighbour not in set(queue):
73 queue.append(neighbour)
74
75 searched.add(item)
76
77 yield item
diff --git a/aoc/tests/__init__.py b/aoc/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/aoc/tests/__init__.py
diff --git a/aoc/tests/test_datastructures.py b/aoc/tests/test_datastructures.py
new file mode 100644
index 0000000..a7965f5
--- /dev/null
+++ b/aoc/tests/test_datastructures.py
@@ -0,0 +1,16 @@
1# -*- coding: utf-8 -*-
2from aoc.datastructures import Coordinate3
3
4
5class TestCoordinate3:
6 def test_neighbours_nodiagonal(self):
7 coordinate = Coordinate3(0, 0, 0)
8
9 assert set(coordinate.neighbours(True)) == {
10 Coordinate3(-1, 0, 0),
11 Coordinate3(1, 0, 0),
12 Coordinate3(0, -1, 0),
13 Coordinate3(0, 1, 0),
14 Coordinate3(0, 0, -1),
15 Coordinate3(0, 0, 1),
16 }
diff --git a/day17/__init__.py b/day17/__init__.py
index 41b25dc..03e9003 100644
--- a/day17/__init__.py
+++ b/day17/__init__.py
@@ -30,12 +30,12 @@ class AssignmentOne(Assignment):
30 30
31 moves = self.infinite_moves(input) 31 moves = self.infinite_moves(input)
32 32
33 for move in self.infinite_moves(next(input)): 33 # for move in self.infinite_moves(next(input)):
34 i += 1 34 # i += 1
35 print(move) 35 # print(move)
36 36 #
37 if i == 100: 37 # if i == 100:
38 break 38 # break
39 39
40 40
41class AssignmentTwo(Assignment): 41class AssignmentTwo(Assignment):
diff --git a/day18/__init__.py b/day18/__init__.py
new file mode 100644
index 0000000..0500963
--- /dev/null
+++ b/day18/__init__.py
@@ -0,0 +1,75 @@
1# -*- coding: utf-8 -*-
2from abc import ABC
3from typing import Iterator, Set
4
5from aoc import BaseAssignment
6from aoc.datastructures import Coordinate3
7from aoc.mixins import BreathFirstSearchMixin
8
9
10class Assignment(BaseAssignment[int, Coordinate3], ABC):
11 def parse_item(self, item: str) -> Coordinate3:
12 x, y, z = item.split(",")
13 return Coordinate3(int(x), int(y), int(z))
14
15 @staticmethod
16 def open_sides(blocks: Set[Coordinate3]) -> int:
17 return len(
18 [
19 neighbour
20 for block in blocks
21 for neighbour in block.neighbours(True)
22 if neighbour not in blocks
23 ]
24 )
25
26
27class AssignmentOne(Assignment):
28 example_result = 64
29
30 def run(self, input: Iterator[Coordinate3]) -> int:
31 blocks = set(input)
32 return self.open_sides(blocks)
33
34
35class AssignmentTwo(Assignment, BreathFirstSearchMixin[Coordinate3]):
36 example_result = 58
37
38 def run(self, input: Iterator[Coordinate3]) -> int:
39 blocks = set(input)
40
41 lower_x = min([block.x for block in blocks]) - 1
42 higher_x = max([block.x for block in blocks]) + 1
43 lower_y = min([block.y for block in blocks]) - 1
44 higher_y = max([block.y for block in blocks]) + 1
45 lower_z = min([block.z for block in blocks]) - 1
46 higher_z = max([block.z for block in blocks]) + 1
47
48 all_coordinates = set(
49 Coordinate3(x, y, z)
50 for x in range(lower_x, higher_x + 1)
51 for y in range(lower_y, higher_y + 1)
52 for z in range(lower_z, higher_z + 1)
53 )
54
55 def nb(c):
56 for n in c.neighbours(True):
57 if n in all_coordinates and n not in blocks:
58 yield n
59
60 outer_coordinates = {
61 coordinate
62 for coordinate in self.bfs(
63 Coordinate3(lower_x, lower_y, lower_z),
64 neighbours=nb,
65 )
66 }
67
68 return len(
69 [
70 neighbour
71 for block in blocks
72 for neighbour in block.neighbours(True)
73 if neighbour in outer_coordinates
74 ]
75 )
diff --git a/day18/example.txt b/day18/example.txt
new file mode 100644
index 0000000..73a7202
--- /dev/null
+++ b/day18/example.txt
@@ -0,0 +1,13 @@