Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 884fe34

Browse files
committedApr 17, 2024
AoC 2016 Day 22 Part 2
1 parent 3caa084 commit 884fe34

File tree

1 file changed

+93
-50
lines changed

1 file changed

+93
-50
lines changed
 

‎src/main/python/AoC2016_22.py

Lines changed: 93 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,28 @@
44
#
55

66
from __future__ import annotations
7+
8+
import sys
79
from typing import NamedTuple
8-
import aocd
9-
import re
10-
from aoc import my_aocd
1110

11+
from aoc.common import InputData
12+
from aoc.common import SolutionBase
13+
from aoc.common import aoc_samples
14+
from aoc.geometry import Position
1215

13-
REGEX = r'^\/dev\/grid\/node-x([0-9]+)-y([0-9]+)' \
14-
+ r'\s+[0-9]+T\s+([0-9]+)T\s+([0-9]+)T\s+[0-9]+%$'
16+
TEST = """\
17+
root@ebhq-gridcenter# df -h
18+
Filesystem Size Used Avail Use%
19+
/dev/grid/node-x0-y0 10T 8T 2T 80%
20+
/dev/grid/node-x0-y1 11T 6T 5T 54%
21+
/dev/grid/node-x0-y2 32T 28T 4T 87%
22+
/dev/grid/node-x1-y0 9T 7T 2T 77%
23+
/dev/grid/node-x1-y1 8T 0T 8T 0%
24+
/dev/grid/node-x1-y2 11T 7T 4T 63%
25+
/dev/grid/node-x2-y0 10T 6T 4T 60%
26+
/dev/grid/node-x2-y1 9T 8T 1T 88%
27+
/dev/grid/node-x2-y2 9T 6T 3T 66%
28+
"""
1529

1630

1731
class Node(NamedTuple):
@@ -21,63 +35,92 @@ class Node(NamedTuple):
2135
free: int
2236

2337
@classmethod
24-
def of(cls, x: str, y: str, used: str, free: str) -> Node:
25-
return Node(int(x), int(y), int(used), int(free))
38+
def from_input(cls, line: str) -> Node:
39+
fs, size, used, avail, pct = line.split()
40+
x, y = fs.split("-")[-2:]
41+
return Node(int(x[1:]), int(y[1:]), int(used[:-1]), int(avail[:-1]))
2642

27-
def __eq__(self, other) -> bool:
28-
if other is None:
43+
def __eq__(self, other: object) -> bool:
44+
if other is None or not type(other) is Node:
2945
return False
3046
return self.x == other.x and self.y == other.y
3147

3248
def is_not_empty(self) -> bool:
3349
return self.used > 0
3450

3551

36-
def _parse(inputs: tuple[str]) -> list[Node]:
37-
return [Node.of(*re.search(REGEX, i).groups()) for i in inputs[2:]]
38-
39-
40-
def part_1(inputs: tuple[str]) -> str:
41-
nodes = _parse(inputs)
42-
return sum(1
43-
for b in nodes
44-
for a in nodes if a.is_not_empty()
45-
if a != b and a.used <= b.free)
46-
47-
48-
def part_2(inputs: tuple[str]) -> str:
49-
return 0
52+
class Cluster(NamedTuple):
53+
nodes: list[Node]
5054

51-
52-
TEST = '''\
53-
root@ebhq-gridcenter# df -h
54-
Filesystem Size Used Avail Use%
55-
/dev/grid/node-x0-y0 10T 8T 2T 80%
56-
/dev/grid/node-x0-y1 11T 6T 5T 54%
57-
/dev/grid/node-x0-y2 32T 28T 4T 87%
58-
/dev/grid/node-x1-y0 9T 7T 2T 77%
59-
/dev/grid/node-x1-y1 8T 0T 8T 0%
60-
/dev/grid/node-x1-y2 11T 7T 4T 63%
61-
/dev/grid/node-x2-y0 10T 6T 4T 60%
62-
/dev/grid/node-x2-y1 9T 8T 1T 88%
63-
/dev/grid/node-x2-y2 9T 6T 3T 66%
64-
'''.splitlines()
55+
@classmethod
56+
def from_input(cls, input: InputData) -> Cluster:
57+
return Cluster([Node.from_input(line) for line in list(input)[2:]])
58+
59+
def get_unusable_nodes(self) -> set[Node]:
60+
max_free = max(self.nodes, key=lambda n: n.free).free
61+
return {n for n in self.nodes if n.used > max_free}
62+
63+
def get_max_x(self) -> int:
64+
return max(self.nodes, key=lambda n: n.x).x
65+
66+
def get_empty_node(self) -> Node:
67+
empty_nodes = [n for n in self.nodes if n.used == 0]
68+
assert len(empty_nodes) == 1, "Expected 1 empty node"
69+
return empty_nodes[0]
70+
71+
def get_goal_node(self) -> Node:
72+
return [n for n in self.nodes if n.x == self.get_max_x() and n.y == 0][
73+
0
74+
]
75+
76+
77+
Input = Cluster
78+
Output1 = int
79+
Output2 = int
80+
81+
82+
class Solution(SolutionBase[Input, Output1, Output2]):
83+
def parse_input(self, inputs: InputData) -> Input:
84+
return Cluster.from_input(inputs)
85+
86+
def part_1(self, cluster: Input) -> int:
87+
return sum(
88+
1
89+
for b in cluster.nodes
90+
for a in filter(lambda a: a.is_not_empty(), cluster.nodes)
91+
if a != b and a.used <= b.free
92+
)
93+
94+
def part_2(self, cluster: Input) -> int:
95+
unusable = cluster.get_unusable_nodes()
96+
hole_ys = {n.y for n in unusable}
97+
assert len(hole_ys) == 1, "Expected all unusable nodes in 1 row"
98+
hole_y = next(_ for _ in hole_ys)
99+
if hole_y <= 1:
100+
raise RuntimeError("Unsolvable")
101+
assert not (
102+
max(unusable, key=lambda n: n.x).x != cluster.get_max_x()
103+
), "Expected unusable row to touch side"
104+
hole_x = min(unusable, key=lambda n: n.x).x
105+
hole = Position(hole_x - 1, hole_y)
106+
empty_node = cluster.get_empty_node()
107+
d1 = Position(empty_node.x, empty_node.y).manhattan_distance(hole)
108+
goal_node = cluster.get_goal_node()
109+
d2 = Position(goal_node.x - 1, goal_node.y).manhattan_distance(hole)
110+
d3 = 5 * (goal_node.x - 1)
111+
return d1 + d2 + d3 + 1
112+
113+
@aoc_samples((("part_1", TEST, 7),))
114+
def samples(self) -> None:
115+
pass
116+
117+
118+
solution = Solution(2016, 22)
65119

66120

67121
def main() -> None:
68-
puzzle = aocd.models.Puzzle(2016, 22)
69-
my_aocd.print_header(puzzle.year, puzzle.day)
70-
71-
assert part_1(TEST) == 7
72-
assert part_2(TEST) == 0
73-
74-
inputs = my_aocd.get_input_data(puzzle, 1052)
75-
result1 = part_1(inputs)
76-
print(f"Part 1: {result1}")
77-
result2 = part_2(inputs)
78-
print(f"Part 2: {result2}")
79-
my_aocd.check_results(puzzle, result1, result2)
122+
solution.run(sys.argv)
80123

81124

82-
if __name__ == '__main__':
125+
if __name__ == "__main__":
83126
main()

0 commit comments

Comments
 (0)
Please sign in to comment.