day17 finished
This commit is contained in:
parent
c798fa7b58
commit
098e36e2d3
5 changed files with 187 additions and 0 deletions
151
advent/days/day17/solution.py
Normal file
151
advent/days/day17/solution.py
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from itertools import cycle
|
||||
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 17
|
||||
|
||||
|
||||
def part1(lines: Iterator[str]) -> int:
|
||||
cave = Cave.create(7, next(lines))
|
||||
return cave.process_many_rocks(2022)
|
||||
|
||||
|
||||
def part2(lines: Iterator[str]) -> int:
|
||||
cave = Cave.create(7, next(lines))
|
||||
return cave.process_many_rocks(1_000_000_000_000)
|
||||
|
||||
|
||||
patterns = [["####"],
|
||||
[" # ", "###", " # "],
|
||||
["###", " #", " #"],
|
||||
["#", "#", "#", "#"],
|
||||
["##", "##"]
|
||||
]
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Position:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
def left(self) -> Position:
|
||||
return Position(self.x - 1, self.y)
|
||||
|
||||
def right(self) -> Position:
|
||||
return Position(self.x + 1, self.y)
|
||||
|
||||
def down(self) -> Position:
|
||||
return Position(self.x, self.y - 1)
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class Pattern:
|
||||
lines: list[str]
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return len(self.lines)
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return len(self.lines[0])
|
||||
|
||||
def stones(self, position: Position) -> Iterator[Position]:
|
||||
for y, line in enumerate(self.lines):
|
||||
for x, block in enumerate(line):
|
||||
if block == "#":
|
||||
yield Position(position.x + x, position.y + y)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Cave:
|
||||
width: int
|
||||
cave: list[str]
|
||||
gas_pushes: Iterator[str]
|
||||
rock_dispenser: Iterator[Pattern]
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return len(self.cave)
|
||||
|
||||
def check_free(self, rock: Pattern, position: Position) -> bool:
|
||||
for block in rock.stones(position):
|
||||
if block.y < len(self.cave) and self.cave[block.y][block.x] == '#':
|
||||
return False
|
||||
return True
|
||||
|
||||
def fix_rock(self, rock: Pattern, position: Position):
|
||||
for _ in range(len(self.cave), position.y + rock.height):
|
||||
self.cave.append(' ' * self.width)
|
||||
for block in rock.stones(position):
|
||||
old = self.cave[block.y]
|
||||
self.cave[block.y] = old[:block.x] + '#' + old[block.x + 1:]
|
||||
|
||||
@ classmethod
|
||||
def create(cls, width: int, gas_pushes: str) -> Self:
|
||||
cave = []
|
||||
return cls(width, cave, cycle(gas_pushes), cycle(Pattern(pattern) for pattern in patterns))
|
||||
|
||||
def process_one_rock(self) -> tuple[Pattern, Position]:
|
||||
rock = next(self.rock_dispenser)
|
||||
position = Position(2, len(self.cave) + 3)
|
||||
|
||||
while True:
|
||||
push = next(self.gas_pushes)
|
||||
if push == '<':
|
||||
if position.x > 0 and self.check_free(rock, position.left()):
|
||||
position = position.left()
|
||||
else:
|
||||
if (position.x + rock.width < self.width
|
||||
and self.check_free(rock, position.right())):
|
||||
position = position.right()
|
||||
|
||||
if position.y > 0 and self.check_free(rock, position.down()):
|
||||
position = position.down()
|
||||
else:
|
||||
self.fix_rock(rock, position)
|
||||
return rock, position
|
||||
|
||||
def process_many_rocks(self, max_rounds: int) -> int:
|
||||
last_landing_row = 0
|
||||
last_max_drop_time = 0
|
||||
last_max_drop_height = 0
|
||||
last_max_drop_row = 0
|
||||
last_max_drop_pattern: Pattern | None = None
|
||||
max_drop_height = 0
|
||||
time = 0
|
||||
added_height = 0
|
||||
while time < max_rounds:
|
||||
pattern, position = self.process_one_rock()
|
||||
|
||||
if position.y < last_landing_row:
|
||||
drop_height = last_landing_row - position.y
|
||||
if drop_height == max_drop_height and last_max_drop_pattern == pattern:
|
||||
drop_cycle_height = position.y - last_max_drop_row
|
||||
if last_max_drop_row - drop_cycle_height > 0:
|
||||
different = False
|
||||
for row in range(last_max_drop_row - drop_cycle_height, last_max_drop_row):
|
||||
if self.cave[row] != self.cave[row - drop_cycle_height]:
|
||||
different = True
|
||||
break
|
||||
if not different:
|
||||
time_diff = time - last_max_drop_time
|
||||
height_diff = self.height - last_max_drop_height
|
||||
cycle_count = (max_rounds - time) // time_diff
|
||||
time += cycle_count * time_diff
|
||||
added_height = cycle_count * height_diff
|
||||
last_max_drop_time = time
|
||||
last_max_drop_height = self.height
|
||||
last_max_drop_row = position.y
|
||||
elif drop_height > max_drop_height:
|
||||
last_max_drop_time = time
|
||||
last_max_drop_height = self.height
|
||||
last_max_drop_row = position.y
|
||||
last_max_drop_pattern = pattern
|
||||
max_drop_height = drop_height
|
||||
last_landing_row = position.y
|
||||
time += 1
|
||||
|
||||
return self.height + added_height
|
||||
Loading…
Add table
Add a link
Reference in a new issue