From b1e402180ff352c6581fa1faf9f775404051da5e Mon Sep 17 00:00:00 2001 From: Xiretza Date: Tue, 5 Dec 2023 19:39:27 +0000 Subject: [PATCH] 2023 day5/rust: add solution --- 2023/day5/rust/Cargo.toml | 9 ++ 2023/day5/rust/src/main.rs | 163 +++++++++++++++++++++++++++++++++++++ Cargo.lock | 7 ++ Cargo.toml | 1 + 4 files changed, 180 insertions(+) create mode 100644 2023/day5/rust/Cargo.toml create mode 100644 2023/day5/rust/src/main.rs diff --git a/2023/day5/rust/Cargo.toml b/2023/day5/rust/Cargo.toml new file mode 100644 index 0000000..1fd9bab --- /dev/null +++ b/2023/day5/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust_2023_05" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aoc = { path = "../../../common/rust" } diff --git a/2023/day5/rust/src/main.rs b/2023/day5/rust/src/main.rs new file mode 100644 index 0000000..59d04d3 --- /dev/null +++ b/2023/day5/rust/src/main.rs @@ -0,0 +1,163 @@ +#![warn(clippy::pedantic)] + +use std::{io::stdin, ops::ControlFlow}; + +use aoc::{SectionRange, UpToTwo}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct MapRange { + source: SectionRange, + dest_start: usize, +} + +impl MapRange { + /// Returns overlapping (mapped) and non-overlapping (unmapped) ranges + pub fn map( + &self, + source_range: SectionRange, + ) -> (Option>, UpToTwo>) { + if let Some(overlap) = self.source & source_range { + let non_overlapping = source_range - overlap; + let overlap_offset = overlap + .offset_by_neg(*self.source.start()) + .offset_by(self.dest_start); + + (Some(overlap_offset), non_overlapping) + } else { + (None, UpToTwo::One(source_range)) + } + } + + pub fn parse(s: &str) -> Self { + let &[dest_start, source_start, len] = s + .split(' ') + .map(|n| n.parse().unwrap()) + .collect::>() + .as_slice() + else { + panic!("Invalid input: {s}"); + }; + + Self { + source: SectionRange::try_new(source_start, source_start + len - 1).unwrap(), + dest_start, + } + } +} + +struct IdMapping { + ranges: Vec, +} + +impl IdMapping { + pub fn new(ranges: Vec) -> Self { + for window in ranges.windows(2) { + let [first, second] = window else { panic!() }; + assert!( + (first.source & second.source).is_none(), + "source ranges overlap" + ); + } + Self { ranges } + } + + pub fn map(&self, source_range: SectionRange) -> Vec> { + let result = self.ranges.iter().try_fold( + (vec![], vec![source_range]), + |(mut mapped, unmapped), range| { + if unmapped.is_empty() { + return ControlFlow::Break(mapped); + } + + let mut remaining_unmapped = Vec::new(); + for non_mapped in unmapped { + let (overlapping, non_overlapping) = range.map(non_mapped); + mapped.extend(overlapping); + remaining_unmapped.extend(non_overlapping); + } + ControlFlow::Continue((mapped, remaining_unmapped)) + }, + ); + match result { + ControlFlow::Continue((mut mapped, unmapped)) => { + // unmapped ranges stay as-is + mapped.extend(unmapped); + mapped + } + ControlFlow::Break(mapped) => mapped, + } + } +} + +fn map_ranges_once( + ranges: &[SectionRange], + mapping: &IdMapping, +) -> Vec> { + ranges + .iter() + .flat_map(|&range| mapping.map(range)) + .collect() +} + +fn map_ranges_completely( + init_ranges: Vec>, + mappings: &[IdMapping], +) -> Vec> { + mappings + .iter() + .fold(init_ranges, |ranges, map| map_ranges_once(&ranges, map)) +} + +fn minimum_result(init_ranges: Vec>, mappings: &[IdMapping]) -> usize { + *map_ranges_completely(init_ranges, mappings) + .iter() + .min() + .unwrap() + .start() +} + +fn main() { + let mut lines = stdin().lines().map(Result::unwrap); + + let seeds: Vec = lines + .next() + .unwrap() + .split(' ') + .skip(1) + .map(|n| n.parse().unwrap()) + .collect(); + + let mut maps = Vec::new(); + while let Some(line) = lines.next() { + if line.is_empty() { + lines.next(); + continue; + } + + let ranges = lines + .by_ref() + .take_while(|l| !l.is_empty()) + .map(|l| MapRange::parse(&l)) + .collect(); + maps.push(IdMapping::new(ranges)); + } + + let single_seeds: Vec<_> = seeds + .iter() + .map(|&seed| SectionRange::try_new(seed, seed).unwrap()) + .collect(); + + println!("{}", minimum_result(single_seeds, &maps)); + + let seed_ranges: Vec<_> = seeds + .chunks(2) + .map(|pair| { + let &[start, len] = pair else { + panic!("Not a pair: {pair:?}"); + }; + SectionRange::try_new(start, start + len - 1).unwrap() + }) + .collect(); + + println!("{}", minimum_result(seed_ranges, &maps)); +} diff --git a/Cargo.lock b/Cargo.lock index 087d27c..6448e9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,6 +663,13 @@ dependencies = [ "enum-map", ] +[[package]] +name = "rust_2023_05" +version = "0.1.0" +dependencies = [ + "aoc", +] + [[package]] name = "rustversion" version = "1.0.11" diff --git a/Cargo.toml b/Cargo.toml index 042d422..f4cf0d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,4 +29,5 @@ members = [ "2022/day18/rust", "2023/day1/rust", "2023/day2/rust", + "2023/day5/rust", ]