From e3c6a8f29c4b297c3556c78861d1fe4b5baa48c4 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Sun, 23 Jan 2022 17:50:55 +0100 Subject: [PATCH] 2021 day22/rust: add solution --- 2021/day22/day22_rs/Cargo.toml | 9 ++ 2021/day22/day22_rs/src/main.rs | 167 ++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 2021/day22/day22_rs/Cargo.toml create mode 100644 2021/day22/day22_rs/src/main.rs diff --git a/2021/day22/day22_rs/Cargo.toml b/2021/day22/day22_rs/Cargo.toml new file mode 100644 index 0000000..7d07c34 --- /dev/null +++ b/2021/day22/day22_rs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day22_rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = "7.1.0" diff --git a/2021/day22/day22_rs/src/main.rs b/2021/day22/day22_rs/src/main.rs new file mode 100644 index 0000000..1a264a7 --- /dev/null +++ b/2021/day22/day22_rs/src/main.rs @@ -0,0 +1,167 @@ +#![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}"); +}