use std::cmp::{max, min}; use std::fmt::Debug; use std::ops::RangeInclusive; use aoc_runner_derive::{aoc, aoc_generator}; struct Database { fresh_ingredients: Vec>, available_ingredients: Vec, } struct Database2 { fresh_ingredients: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] struct Span { start: u64, end: u64, } impl From<(u64, u64)> for Span { fn from(value: (u64, u64)) -> Self { Self { start: value.0, end: value.1, } } } impl Span { fn len(&self) -> u64 { self.end - self.start + 1 } } struct RangeSet { ranges: Vec, } impl RangeSet { fn new() -> Self { Self { ranges: Vec::new() } } fn simplify(&mut self) { // sort the ranges by start if self.ranges.len() < 2 { return; } let mut modified = true; while modified { let mut new_ranges = Vec::new(); modified = false; self.ranges.sort_by_key(|s| s.start); for chunk in self.ranges.chunks(2) { if chunk.len() == 1 { new_ranges.push(chunk[0].clone()); continue; } let (l, r) = (&chunk[0], &chunk[1]); if r.start <= l.end + 1 { modified = true; new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into()) } else { new_ranges.push(l.clone()); new_ranges.push(r.clone()); } } if !self.ranges.len().is_multiple_of(2) && new_ranges.len() >= 2 { let (l, r) = (new_ranges.pop().unwrap(), new_ranges.pop().unwrap()); if r.start <= l.end + 1 && r.end >= l.start - 1 { modified = true; new_ranges.push((min(r.start, l.start), max(l.end, r.end)).into()) } else { new_ranges.push(l.clone()); new_ranges.push(r.clone()); } } self.ranges = new_ranges; } } fn add(&mut self, s: Span) { self.ranges.push(s); self.simplify() } } #[aoc_generator(day5, part1)] fn parse(input: &str) -> Database { let mut fresh_ingredients = Vec::new(); let mut available_ingredients = Vec::new(); let mut parsing_ranges = true; for line in input.lines() { if line.is_empty() { parsing_ranges = false; continue; } if parsing_ranges { let (start, end) = line.split_once('-').unwrap(); fresh_ingredients.push(RangeInclusive::new( start.parse().unwrap(), end.parse().unwrap(), )); } else { available_ingredients.push(line.parse().unwrap()) } } Database { fresh_ingredients, available_ingredients, } } #[aoc_generator(day5, part2)] fn parse2(input: &str) -> Database2 { let mut fresh_ingredients = Vec::new(); for line in input.lines() { if line.is_empty() { return Database2 { fresh_ingredients }; } let (start, end) = line.split_once('-').unwrap(); fresh_ingredients.push((start.parse().unwrap(), end.parse().unwrap()).into()); } Database2 { fresh_ingredients } } #[aoc(day5, part1)] fn part1(input: &Database) -> u64 { input .available_ingredients .iter() .filter(|i| input.fresh_ingredients.iter().any(|r| r.contains(i))) .count() as u64 } #[aoc(day5, part2)] fn part2(input: &Database2) -> u64 { let mut all_ingredients = RangeSet::new(); for r in &input.fresh_ingredients { all_ingredients.add(r.clone()) } all_ingredients.ranges.iter().map(|r| r.len()).sum::() } #[cfg(test)] mod tests { use super::*; const EXAMPLE: &str = "3-5 10-14 16-20 12-18 1 5 8 11 17 32"; #[test] fn part1_example() { assert_eq!(part1(&parse(EXAMPLE)), 3); } #[test] fn part2_example() { assert_eq!(part2(&parse2(EXAMPLE)), 14); } }