day17 finished

This commit is contained in:
Ruediger Ludwig 2022-12-18 09:13:43 +01:00
parent c798fa7b58
commit 098e36e2d3
5 changed files with 187 additions and 0 deletions

View file

View file

@ -0,0 +1 @@
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>

File diff suppressed because one or more lines are too long

View 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

View file

@ -0,0 +1,34 @@
from advent.common import input
from .solution import Cave, day_num, part1, part2
def test_part1():
lines = input.read_lines(day_num, 'example01.txt')
expected = 3068
result = part1(lines)
assert result == expected
def test_part2():
lines = input.read_lines(day_num, 'example01.txt')
expected = 1514285714288
result = part2(lines)
assert result == expected
def test_first():
input = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"
cave = Cave.create(7, input)
cave.process_one_rock()
expected = " #### "
assert cave.cave[0] == expected
def test_third():
input = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"
cave = Cave.create(7, input)
height = cave.process_many_rocks(3)
expected = "#### "
assert cave.cave[3] == expected
assert height == 6