#![warn(clippy::pedantic)] use std::{ cmp::{max, min}, io::{stdin, BufRead}, ops::RangeInclusive, }; use nom::{ branch::alt, bytes::complete::tag, character::complete::i64, combinator::map, error::ParseError, sequence::{pair, preceded, separated_pair, terminated, tuple}, }; #[derive(Clone, Debug, PartialEq, Eq)] struct Cuboid { x: RangeInclusive, y: RangeInclusive, z: RangeInclusive, } fn range_len(range: &RangeInclusive) -> u64 { (range.end() - range.start() + 1).try_into().unwrap_or(0) } fn range_intersect(a: &RangeInclusive, b: &RangeInclusive) -> RangeInclusive { max(*a.start(), *b.start())..=min(*a.end(), *b.end()) } impl Cuboid { pub fn cubes(&self) -> u64 { range_len(&self.x) * range_len(&self.y) * range_len(&self.z) } pub fn intersect(&self, other: &Cuboid) -> Option { let x = range_intersect(&self.x, &other.x); let y = range_intersect(&self.y, &other.y); let z = range_intersect(&self.z, &other.z); if x.is_empty() || y.is_empty() || z.is_empty() { None } else { Some(Self { x, y, z }) } } pub fn in_small_range(&self) -> bool { let check = |r: &RangeInclusive| -50 <= *r.start() && *r.end() <= 50; check(&self.x) && check(&self.y) && check(&self.z) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum CubeState { Off, On, } #[derive(Clone, Debug, PartialEq, Eq)] struct CuboidSpec { state: CubeState, cuboid: Cuboid, } impl CuboidSpec { fn parse<'input, E: ParseError<&'input str>>( input: &'input str, ) -> nom::IResult<&'input str, Self, E> { let dimension_range = |dim| { map( preceded( pair(tag(dim), tag("=")), separated_pair(i64, tag(".."), i64), ), |(start, end)| start..=end, ) }; map( separated_pair( alt(( map(tag("off"), |_| CubeState::Off), map(tag("on"), |_| CubeState::On), )), tag(" "), map( tuple(( terminated(dimension_range("x"), tag(",")), terminated(dimension_range("y"), tag(",")), dimension_range("z"), )), |(x, y, z)| Cuboid { x, y, z }, ), ), |(state, cuboid)| Self { state, cuboid }, )(input) } } fn count_intersecting(first: &Cuboid, rest: impl IntoIterator) -> u64 { let intersections = rest.into_iter().filter_map(|c| c.intersect(first)); count_union(intersections, Entry::Use) } enum Entry { Ignore(Cuboid), Use(Cuboid), } fn count_union(cuboids: impl IntoIterator, get_cuboid: impl Fn(T) -> Entry) -> u64 { cuboids .into_iter() .scan(Vec::new(), |previous, x| match get_cuboid(x) { Entry::Ignore(c) => { previous.push(c); Some(0) } Entry::Use(c) => { let count = c.cubes() - count_intersecting(&c, previous.iter().cloned()); previous.push(c); Some(count) } }) .sum() } fn main() { let mut small_range = None; let mut lit_cubes: u64 = 0; let mut placed_cuboids: Vec = Vec::new(); for line in stdin().lock().lines() { let spec = CuboidSpec::parse::<()>(&line.unwrap()).unwrap().1; if !spec.cuboid.in_small_range() { small_range.get_or_insert(lit_cubes); } // all overlaps with previous cuboids let intersections = placed_cuboids.iter().cloned().filter_map(|mut s| { s.cuboid = s.cuboid.intersect(&spec.cuboid)?; Some(s) }); // count the cubes that overlap with currently-ON cubes let overlap = count_union(intersections.rev(), |s| { (if s.state == CubeState::On { Entry::Use } else { Entry::Ignore })(s.cuboid) }); if let CubeState::On = spec.state { let size = spec.cuboid.cubes(); lit_cubes += size; }; lit_cubes -= overlap; placed_cuboids.push(spec); } println!("{}", small_range.unwrap()); println!("{lit_cubes}"); }