use std::{ops::Range, str::FromStr}; use crate::{Problem, Solution}; pub struct Day05; impl Problem for Day05 { const DAY: u8 = 5; const INPUT: &'static str = include_str!("../input/day_05.txt"); } impl Solution for Day05 { type Answer1 = usize; type Answer2 = usize; fn part_1(input: &str) -> anyhow::Result { let (first, rest) = input .trim() .split_once('\n') .ok_or(anyhow::format_err!("Missing value map label"))?; let mut values = first .strip_prefix("seeds: ") .ok_or(anyhow::format_err!("Failed to get seeds"))? .split_whitespace() .map(FromStr::from_str) .try_collect::>()?; let value_maps = rest .trim() .split("\n\n") .map(parse_value_maps) .try_collect::>()?; for value_map in value_maps { values.iter_mut().for_each(|value| { if let Some(v) = value_map.iter().find_map(|v| v.map_value(*value)) { *value = v } }) } values .into_iter() .min() .ok_or(anyhow::format_err!("No values found")) } fn part_2(input: &str) -> anyhow::Result { let (first, rest) = input .trim() .split_once('\n') .ok_or(anyhow::format_err!("Missing value map label"))?; let seeds: Vec> = first .strip_prefix("seeds: ") .ok_or(anyhow::format_err!("Failed to get seeds"))? .split_whitespace() .map(FromStr::from_str) .try_collect::>()? .into_iter() .array_chunks() .map(|[n, len]| Range { start: n, end: n + len, }) .collect(); let max = seeds .iter() .map(|r| r.end) .max() .ok_or(anyhow::format_err!("Failed to get max seed"))?; let value_maps = rest .trim() .split("\n\n") .map(parse_value_maps) .try_collect::>()?; (0..=max) .find(|location| { let mut value = *location; for value_map in value_maps.iter().rev() { if let Some(v) = value_map.iter().find_map(|v| v.r_map_value(value)) { value = v; } } seeds.iter().any(|s| s.contains(&value)) }) .ok_or(anyhow::format_err!("Failed to find min seed")) } } fn parse_value_maps(s: &str) -> anyhow::Result> { s.trim() .lines() .skip(1) .map(FromStr::from_str) .try_collect::>() } #[derive(Debug, PartialEq, Eq, Hash)] struct ValueMap { source: Range, destination: Range, offset: isize, } impl ValueMap { fn map_value(&self, value: usize) -> Option { self.source .contains(&value) .then_some((value as isize + self.offset) as usize) } fn r_map_value(&self, value: usize) -> Option { self.destination .contains(&value) .then_some((value as isize - self.offset) as usize) } } impl FromStr for ValueMap { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut iter = s.trim().splitn(3, ' ').map(FromStr::from_str); let (Some(Ok(destination_start)), Some(Ok(source_start)), Some(Ok(length))) = (iter.next(), iter.next(), iter.next()) else { anyhow::bail!("Invalid value map range"); }; Ok(Self { source: source_start..source_start + length, destination: destination_start..destination_start + length, offset: isize::try_from(destination_start)? - isize::try_from(source_start)?, }) } } #[cfg(test)] mod tests { use super::*; const INPUT: &str = indoc::indoc! {" seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48 soil-to-fertilizer map: 0 15 37 37 52 2 39 0 15 fertilizer-to-water map: 49 53 8 0 11 42 42 0 7 57 7 4 water-to-light map: 88 18 7 18 25 70 light-to-temperature map: 45 77 23 81 45 19 68 64 13 temperature-to-humidity map: 0 69 1 1 0 69 humidity-to-location map: 60 56 37 56 93 4 "}; #[test] fn test_part_1() -> anyhow::Result<()> { Ok(assert_eq!(35, Day05::part_1(INPUT)?)) } #[test] fn test_part_2() -> anyhow::Result<()> { Ok(assert_eq!(46, Day05::part_2(INPUT)?)) } }