168 lines
4.5 KiB
Rust
168 lines
4.5 KiB
Rust
|
#![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}");
|
||
|
}
|