day 14 finished
This commit is contained in:
parent
3f4b7f93a3
commit
334debccc6
6 changed files with 386 additions and 0 deletions
126
advent/days/day14/solution.py
Normal file
126
advent/days/day14/solution.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from itertools import count
|
||||
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 14
|
||||
|
||||
|
||||
def part1(lines: Iterator[str]) -> int:
|
||||
cave = BottomLessCave.create(lines)
|
||||
return cave.drip_till_forever()
|
||||
|
||||
|
||||
def part2(lines: Iterator[str]) -> int:
|
||||
cave = FlooredCave.create(lines, floor=2)
|
||||
return cave.drip_till_full()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CaveMap:
|
||||
filled: set[tuple[int, int]]
|
||||
max_depths: int
|
||||
|
||||
def is_filled(self, pos: tuple[int, int]) -> bool:
|
||||
return pos in self.filled
|
||||
|
||||
def set_filled(self, pos: tuple[int, int]):
|
||||
self.filled.add(pos)
|
||||
|
||||
@classmethod
|
||||
def get_map(cls, lines: Iterator[str], add_floor: int) -> Self:
|
||||
rocks: set[tuple[int, int]] = set()
|
||||
for line in lines:
|
||||
path = cls.parse_rock_path(line)
|
||||
current = path[0]
|
||||
rocks.add(current)
|
||||
for next in path[1:]:
|
||||
rocks.update(cls.get_path(current, next))
|
||||
current = next
|
||||
return CaveMap(rocks, max(y for _, y in rocks) + add_floor)
|
||||
|
||||
@classmethod
|
||||
def get_path(cls, from_pos: tuple[int, int],
|
||||
to_pos: tuple[int, int]) -> Iterator[tuple[int, int]]:
|
||||
from_x, from_y = from_pos
|
||||
to_x, to_y = to_pos
|
||||
if from_x == to_x:
|
||||
if from_y < to_y:
|
||||
return ((from_x, y) for y in range(from_y + 1, to_y + 1))
|
||||
else:
|
||||
return ((from_x, y) for y in range(from_y - 1, to_y - 1, -1))
|
||||
elif from_x < to_x:
|
||||
return ((x, from_y) for x in range(from_x + 1, to_x + 1))
|
||||
else:
|
||||
return ((x, from_y) for x in range(from_x - 1, to_x - 1, -1))
|
||||
|
||||
@classmethod
|
||||
def parse_rock_path(cls, line: str) -> list[tuple[int, int]]:
|
||||
return [(int(x.strip()), int(y.strip()))
|
||||
for x, y in (part.split(',') for part in line.split('->'))]
|
||||
|
||||
def next_free(self, pos: tuple[int, int]) -> tuple[int, int] | None:
|
||||
pos_x, pos_y = pos
|
||||
if not self.is_filled((pos_x, pos_y + 1)):
|
||||
return pos_x, pos_y + 1
|
||||
if not self.is_filled((pos_x - 1, pos_y + 1)):
|
||||
return pos_x - 1, pos_y + 1
|
||||
if not self.is_filled((pos_x + 1, pos_y + 1)):
|
||||
return pos_x + 1, pos_y + 1
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class BottomLessCave:
|
||||
cave_map: CaveMap
|
||||
|
||||
@classmethod
|
||||
def create(cls, lines: Iterator[str]) -> Self:
|
||||
cave_map = CaveMap.get_map(lines, 0)
|
||||
return BottomLessCave(cave_map)
|
||||
|
||||
def drip(self) -> bool:
|
||||
origin = 500, 0
|
||||
if self.cave_map.is_filled(origin):
|
||||
return False
|
||||
|
||||
while True:
|
||||
next_pos = self.cave_map.next_free(origin)
|
||||
if next_pos is None:
|
||||
self.cave_map.set_filled(origin)
|
||||
return True
|
||||
if next_pos[1] == self.cave_map.max_depths:
|
||||
return False
|
||||
origin = next_pos
|
||||
|
||||
def drip_till_forever(self) -> int:
|
||||
for round in count():
|
||||
if not self.drip():
|
||||
return round
|
||||
assert False, "Unreachable"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlooredCave:
|
||||
cave_map: CaveMap
|
||||
|
||||
@classmethod
|
||||
def create(cls, lines: Iterator[str], floor: int) -> Self:
|
||||
return FlooredCave(CaveMap.get_map(lines, floor))
|
||||
|
||||
def drip_till_full(self) -> int:
|
||||
count = 1
|
||||
line: set[tuple[int, int]] = {(500, 0)}
|
||||
for _ in range(self.cave_map.max_depths - 1):
|
||||
next_line: set[tuple[int, int]] = set()
|
||||
for x, y in line:
|
||||
if not self.cave_map.is_filled((x - 1, y + 1)):
|
||||
next_line.add((x - 1, y + 1))
|
||||
if not self.cave_map.is_filled((x, y + 1)):
|
||||
next_line.add((x, y + 1))
|
||||
if not self.cave_map.is_filled((x + 1, y + 1)):
|
||||
next_line.add((x + 1, y + 1))
|
||||
count += len(next_line)
|
||||
line = next_line
|
||||
return count
|
||||
Loading…
Add table
Add a link
Reference in a new issue