2021: d22: ex1: add solution
This commit is contained in:
parent
3152b281e2
commit
9ab9b3bd08
173
2021/d22/ex1/ex1.py
Executable file
173
2021/d22/ex1/ex1.py
Executable file
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
from typing import Iterator, List, NamedTuple, Optional, Set, Tuple, cast
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
z: int
|
||||
|
||||
|
||||
class Cuboid(NamedTuple):
|
||||
min: Point
|
||||
max: Point
|
||||
|
||||
|
||||
class Step(NamedTuple):
|
||||
state: bool
|
||||
bounds: Cuboid
|
||||
|
||||
|
||||
Grid = Set[Cuboid]
|
||||
|
||||
MAX_BOUND = 10000000000000000000000000000000000000000000 # Just a very large integer
|
||||
MIN_BOUND = -MAX_BOUND
|
||||
|
||||
|
||||
def solve(input: List[str]) -> int:
|
||||
def parse() -> List[Step]:
|
||||
def parse_step(line: str) -> Step:
|
||||
state, cuboid = line.split(" ")
|
||||
|
||||
xs, ys, zs = cuboid.split(",")
|
||||
|
||||
min_x, max_x = map(int, xs[2:].split(".."))
|
||||
min_y, max_y = map(int, ys[2:].split(".."))
|
||||
min_z, max_z = map(int, zs[2:].split(".."))
|
||||
|
||||
# Sanity check
|
||||
assert min_x <= max_x
|
||||
assert min_y <= max_y
|
||||
assert min_z <= max_z
|
||||
|
||||
bounds = Cuboid(Point(min_x, min_y, min_z), Point(max_x, max_y, max_z))
|
||||
|
||||
return Step(state == "on", bounds)
|
||||
|
||||
return [parse_step(line) for line in input]
|
||||
|
||||
def overlapping_range(
|
||||
min_a: int, max_a: int, min_b: int, max_b: int
|
||||
) -> Optional[Tuple[int, int]]:
|
||||
if max_a < min_b or min_a > max_b:
|
||||
return None
|
||||
return max(min_a, min_b), min(max_a, max_b)
|
||||
|
||||
def overlapping_cube(cube: Cuboid, other: Cuboid) -> Optional[Cuboid]:
|
||||
xs = overlapping_range(cube.min.x, cube.max.x, other.min.x, other.max.x)
|
||||
ys = overlapping_range(cube.min.y, cube.max.y, other.min.y, other.max.y)
|
||||
zs = overlapping_range(cube.min.z, cube.max.z, other.min.z, other.max.z)
|
||||
|
||||
if xs is None or ys is None or zs is None:
|
||||
return None
|
||||
return Cuboid(Point(xs[0], ys[0], zs[0]), Point(xs[1], ys[1], zs[1]))
|
||||
|
||||
def overlaps(cube: Cuboid, other: Cuboid) -> bool:
|
||||
return overlapping_cube(cube, other) is not None
|
||||
|
||||
def carve_out(grid: Grid, hole: Cuboid) -> Grid:
|
||||
from itertools import filterfalse
|
||||
|
||||
def do_carve(c: Cuboid) -> Set[Cuboid]:
|
||||
cubes: Set[Cuboid] = set()
|
||||
|
||||
min, max = c
|
||||
|
||||
rightside = overlapping_range(hole.max.x + 1, MAX_BOUND, min.x, max.x)
|
||||
leftside = overlapping_range(MIN_BOUND, hole.min.x - 1, min.x, max.x)
|
||||
xs = overlapping_range(hole.min.x, hole.max.x, min.x, max.x)
|
||||
if rightside is not None:
|
||||
min_r, max_r = rightside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_r, min.y, min.z), Point(max_r, max.y, max.z))
|
||||
)
|
||||
if leftside is not None:
|
||||
min_l, max_l = leftside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_l, min.y, min.z), Point(max_l, max.y, max.z))
|
||||
)
|
||||
|
||||
backside = overlapping_range(hole.max.y + 1, MAX_BOUND, min.y, max.y)
|
||||
frontside = overlapping_range(MIN_BOUND, hole.min.y - 1, min.y, max.y)
|
||||
ys = overlapping_range(hole.min.y, hole.max.y, min.y, max.y)
|
||||
if backside is not None and xs is not None:
|
||||
min_x, max_x = xs
|
||||
min_b, max_b = backside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_x, min_b, min.z), Point(max_x, max_b, max.z))
|
||||
)
|
||||
if frontside is not None and xs is not None:
|
||||
min_x, max_x = xs
|
||||
min_f, max_f = frontside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_x, min_f, min.z), Point(max_x, max_f, max.z))
|
||||
)
|
||||
|
||||
topside = overlapping_range(hole.max.z + 1, MAX_BOUND, min.z, max.z)
|
||||
bottomside = overlapping_range(MIN_BOUND, hole.min.z - 1, min.z, max.z)
|
||||
if topside is not None and xs is not None and ys is not None:
|
||||
min_x, max_x = xs
|
||||
min_y, max_y = ys
|
||||
min_t, max_t = topside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_x, min_y, min_t), Point(max_x, max_y, max_t))
|
||||
)
|
||||
if bottomside is not None and xs is not None and ys is not None:
|
||||
min_x, max_x = xs
|
||||
min_y, max_y = ys
|
||||
min_b, max_b = bottomside
|
||||
cubes.add(
|
||||
Cuboid(Point(min_x, min_y, min_b), Point(max_x, max_y, max_b))
|
||||
)
|
||||
|
||||
return cubes
|
||||
|
||||
overlaps_us = lambda c: overlaps(c, hole)
|
||||
|
||||
of_interest, other = filter(overlaps_us, grid), filterfalse(overlaps_us, grid)
|
||||
|
||||
return set(other) | set(
|
||||
itertools.chain.from_iterable(do_carve(c) for c in of_interest)
|
||||
)
|
||||
|
||||
def apply(grid: Grid, step: Step) -> Grid:
|
||||
cuboid = step.bounds
|
||||
|
||||
# Remove that cube from the grid, potentially splitting cubes that overlap
|
||||
grid = carve_out(grid, cuboid)
|
||||
|
||||
# Add it back in if we want to turn on those cubes
|
||||
if step.state:
|
||||
grid.add(cuboid)
|
||||
|
||||
return grid
|
||||
|
||||
def count_cubes(c: Cuboid) -> int:
|
||||
min, max = c
|
||||
return (max.x + 1 - min.x) * (max.y + 1 - min.y) * (max.z + 1 - min.z)
|
||||
|
||||
def score(grid: Grid) -> int:
|
||||
area_of_interest = Cuboid(Point(-50, -50, -50), Point(50, 50, 50))
|
||||
of_interest = {
|
||||
cube
|
||||
for cube in map(lambda c: overlapping_cube(c, area_of_interest), grid)
|
||||
if cube is not None
|
||||
}
|
||||
return sum(map(count_cubes, of_interest))
|
||||
|
||||
steps = parse()
|
||||
grid: Grid = functools.reduce(apply, steps, set())
|
||||
return score(grid)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
input = [line.strip() for line in sys.stdin.readlines()]
|
||||
print(solve(input))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue