2023 Day 22 Part 1
This commit is contained in:
parent
83e5d44f61
commit
f728880a8e
1 changed files with 170 additions and 0 deletions
170
2023/day22.py
Normal file
170
2023/day22.py
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from itertools import product
|
||||||
|
from typing import Self, Iterator
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Point:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
z: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, text: str) -> Self:
|
||||||
|
x, y, z = [int(n) for n in text.split(',')]
|
||||||
|
return cls(x, y, z)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"({self.x}, {self.y}, {self.z})"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Point({self.x}, {self.y}, {self.z})"
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.x, self.y, self.z))
|
||||||
|
|
||||||
|
def __lt__(self: Self, other: Self) -> bool:
|
||||||
|
return (self.z, self.x, self.y) < (other.z, other.x, other.y)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Brick:
|
||||||
|
id: int
|
||||||
|
a: Point
|
||||||
|
b: Point
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, id: int, text: str) -> Self:
|
||||||
|
a, b = text.split('~')
|
||||||
|
a = Point.parse(a)
|
||||||
|
b = Point.parse(b)
|
||||||
|
assert a.x <= b.x and a.y <= b.y and a.z <= b.z
|
||||||
|
return cls(id, a, b)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.id} = {self.a} ~ {self.b}"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Brick({self.id}, {self.a}, {self.b})"
|
||||||
|
|
||||||
|
def points(self) -> Iterator[Point]:
|
||||||
|
for x in range(self.a.x, self.b.x+1):
|
||||||
|
for y in range(self.a.y, self.b.y+1):
|
||||||
|
for z in range(self.a.z, self.b.z+1):
|
||||||
|
yield Point(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Snapshot:
|
||||||
|
bricks: list[Brick]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, text: str) -> Self:
|
||||||
|
bricks = [
|
||||||
|
Brick.parse(i + 1, line)
|
||||||
|
for i, line in enumerate(text.splitlines())
|
||||||
|
]
|
||||||
|
return cls(bricks)
|
||||||
|
|
||||||
|
|
||||||
|
def part1(snap: Snapshot):
|
||||||
|
supports: dict[int, set[int]] = defaultdict(lambda: set())
|
||||||
|
tower: dict[Point, int] = {}
|
||||||
|
bricks: dict[int, Brick] = {}
|
||||||
|
|
||||||
|
lo = Point(0, 0, 0)
|
||||||
|
hi = Point(0, 0, 0)
|
||||||
|
|
||||||
|
for brick in snap.bricks:
|
||||||
|
bricks[brick.id] = brick
|
||||||
|
for p in brick.points():
|
||||||
|
tower[p] = brick.id
|
||||||
|
|
||||||
|
lo = Point(
|
||||||
|
min(lo.x, p.x),
|
||||||
|
min(lo.y, p.y),
|
||||||
|
min(lo.z, p.z),
|
||||||
|
)
|
||||||
|
hi = Point(
|
||||||
|
max(hi.x, p.x),
|
||||||
|
max(hi.y, p.y),
|
||||||
|
max(hi.z, p.z),
|
||||||
|
)
|
||||||
|
|
||||||
|
ids = list(bricks.keys())
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
while changed:
|
||||||
|
changed = False
|
||||||
|
for id in ids:
|
||||||
|
brick = bricks[id]
|
||||||
|
|
||||||
|
new_z = None
|
||||||
|
for z in range(brick.a.z-1, 0, -1):
|
||||||
|
supported = False
|
||||||
|
surface = product(
|
||||||
|
range(brick.a.x, brick.b.x + 1),
|
||||||
|
range(brick.a.y, brick.b.y + 1),
|
||||||
|
)
|
||||||
|
for (x, y) in surface:
|
||||||
|
if tower.get(Point(x, y, z)):
|
||||||
|
supported = True
|
||||||
|
break
|
||||||
|
if supported:
|
||||||
|
new_z = z + 1
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
new_z = 1
|
||||||
|
|
||||||
|
dz = brick.a.z - new_z
|
||||||
|
assert dz >= 0
|
||||||
|
|
||||||
|
if dz == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for p in brick.points():
|
||||||
|
del tower[p]
|
||||||
|
|
||||||
|
brick.a.z -= dz
|
||||||
|
brick.b.z -= dz
|
||||||
|
|
||||||
|
for p in brick.points():
|
||||||
|
tower[p] = brick.id
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
for brick in snap.bricks:
|
||||||
|
for z in range(brick.a.z-1, 0, -1):
|
||||||
|
surface = product(
|
||||||
|
range(brick.a.x, brick.b.x + 1),
|
||||||
|
range(brick.a.y, brick.b.y + 1),
|
||||||
|
)
|
||||||
|
for (x, y) in surface:
|
||||||
|
lower = tower.get(Point(x, y, z))
|
||||||
|
if not lower:
|
||||||
|
continue
|
||||||
|
supports[brick.id].add(lower)
|
||||||
|
if supports[brick.id]:
|
||||||
|
break
|
||||||
|
|
||||||
|
solos: set[int] = set()
|
||||||
|
for v in supports.values():
|
||||||
|
if len(v) == 1:
|
||||||
|
solos.add(next(iter(v)))
|
||||||
|
|
||||||
|
return len(snap.bricks) - len(solos)
|
||||||
|
|
||||||
|
|
||||||
|
def relpath(path: str) -> str:
|
||||||
|
return os.path.join(os.path.dirname(__file__), path)
|
||||||
|
|
||||||
|
|
||||||
|
input_path = "./input/day22.txt"
|
||||||
|
with open(relpath(input_path)) as f:
|
||||||
|
snap = Snapshot.parse(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
print(part1(snap))
|
||||||
Loading…
Reference in a new issue