Finally, two parts working

This commit is contained in:
Maxime Augier 2023-12-11 19:57:29 +01:00
parent c025aee62a
commit 99b5bda691

View File

@ -1,16 +1,17 @@
use std::{collections::BTreeMap, io::stdin, ops::{Mul, Range}}; use std::{io::stdin, ops::Range};
use anyhow::{Result, bail}; use anyhow::{Result, anyhow, bail, Context};
use itertools::Itertools;
mod parse; mod parse;
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
struct MapRange { struct MapRange {
base: usize, base: usize,
target: usize, target: usize,
} }
/// Stores a map as a series of contiguous offset ranges. /// Stores a map as a series of contiguous offset ranges.
#[derive(Debug)] #[derive(Debug, Clone)]
struct Map { struct Map {
from: String, from: String,
to: String, to: String,
@ -20,6 +21,12 @@ struct Map {
impl 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 { fn from_ranges(from: String, to: String, mut v: Vec<(usize,usize,usize)>) -> Self {
v.sort_by_key(|r| r.1); v.sort_by_key(|r| r.1);
@ -37,40 +44,117 @@ impl Map {
Self { from, to, ranges } Self { from, to, ranges }
} }
/// Gives the segment index relevant for a given source location in the map
fn range_idx(&self, target: usize) -> usize { fn range_idx(&self, target: usize) -> usize {
self.ranges.binary_search_by_key(&target, |r| r.base) 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 { fn lookup(&self, src: usize) -> usize {
let range = &self.ranges[self.range_idx(src)]; let range = &self.ranges[self.range_idx(src)];
(src + range.target) - range.base (src + range.target) - range.base
} }
fn segments(&self) -> impl Iterator<Item=(Range<usize>, Range<usize>)> + '_ {
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<Map> {
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<usize>, b: &Range<usize>) -> Range<usize> {
a.start.max(b.start) .. a.end.min(b.end)
}
fn vec_to_ranges(s: &[usize]) -> Result<Vec<Range<usize>>> {
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<()> { fn main() -> Result<()> {
let (seeds, maps) = parse::input(stdin().lock())?; 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 { eprintln!("built combined map from {} to {} ({} segments)", map.from, map.to, map.ranges.len());
if map.from != current { bail!("Wrong map order, expected {current}") }
current = &map.to; 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<usize> = 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() let best2 = best2.ok_or(anyhow!("No intersect inside known range"))?;
.map(|&s| {
let mut cur = s; println!("Part 2: lowest location {best2}");
eprint!("{cur}");
for m in &maps {
cur = m.lookup(cur);
eprint!(" -> {cur}")
}
eprintln!();
cur
}).min();
println!("lowest location: {best:?}");
Ok(()) Ok(())
} }