Skip to content

Commit ee77820

Browse files
committed
fix: 2023 solutions
1 parent d09877e commit ee77820

File tree

10 files changed

+516
-171
lines changed

10 files changed

+516
-171
lines changed

_2023/solutions/day01.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Day 1: Trebuchet?!
22
33
This module provides the solution for Advent of Code 2023 - Day 1.
4+
45
It handles extraction of calibration values from strings containing digits
56
and written numbers.
67

_2023/solutions/day02.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Day 2: Cube Conundrum.
1+
"""Day 2: Cube Conundrum
22
33
This module provides the solution for Advent of Code 2023 - Day 2.
4+
45
It handles analysis of cube games where colored cubes are drawn from a bag
56
in multiple sets, with each game having several draws of red, green, and blue cubes.
67
@@ -59,7 +60,10 @@ def part1(self, data: list[str]) -> int:
5960
"""
6061
total = 0
6162
for game in data:
62-
game_id = int(re.search(self.id_regex, game).group(1))
63+
match = re.search(self.id_regex, game)
64+
if match is None:
65+
continue
66+
game_id = int(match.group(1))
6367
sets = game.split(":")[1].split(";")
6468
if all(self.is_possible(s) for s in sets):
6569
total += game_id

_2023/solutions/day03.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Day 3: Gear Ratios.
1+
"""Day 3: Gear Ratios
22
33
This module provides the solution for Advent of Code 2023 - Day 3.
4+
45
It handles analysis of engine schematics containing numbers and symbols,
56
where valid part numbers are determined by their adjacency to symbols.
67

_2023/solutions/day04.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Day 4: Scratchcards.
1+
"""Day 4: Scratchcards
22
33
This module provides the solution for Advent of Code 2023 - Day 4.
44
It handles scoring scratchcards where each card has two sets of numbers:

_2023/solutions/day05.py

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,54 @@
1-
from aoc.models.base import SolutionBase
1+
"""Day 5: If You Give A Seed A Fertilizer
22
3+
This module provides the solution for Advent of Code 2023 - Day 5.
34
4-
class Solution(SolutionBase):
5-
"""Solution for Advent of Code 2023 - Day 5: If You Give A Seed A Fertilizer.
5+
It handles mapping seeds through various conversion stages (soil, fertilizer,
6+
water, light, temperature, humidity, and location). Each stage has mapping
7+
rules that convert source numbers to destination numbers based on ranges.
8+
9+
The module contains a Solution class that inherits from SolutionBase and implements
10+
methods to parse input data, convert numbers through mapping stages, and find the
11+
lowest final location number for both individual seeds and seed ranges.
12+
"""
13+
14+
from aoc.models.base import SolutionBase
615

7-
This class solves a puzzle involving mapping seeds through various conversion stages
8-
(soil, fertilizer, water, light, temperature, humidity, and location). Each stage
9-
has its own mapping rules that convert source numbers to destination numbers based
10-
on ranges.
1116

12-
Input format:
13-
Multiple sections separated by blank lines:
14-
- First line: "seeds: " followed by seed numbers
15-
- Multiple mapping sections, each starting with a header (e.g., "seed-to-soil map:")
16-
- Each mapping section contains lines of three numbers:
17-
[destination_start source_start range_length]
17+
class Solution(SolutionBase):
18+
"""Process seeds through mapping stages to find lowest location numbers.
1819
19-
Part 1 processes individual seed numbers through all mappings.
20-
Part 2 treats seed numbers as pairs representing ranges (start and length).
20+
This solution handles two types of seed processing:
21+
Part 1 processes individual seed numbers through all mapping stages.
22+
Part 2 treats seed numbers as pairs representing ranges (start and length)
23+
and processes entire ranges to find the lowest location.
2124
22-
This class inherits from `SolutionBase` and provides methods to parse the input,
23-
convert numbers through mapping ranges, and find the lowest final location number.
25+
The solution uses range-based mappings to efficiently convert numbers through
26+
multiple stages of transformations.
2427
"""
2528

2629
def parse_data(
2730
self, data: list[str], *, use_ranges: bool = False
28-
) -> tuple[list[int], dict[str, list[str]]]:
31+
) -> tuple[list[int] | list[tuple[int, int]], dict[str, list[list[int]]]]:
2932
"""Parse input data into seeds and mapping sections.
3033
34+
Each line of input is processed to extract seed numbers and mapping rules.
35+
The input format includes a seeds line followed by multiple mapping sections,
36+
each with a header and range definitions.
37+
3138
Args:
32-
data: Raw input lines
33-
use_ranges: If True, parse seed numbers as pairs representing ranges
39+
data (list[str]): List of strings where each string is a line from the input
40+
use_ranges (bool): If True, parse seed numbers as (start, length) pairs
3441
3542
Returns
3643
-------
37-
Tuple of:
38-
- seeds: List of individual numbers or list of (start, length)
39-
tuples if `use_ranges=True`
40-
- mappings: Dict mapping section names to lists of
41-
[dest_start, source_start, range_len]
44+
Tuple containing:
45+
- seeds: List of individual numbers or list of (start, length) tuples
46+
- mappings: Dict mapping section names to lists of [dest_start, source_start, range_len]
4247
"""
4348
# Initialize data structures
44-
seeds = []
45-
mappings = {}
46-
current_map = None
49+
seeds: list[int] | list[tuple[int, int]] = []
50+
mappings: dict[str, list[list[int]]] = {}
51+
current_map: str | None = None
4752

4853
for line in data:
4954
# Skip empty lines
@@ -55,14 +60,13 @@ def parse_data(
5560
seed_numbers = [int(x) for x in line.split(":")[1].strip().split()]
5661
if use_ranges:
5762
# Convert pairs of numbers into (start, length) tuples
58-
seeds = []
59-
for i in range(0, len(seed_numbers), 2):
60-
if i + 1 < len(seed_numbers): # Ensure we have a pair
61-
seeds.append((seed_numbers[i], seed_numbers[i + 1]))
62-
63+
seeds = [
64+
(seed_numbers[i], seed_numbers[i + 1])
65+
for i in range(0, len(seed_numbers), 2)
66+
if i + 1 < len(seed_numbers)
67+
]
6368
else:
6469
seeds = seed_numbers
65-
6670
continue
6771

6872
# Check for new mapping section
@@ -84,36 +88,48 @@ def process_seed_range(
8488
) -> int:
8589
"""Process a range of seed numbers to find lowest location number.
8690
91+
Iterates through each seed in the specified range, mapping it through
92+
all conversion stages to find the minimum final location value.
93+
8794
Args:
88-
start: Starting seed number
89-
length: Length of range to process
90-
mappings: Dictionary of mapping ranges for each stage
95+
start (int): Starting seed number for the range
96+
length (int): Number of seeds in the range
97+
mappings (dict[str, list[list[int]]]): Dictionary of mapping ranges for each stage
9198
9299
Returns
93100
-------
94101
Lowest location number found in the range
95102
"""
96-
lowest_location = float("inf")
103+
lowest_location: int | float = float("inf")
97104

98105
# Process each seed in the range
99106
for seed in range(start, start + length):
100107
location = self.map_seed(seed, mappings)
101108
lowest_location = min(lowest_location, location)
102109

103-
return lowest_location
110+
return int(lowest_location)
104111

105-
def convert(self, num: int, ranges: list[int]) -> int:
112+
def convert(self, num: int, ranges: list[list[int]]) -> int:
106113
"""Convert a single number through one mapping section's ranges.
107114
115+
Checks each range to see if the number falls within its source range.
116+
If a match is found, the number is converted to the destination range.
117+
If no match is found, the number maps to itself.
118+
108119
Args:
109-
num: Number to convert
110-
ranges: List of [dest_start, source_start, range_len] mappings
120+
num (int): Number to convert
121+
ranges (list[list[int]]): List of [dest_start, source_start, range_len] mappings
111122
112123
Returns
113124
-------
114125
Converted number, or original number if no range matches
115126
"""
116-
for dst_start, src_start, length in ranges:
127+
for range_item in ranges:
128+
dst_start: int
129+
src_start: int
130+
length: int
131+
dst_start, src_start, length = range_item
132+
117133
if src_start <= num < src_start + length:
118134
# Calculate offset from source_start and add to dest_start
119135
offset = num - src_start
@@ -122,12 +138,15 @@ def convert(self, num: int, ranges: list[int]) -> int:
122138
# If no range matches, number maps to itself
123139
return num
124140

125-
def map_seed(self, seed: int, mappings: dict[str, list[list[int]]]):
141+
def map_seed(self, seed: int, mappings: dict[str, list[list[int]]]) -> int:
126142
"""Process a seed number through all mapping stages.
127143
144+
Takes an initial seed value and applies each mapping stage in sequence
145+
to produce the final location number.
146+
128147
Args:
129-
seed: Initial seed number
130-
mappings: Dictionary of mapping ranges for each stage
148+
seed (int): Initial seed number
149+
mappings (dict[str, list[list[int]]]): Dictionary of mapping ranges for each stage
131150
132151
Returns
133152
-------
@@ -142,16 +161,20 @@ def map_seed(self, seed: int, mappings: dict[str, list[list[int]]]):
142161
return current_value
143162

144163
def part1(self, data: list[str]) -> int:
145-
"""Solve part 1: Process individual seed numbers.
164+
"""Calculate lowest location number from individual seed numbers.
165+
166+
Processes each seed number through all mapping stages and finds
167+
the minimum final location value.
146168
147169
Args:
148-
data: Raw input data
170+
data (list[str]): List of strings representing the puzzle input
149171
150172
Returns
151173
-------
152174
Lowest location number found across all seeds
153175
"""
154-
seeds, mappings = self.parse_data(data)
176+
seeds_raw, mappings = self.parse_data(data)
177+
seeds: list[int] = seeds_raw # type: ignore[assignment]
155178

156179
locations = set()
157180
for seed in seeds:
@@ -160,23 +183,26 @@ def part1(self, data: list[str]) -> int:
160183
return min(locations)
161184

162185
def part2(self, data: list[str]) -> int:
163-
"""Solve part 2: Process seed numbers as ranges.
186+
"""Calculate lowest location number from seed ranges.
187+
188+
Treats seed numbers as pairs representing ranges (start and length).
189+
Processes all seeds in each range to find the minimum location.
164190
165191
Args:
166-
data: Raw input data
192+
data (list[str]): List of strings representing the puzzle input
167193
168194
Returns
169195
-------
170196
Lowest location number found across all seed ranges
171197
"""
172198
# Parse data with ranges enabled
173-
seed_ranges, mappings = self.parse_data(data, use_ranges=True)
199+
seed_ranges_raw, mappings = self.parse_data(data, use_ranges=True)
200+
seed_ranges: list[tuple[int, int]] = seed_ranges_raw # type: ignore[assignment]
174201

175202
# Find lowest location across all ranges
176-
lowest_location = float("inf")
177-
203+
lowest_location: int | float = float("inf")
178204
for start, length in seed_ranges:
179205
range_lowest = self.process_seed_range(start, length, mappings)
180206
lowest_location = min(lowest_location, range_lowest)
181207

182-
return lowest_location if lowest_location != float("inf") else None
208+
return int(lowest_location) if lowest_location != float("inf") else 0

0 commit comments

Comments
 (0)