4
4
#
5
5
6
6
from __future__ import annotations
7
+
8
+ import sys
7
9
from typing import NamedTuple
8
- import aocd
9
- import re
10
- from aoc import my_aocd
11
10
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
12
15
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
+ """
15
29
16
30
17
31
class Node (NamedTuple ):
@@ -21,63 +35,92 @@ class Node(NamedTuple):
21
35
free : int
22
36
23
37
@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 ]))
26
42
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 :
29
45
return False
30
46
return self .x == other .x and self .y == other .y
31
47
32
48
def is_not_empty (self ) -> bool :
33
49
return self .used > 0
34
50
35
51
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 ]
50
54
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 )
65
119
66
120
67
121
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 )
80
123
81
124
82
- if __name__ == ' __main__' :
125
+ if __name__ == " __main__" :
83
126
main ()
0 commit comments