advent-of-code/2021/day22/rust/src/main.rs

168 lines
4.5 KiB
Rust
Raw Normal View History

2022-01-23 17:50:55 +01:00
#![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<i64>,
y: RangeInclusive<i64>,
z: RangeInclusive<i64>,
}
fn range_len(range: &RangeInclusive<i64>) -> u64 {
(range.end() - range.start() + 1).try_into().unwrap_or(0)
}
fn range_intersect(a: &RangeInclusive<i64>, b: &RangeInclusive<i64>) -> RangeInclusive<i64> {
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<Cuboid> {
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<i64>| -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<Item = Cuboid>) -> 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<T>(cuboids: impl IntoIterator<Item = T>, 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<CuboidSpec> = 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}");
}