1
- from typing import Any
2
- from advent_of_code .util import format_solution , puzzle_input
3
1
from collections import UserDict
4
2
from dataclasses import dataclass , field
5
3
4
+ from advent_of_code .util import format_solution , puzzle_input
5
+
6
6
7
7
@dataclass
8
8
class AlmanacMap (UserDict ):
@@ -18,11 +18,53 @@ def set_key_range(self, key_start: int, value_start: int, map_range: int):
18
18
19
19
def __getitem__ (self , key : int ) -> int :
20
20
for key_start , value_start , map_range in self .key_ranges :
21
- if key_start <= key <= key_start + map_range :
21
+ if key_start <= key < key_start + map_range :
22
22
return value_start + (key - key_start )
23
23
else :
24
24
return key
25
25
26
+ def transform_range (
27
+ self , range_start : int , range_size : int
28
+ ) -> list [tuple [int , int ]]:
29
+ """
30
+ Transform a range of numbers by the mapping, returning potentially many
31
+ (start, size) pairs
32
+ """
33
+ untransformed_ranges = [(range_start , range_size )]
34
+ result = []
35
+
36
+ for map_key_start , map_value_start , map_range in self .key_ranges :
37
+ new_untransformed = []
38
+ for u_range_start , u_range_size in untransformed_ranges :
39
+ overlap_min = max (map_key_start , u_range_start )
40
+ overlap_max = min (
41
+ map_key_start + map_range - 1 ,
42
+ u_range_start + u_range_size - 1 ,
43
+ )
44
+
45
+ if overlap_min <= overlap_max :
46
+ # Transform the overlap and add the rest to the new
47
+ # untransformed list
48
+ map_offset = overlap_min - map_key_start
49
+ transformed_start = map_value_start + map_offset
50
+ transformed_size = overlap_max + 1 - overlap_min
51
+ result .append ((transformed_start , transformed_size ))
52
+
53
+ if overlap_min > u_range_start :
54
+ nu_start = u_range_start
55
+ nu_size = overlap_min - nu_start
56
+ untransformed_ranges .append ((nu_start , nu_size ))
57
+ if overlap_max < (u_range_start + u_range_size - 1 ):
58
+ nu_start = overlap_max + 1
59
+ nu_size = (u_range_start + u_range_size ) - nu_start
60
+ untransformed_ranges .append ((nu_start , nu_size ))
61
+ else :
62
+ new_untransformed .append ((u_range_start , u_range_size ))
63
+
64
+ untransformed_ranges = new_untransformed
65
+
66
+ return result + untransformed_ranges
67
+
26
68
27
69
@dataclass
28
70
class Almanac :
@@ -84,6 +126,24 @@ def get_seed_location(self, seed: int):
84
126
]
85
127
]
86
128
129
+ def get_min_seed_range_location (self , seed_range : tuple [int , int ]) -> int :
130
+ """
131
+ Get the minimum location across the given seed range
132
+ """
133
+ range_mins = set ()
134
+ for r0 in self .seed_to_soil .transform_range (* seed_range ):
135
+ for r1 in self .soil_to_fertilizer .transform_range (* r0 ):
136
+ for r2 in self .fertilizer_to_water .transform_range (* r1 ):
137
+ for r3 in self .water_to_light .transform_range (* r2 ):
138
+ for r4 in self .light_to_temperature .transform_range (* r3 ):
139
+ for r5 in self .temperature_to_humidity .transform_range (* r4 ):
140
+ for r6 in self .humidity_to_location .transform_range (
141
+ * r5
142
+ ):
143
+ range_mins .add (r6 [0 ])
144
+
145
+ return min (range_mins )
146
+
87
147
88
148
def part_1 (almanac_input : list [str ]) -> int :
89
149
almanac = Almanac .from_input (almanac_input )
@@ -94,6 +154,17 @@ def part_1(almanac_input: list[str]) -> int:
94
154
95
155
def part_2 (almanac_input : list [str ]) -> int :
96
156
almanac = Almanac .from_input (almanac_input )
157
+ seed_range_values = [
158
+ int (s ) for s in almanac_input [0 ].split ("seeds:" )[1 ].strip ().split ()
159
+ ]
160
+ seed_ranges = [
161
+ (seed_range_values [i ], seed_range_values [i + 1 ])
162
+ for i in range (0 , len (seed_range_values ), 2 )
163
+ ]
164
+
165
+ return min (
166
+ almanac .get_min_seed_range_location (seed_range ) for seed_range in seed_ranges
167
+ )
97
168
98
169
99
170
if __name__ == "__main__" :
0 commit comments