diff --git a/data/examples/05.txt b/data/examples/05.txt new file mode 100644 index 000000000..f756727 --- /dev/null +++ b/data/examples/05.txt @@ -0,0 +1,33 @@ +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 diff --git a/src/bin/05.rs b/src/bin/05.rs new file mode 100644 index 000000000..3e7cd0b --- /dev/null +++ b/src/bin/05.rs @@ -0,0 +1,158 @@ +use std::{cmp::min, str::FromStr}; + +use aoc::parsers::to_vec; + +#[derive(Debug)] +struct Mapping { + destination: u64, + source: u64, + range: u64, +} + +struct ParseMappingError; + +impl FromStr for Mapping { + type Err = ParseMappingError; + fn from_str(s: &str) -> Result { + let nums: Vec = to_vec(s, ' '); + + Ok(Self { + destination: nums[0], + source: nums[1], + range: nums[2], + }) + } +} + +impl Mapping { + fn contains(&self, n: u64) -> bool { + n >= self.source && n < self.source + self.range + } + + fn contains_any(&self, s: u64, r: u64) -> bool { + s < self.source + self.range && s + r > self.source + } + + fn map(&self, n: u64) -> u64 { + debug_assert!(self.contains(n)); + let shift = n - self.source; + self.destination + shift + } + + fn map_range(&self, s: u64, r: u64) -> ((u64, u64), Vec<(u64, u64)>) { + debug_assert!(self.contains_any(s, r)); + let shift = { + if self.contains(s) { + s - self.source + } else { + 0 + } + }; + let neg_shift = { + if self.contains(s) { + 0 + } else { + self.source - s + } + }; + let dest_start = self.destination + shift; + let dest_range = min(r - neg_shift, self.range - shift); + let mut rest = Vec::new(); + + if !self.contains(s) { + rest.push((s, self.source - s)); + } + + if r - neg_shift > dest_range { + rest.push((self.source + self.range, r - dest_range)) + } + + ((dest_start, dest_range), rest) + } +} + +fn parse_map(maps: &str) -> Vec> { + let mut res = Vec::new(); + + for mapper in maps.split("\n\n") { + res.push( + mapper + .lines() + .skip(1) + .filter_map(|m| m.parse().ok()) + .collect(), + ) + } + + res +} + +pub fn part_one(input: &str) -> Option { + let (first, rest) = input.split_once("\n\n").unwrap(); + let maps = parse_map(rest); + let seeds = to_vec::(first.strip_prefix("seeds: ").unwrap(), ' '); + + let mut m = u64::MAX; + + for seed in seeds.iter() { + let mut s = *seed; + 'maps: for map in maps.iter() { + for inner in map.iter() { + if inner.contains(s) { + s = inner.map(s); + continue 'maps; + } + } + } + + m = min(m, s) + } + + Some(m) +} + +pub fn part_two(input: &str) -> Option { + let (first, rest) = input.split_once("\n\n").unwrap(); + let maps = parse_map(rest); + let seeds = to_vec::(first.strip_prefix("seeds: ").unwrap(), ' '); + let mut seeds_ranges: Vec<_> = Vec::new(); + for s in seeds.chunks(2) { + seeds_ranges.push((s[0], s[1])) + } + + for mapping in maps.iter() { + let mut new_ranges = Vec::new(); + + 'queue: while let Some((s, r)) = seeds_ranges.pop() { + for map in mapping { + if map.contains_any(s, r) { + let (rng, rest) = map.map_range(s, r); + new_ranges.push(rng); + seeds_ranges.extend_from_slice(&rest); + continue 'queue; + } + } + + new_ranges.push((s, r)); + } + + seeds_ranges = new_ranges; + } + + seeds_ranges.iter().map(|(x, _)| *x).min() +} + +aoc::solution!(5); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!(part_one(&aoc::template::read_file("examples", 5)), Some(35)); + } + #[test] + fn test_part_two() { + assert_eq!(part_two(&aoc::template::read_file("examples", 5)), Some(46)); + } +}