From 99b5bda691b5089050ee180fcbdce771a5e9aa6a Mon Sep 17 00:00:00 2001 From: Maxime Augier Date: Mon, 11 Dec 2023 19:57:29 +0100 Subject: [PATCH] Finally, two parts working --- day5/src/main.rs | 128 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/day5/src/main.rs b/day5/src/main.rs index 3322450..b725fb8 100644 --- a/day5/src/main.rs +++ b/day5/src/main.rs @@ -1,16 +1,17 @@ -use std::{collections::BTreeMap, io::stdin, ops::{Mul, Range}}; -use anyhow::{Result, bail}; +use std::{io::stdin, ops::Range}; +use anyhow::{Result, anyhow, bail, Context}; +use itertools::Itertools; mod parse; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] struct MapRange { base: usize, target: usize, } /// Stores a map as a series of contiguous offset ranges. -#[derive(Debug)] +#[derive(Debug, Clone)] struct Map { from: String, to: String, @@ -20,6 +21,12 @@ struct Map { impl Map { + fn identity(from: String) -> Self{ + let to = from.clone(); + Self { from, to, ranges: vec![MapRange { base: 0, target: 0 }] } + } + + /// Build a compact segment map from a list of (target, source, length) tuples fn from_ranges(from: String, to: String, mut v: Vec<(usize,usize,usize)>) -> Self { v.sort_by_key(|r| r.1); @@ -37,40 +44,117 @@ impl Map { Self { from, to, ranges } } + /// Gives the segment index relevant for a given source location in the map fn range_idx(&self, target: usize) -> usize { self.ranges.binary_search_by_key(&target, |r| r.base) - .unwrap_or_else(|i| i-1) + .unwrap_or_else(|i| i-1) //INVARIANT: `i`` can never be 0 because we always have a range starting at 0 } + /// Computes the image of a location over the map fn lookup(&self, src: usize) -> usize { let range = &self.ranges[self.range_idx(src)]; (src + range.target) - range.base } + + fn segments(&self) -> impl Iterator, Range)> + '_ { + self.ranges.iter() + .zip(self.ranges.iter().dropping(1)) + .map(|(a,b)| (a.base..b.base, a.target..(b.base-a.base))) + } + + /// Composes two maps + fn compose(&self, rhs: &Map) -> Result { + if self.to != rhs.from { bail!("incompatible maps {self:?} and {rhs:?}")}; + + let mut ranges = Vec::with_capacity(self.ranges.len() + rhs.ranges.len()); + + for i in 0..self.ranges.len() { + let mine = &self.ranges[i]; + // because we don't store ends of ranges, we have to check the next one + let stop = self.ranges.get(i+1) + .map(|next| next.base + mine.target - mine.base); // watch for order of + // operations because + // unsigned + + let start_index = rhs.range_idx(mine.target); + // stop index must be inclusive because our stop point may not match rhs boundary + let stop_index = stop.map(|s| rhs.range_idx(s-1)).unwrap_or(rhs.ranges.len()-1); + + // The first target range begins before our own start, treat it specially + let first_range = &rhs.ranges[start_index]; + ranges.push(MapRange { base: mine.base, target: first_range.target + mine.target - first_range.base }); + + for other in &rhs.ranges[start_index+1 ..= stop_index] { + ranges.push(MapRange { base: mine.base + other.base - mine.target, target: other.target }); + } + } + + Ok(Map { + from: self.from.to_owned(), + to: rhs.to.to_owned(), + ranges, + }) + } + +} + +fn intersect(a: &Range, b: &Range) -> Range { + a.start.max(b.start) .. a.end.min(b.end) +} + +fn vec_to_ranges(s: &[usize]) -> Result>> { + let mut i = s.iter(); + let mut rs = Vec::with_capacity(s.len() / 2); + loop { + let Some(&start) = i.next() else { break }; + let len = i.next().ok_or(anyhow!("odd number of values"))?; + rs.push(start .. start+len); + } + Ok(rs) } fn main() -> Result<()> { let (seeds, maps) = parse::input(stdin().lock())?; - eprintln!("{seeds:?}, {maps:?}"); + eprintln!("seeds: {seeds:?}"); - let mut current= "seed"; + let map = maps.iter() + .try_fold(Map::identity("seed".to_owned()), |lhs, rhs| lhs.compose(rhs)) + .context(anyhow!("Building combined map"))?; - for map in &maps { - if map.from != current { bail!("Wrong map order, expected {current}") } - current = &map.to; + eprintln!("built combined map from {} to {} ({} segments)", map.from, map.to, map.ranges.len()); + + let best = seeds.iter().copied() + .map(|v| map.lookup(v)) + .min(); + + println!("Part 1: lowest location: {best:?}"); + + + let part2_ranges = vec_to_ranges(&seeds)?; + let mut segment_index: Vec<_> = map.segments().collect(); + segment_index.sort_by_key(|s| s.1.start); + let mut best2: Option = None; + + for (src_range, dst_range) in &segment_index { + if let Some(b) = best2 { + if b <= dst_range.start { break } + } + for seeds in &part2_ranges { + let inter = intersect(seeds, src_range); + if !inter.is_empty() { + let candidate = inter.start + dst_range.start - src_range.start; + if let Some(b) = best2 { + if b < candidate { continue } + } + best2 = Some(candidate); + } + } } - let best = seeds.iter() - .map(|&s| { - let mut cur = s; - eprint!("{cur}"); - for m in &maps { - cur = m.lookup(cur); - eprint!(" -> {cur}") - } - eprintln!(); - cur - }).min(); - println!("lowest location: {best:?}"); + let best2 = best2.ok_or(anyhow!("No intersect inside known range"))?; + + println!("Part 2: lowest location {best2}"); + Ok(()) }