diff --git a/src/bin/day5.rs b/src/bin/day5.rs new file mode 100644 index 0000000..7cc34b8 --- /dev/null +++ b/src/bin/day5.rs @@ -0,0 +1,202 @@ +use std::time::Instant; + +use aoc_2025::{load, print_res}; +use bstr::BString; + +type Parsed = InventoryState; + +struct IdRange { + start: u64, + end: u64, +} + +impl std::fmt::Debug for IdRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}..={}", self.start, self.end) + } +} + +#[derive(Debug)] +pub struct InventoryState { + fresh: Vec, + avail: Vec, +} + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + let Some((fresh, avail)) = str::from_utf8(input)?.split_once("\n\n") else { + color_eyre::eyre::bail!("No blank line found"); + }; + + Ok(InventoryState { + fresh: fresh + .lines() + .map(|l| { + let Some((start, end)) = l.split_once('-') else { + color_eyre::eyre::bail!("Invalid ID range: {l}"); + }; + Ok(IdRange { + start: start.parse()?, + end: end.parse()?, + }) + }) + .collect::>()?, + avail: avail + .lines() + .map(|id| id.parse()) + .collect::>()?, + }) +} + +#[derive(Debug)] +struct IntervalSet(Vec); + +impl IntervalSet { + pub fn new() -> Self { + Self(Vec::new()) + } + + fn insert_at(&mut self, i: usize, range: IdRange) { + if i == 0 { + if self.0[0].start <= range.end { + todo!() + } else { + self.0.insert(0, range); + } + } else if i == self.0.len() { + if self.0[i - 1].end >= range.start { + todo!() + } else { + self.0.push(range); + } + } else { + // We are in the middle of the array + if self.0[i - 1].end >= range.start { + // The range overlaps with the previous one + + if range.end <= self.0[i - 1].end { + // The new range is completly contained in the previous one + } else { + // The range overlaps with at least the previous one + if range.end >= self.0[i].start { + // Part of the range overlaps the next range + let next = self.0.remove(i); + + if next.end < range.end { + // The range might overlap with two ranges + todo!() + } else { + // Merge the three ranges together + self.0[i - 1].end = next.end; + } + } else { + // Concatenate this range with the next one + self.0[i - 1].end = range.end; + } + } + } else if self.0[i].start <= range.end { + // The range overlaps with the next one + + self.0[i].start = self.0[i].start.min(range.start); + + if range.end <= self.0[i].end { + // The range does not go further than the end of the next + } else { + // We have only inserted up to the end of the next range, insert the + // rest + self.insert(IdRange { + start: self.0[i].end + 1, + end: range.end, + }) + } + } else { + // We overlap with neither ranges + self.0.insert(i, range) + } + } + } + + pub fn insert(&mut self, range: IdRange) { + if self.0.is_empty() { + self.0.push(range); + return; + } + + match self.0.binary_search_by_key(&range.start, |r| r.start) { + // Two starts overlap + Ok(i) => { + if range.end <= self.0[i].end { + // Nothing to do, the range is contained inside the existing one + } else { + // Replace the existing range with this new superset + self.0.remove(i); + self.insert(range); + } + } + Err(i) => self.insert_at(i, range), + } + } + + pub fn contains(&mut self, value: u64) -> bool { + self.0 + .binary_search_by(|r| { + if value < r.start { + std::cmp::Ordering::Greater + } else if value > r.end { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) + .is_ok() + } + + pub fn length(&self) -> u64 { + self.0.iter().map(|r| r.end - r.start + 1).sum() + } +} + +#[inline(never)] +pub fn part1(input: Parsed) { + let mut ranges = IntervalSet::new(); + + for fresh in input.fresh.into_iter() { + ranges.insert(fresh); + } + + let fresh = input.avail.iter().filter(|&&v| ranges.contains(v)).count(); + + print_res!("Fresh ingredient count: {fresh}"); +} + +#[inline(never)] +pub fn part2(input: Parsed) { + let mut ranges = IntervalSet::new(); + + for fresh in input.fresh.into_iter() { + ranges.insert(fresh); + } + + print_res!("Total fresh ingredient count: {}", ranges.length()); +} + +pub fn main() -> color_eyre::Result<()> { + let context = load()?; + + let start = Instant::now(); + let parsed = parsing(&context.input)?; + let elapsed = humantime::format_duration(start.elapsed()); + + let start = Instant::now(); + if context.part == 1 { + part1(parsed); + } else { + part2(parsed); + } + let elapsed_part = humantime::format_duration(start.elapsed()); + + println!(" Parsing: {elapsed}"); + println!(" Solving: {elapsed_part}"); + + Ok(()) +}