diff --git a/2023/day5/README.md b/2023/day5/README.md index 703ff8d..889ebad 100644 --- a/2023/day5/README.md +++ b/2023/day5/README.md @@ -130,3 +130,26 @@ number**. In this example, the corresponding types are: So, the lowest location number in this example is `**35**`. **What is the lowest location number that corresponds to any of the initial seed numbers?** + +## Part 2 + +Everyone will starve if you only plant such a small number of seeds. Re-reading the almanac, it looks like the seeds: +line actually describes ranges of seed numbers. + +The values on the initial seeds: line come in pairs. Within each pair, the first value is the start of the range and +the second value is the length of the range. So, in the first line of the example above: + +`seeds: 79 14 55 13` + +This line describes two ranges of seed numbers to be planted in the garden. The first range starts with seed number `79` +and contains 14 values: `79`, `80`, ..., `91`, `92`. The second range starts with seed number `55` and contains `13` +values: `55`, `56`, ..., `66`, `67`. + +Now, rather than considering four seed numbers, you need to consider a total of **27** seed numbers. + +In the above example, the lowest location number can be obtained from seed number `82`, which corresponds to soil `84`, +fertilizer `84`, water `84`, light `77`, temperature `45`, humidity `46`, and location `46`. +So, the lowest location number is `46`. + +Consider all of the initial seed numbers listed in the ranges on the first line of the almanac. +**What is the lowest location number that corresponds to any of the initial seed numbers?** diff --git a/2023/day5/src/main.rs b/2023/day5/src/main.rs index 828d1b8..9971a8e 100644 --- a/2023/day5/src/main.rs +++ b/2023/day5/src/main.rs @@ -50,9 +50,13 @@ impl DataMap { } fn get_dest_idx(&self, src_value: u64) -> Option { + if !self.to_range.contains(&src_value) { + return None; + } + if let Some(idx) = src_value.checked_sub(self.to_range.start) { if let Some(new_idx) = self.from_range.start.checked_add(idx) { - if new_idx < self.from_range.end { + if self.from_range.contains(&new_idx) { return Some(new_idx); } } @@ -67,6 +71,7 @@ type MapMap = HashMap<(DataType, DataType), Vec>; #[derive(Debug, Default)] struct Almanac { seeds: Vec, + seed_ranges: Vec>, maps: MapMap, } @@ -85,6 +90,22 @@ impl Almanac { .map(|s| s.parse::().unwrap()) .collect(); + let even_seeds: Vec<_> = seeds + .iter() + .enumerate() + .filter_map(|(i, s)| if i % 2 == 0 { Some(*s) } else { None }) + .collect(); + let odd_seeds: Vec<_> = seeds + .iter() + .enumerate() + .filter_map(|(i, s)| if i % 2 == 1 { Some(*s) } else { None }) + .collect(); + let seed_ranges = even_seeds + .into_iter() + .enumerate() + .map(|(i, s)| s..(s + odd_seeds[i])) + .collect(); + let maps = data_chunks .into_iter() .map(|chunk| { @@ -113,40 +134,26 @@ impl Almanac { }) .collect(); - Almanac { seeds, maps } + Almanac { + seeds, + seed_ranges, + maps, + } } fn x_from_y(&self, x: DataType, y: DataType, search: u64) -> u64 { - let maps = self - .maps + self.maps .get(&(y, x)) - .expect(&format!("Missing mapping from {:?} to {:?}", x, y)); - let mut mapped_value: Vec> = maps + .expect(&format!("Missing mapping from {:?} to {:?}", x, y)) .into_iter() - .map(|dm| dm.get_dest_idx(search)) - .filter(|r| r.is_some()) - .collect(); - - if mapped_value.is_empty() { - return search; - } - - mapped_value.pop().unwrap().unwrap() - } - - fn soil_from_seed(&self, seed: u64) -> u64 { - use DataType::*; - - self.x_from_y(Soil, Seed, seed) - } - - fn fertilizer_from_soil(&self, soil: u64) -> u64 { - self.x_from_y(Fertilizer, Soil, soil) + .filter(|dm| dm.to_range.contains(&search)) + .find_map(|dm| dm.get_dest_idx(search)) + .unwrap_or_else(|| search) } fn location_from_seed(&self, seed: u64) -> u64 { - let soil = self.soil_from_seed(seed); - let fert = self.fertilizer_from_soil(soil); + let soil = self.x_from_y(Soil, Seed, seed); + let fert = self.x_from_y(Fertilizer, Soil, soil); let water = self.x_from_y(Water, Fertilizer, fert); let light = self.x_from_y(Light, Water, water); let temp = self.x_from_y(Temperature, Light, light); @@ -169,15 +176,38 @@ impl Almanac { .min() .unwrap() } + + fn lowest_seed_range_location(&self) -> u64 { + self.seed_ranges + .iter() + .map(|r| { + r.clone() + .into_iter() + .map(|s| self.location_from_seed(s)) + .min() + .unwrap() + }) + .min() + .unwrap() + } } -fn part_one() { - let almanac = Almanac::parse(FILE_STR); - println!("Part 1: Lowest seed location: {}", almanac.lowest_seed_location()); +fn part_one(a: &Almanac) { + println!("Part 1: Lowest seed location: {}", a.lowest_seed_location()); +} + +fn part_two(a: &Almanac) { + /* println!("{:#?}", a.seed_ranges); */ + println!( + "Part 2: Lowest seed range location: {}", + a.lowest_seed_range_location() + ); } fn main() { - part_one(); + let almanac = Almanac::parse(FILE_STR); + part_one(&almanac); + part_two(&almanac); } #[cfg(test)] @@ -208,4 +238,22 @@ mod tests { let almanac = Almanac::parse(EXAMPLE_FILE_STR); assert_eq!(35, almanac.lowest_seed_location()); } + + #[test] + fn test_seed_ranges() { + let almanac = Almanac::parse(EXAMPLE_FILE_STR); + let range1 = 79u64..93; + let range2 = 55u64..68; + + assert!(almanac.seed_ranges.contains(&range1)); + assert!(almanac.seed_ranges.contains(&range2)) + } + + #[test] + fn test_lowest_seed_range_location() { + let almanac = Almanac::parse(EXAMPLE_FILE_STR); + assert_ne!(45, almanac.lowest_seed_range_location()); + assert_eq!(46, almanac.lowest_seed_range_location()); + assert_ne!(47, almanac.lowest_seed_range_location()); + } }