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