from lib import get_data from lib import ints from dataclasses import dataclass from typing import Optional data = get_data(__file__) limit = 50 ps = set() for i, line in enumerate(data.splitlines()): x_min, x_max, y_min, y_max, z_min, z_max = ints(line) for x in range(max(x_min, -limit), min(x_max, limit) + 1): for y in range(max(y_min, -limit), min(y_max, limit) + 1): for z in range(max(z_min, -limit), min(z_max, limit) + 1): if line.startswith("on"): ps.add((x, y, z)) elif line.startswith("off"): if (x, y, z) in ps: ps.remove((x, y, z)) else: assert False print(len(ps)) @dataclass class Line: a: int b: int def intersects(self, other: "Line") -> bool: return self.b > other.a and self.a < other.b def intersection(self, other: "Line") -> Optional["Line"]: if not self.intersects(other): return None return Line(max(self.a, other.a), min(self.b, other.b)) def length(self) -> int: assert self.b > self.a return self.b - self.a + 1 @dataclass(frozen=True) class Cube: on: bool x: Line y: Line z: Line @classmethod def from_line(cls, line: str) -> "Cube": xs = ints(line) assert len(xs) == 6 lines = [Line(*xs[i:i+2]) for i in range(0, len(xs), 2)] on = True if line.startswith("on") else False return Cube(on, *lines) def volume(self) -> int: return (self.x.length() * self.y.length() * self.z.length()) * (1 if self.on else -1) def intersects(self, other: "Cube") -> bool: return ( self.x.intersects(other.x) and self.y.intersects(other.y) and self.z.intersects(other.z) ) def intersection(self, other: "Cube") -> Optional["Cube"]: x = self.x.intersection(other.x) y = self.y.intersection(other.y) z = self.z.intersection(other.z) if x is None or y is None or z is None: return None return Cube(not self.on, x, y, z) # Inclusion/exclusion principle from set theory cubes = [] for line in data.splitlines(): cc = Cube.from_line(line) ncubes = [] for i in range(len(cubes)): c = cubes[i] ic = c.intersection(cc) if ic is not None: cubes.append(ic) if cc.on: cubes.append(cc) print(sum(c.volume() for c in cubes))