2022-12-17 - Pyroclastic Flow
(original .ipynb)
from copy import deepcopy
from pprint import pprint
puzzle_input_str = open("./puzzle_input/day17.txt").read()
test_input_str = ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>"
def build_shape(shape):
for r, row in enumerate(shape):
for c, col in enumerate(row):
if col == "@":
yield (r, c)
all_shapes = {
name: (len(shape), list(build_shape(shape)))
for name,shape in [
("horizontal", [ # horizontal line
list("..@@@@.")
]),
("cross", [ # cross
list("...@..."),
list("..@@@.."),
list("...@...")
]),
("angle", [ # angle
list("....@.."),
list("....@.."),
list("..@@@..")
]),
("vertical", [ # vertical line
list("..@...."),
list("..@...."),
list("..@...."),
list("..@....")
]),
("square", [ # square
list("..@@..."),
list("..@@...")
]),
]
}
def init_grid():
return [
["."] * 7 for _ in range(3)
]
def shapes():
keys = ["horizontal", "cross", "angle", "vertical", "square"]
while True:
for key in keys:
yield key
def tower_height(grid):
rows = 0
for row in grid:
if "#" in row:
break
rows += 1
return len(grid) - rows
def pad_grid(grid, shape_height):
rows = 3 + shape_height
for row in grid:
if "#" in row:
break
rows -= 1
if rows < 0:
while rows < 0:
grid.pop(0)
rows += 1
#print(f"padding with {rows} rows")
else:
while rows > 0:
grid.insert(0, ["."] * 7)
rows -= 1
def at_rest(row, shape, grid):
#print("checking at rest")
for r,c in shape:
assert row+r+1 <= len(grid)
if row+r+1 == len(grid):
#print("we reached the bottom at row", row+r+1)
return True
if grid[row+r+1][c] == "#":
return True
return False
def parse_input(input_str):
while True:
for i, c in enumerate(input_str):
yield i, c
def apply_jet(jet, row, shape, grid):
new_shape = []
if jet == "<":
for r, c in shape:
if c == 0 or (grid[row+r][c-1] == "#"):
#print("Jet of gas pushes rock left, but nothing happens")
return shape
new_shape.append((r, c-1))
#print("Jet of gas pushes rock left")
elif jet == ">":
for r, c in shape:
if c == 6 or (grid[row+r][c+1] == "#"):
#print("Jet of gas pushes rock right, but nothing happens")
return shape
new_shape.append((r, c+1))
#print("Jet of gas pushes rock right")
return new_shape
def add_to_grid(row, shape, grid):
for r,c in shape:
assert grid[row+r][c] != "#"
grid[row+r][c] = "#"
def dump_grid(grid):
for row in grid:
print("".join(row))
def prune(grid):
# find the row below which all are covered
tester = [False] * len(grid[0])
for r, row in enumerate(grid):
for c, col in enumerate(row):
if col == "#":
tester[c] = True
if all(tester):
return grid[:r+1], len(grid)-(r+1)
return grid, 0
def get_key(grid, shape_name, jet):
heights = [3] * 7
for r, row in enumerate(grid):
for c, col in enumerate(row):
if col == "#" and heights[c] == 3:
heights[c] = r
if all(h != 3 for h in heights):
break
adjustment = min(heights) - 3
return "-".join(
[str(h-adjustment) for h in heights]
+ [shape_name]
+ [jet]
)
def part_one(input_str, iterations=2022):
jets = parse_input(input_str)
grid = init_grid()
shape_getter = shapes()
total_pruned = 0
iteration = 0
seen_before = {}
height_at = {}
while iteration < iterations:
height_at[iteration] = tower_height(grid) + total_pruned
iteration += 1
shape_name = next(shape_getter)
rows, shape = all_shapes[shape_name]
pad_grid(grid, rows)
j, jet = next(jets)
row = 0
shape = apply_jet(jet, row, shape, grid)
while not at_rest(row, shape, grid):
row += 1
j, jet = next(jets)
shape = apply_jet(jet, row, shape, grid)
key = get_key(grid, shape_name, str(j))
if key in seen_before:
prev_iteration, prev_height = seen_before[key]
cycle_length = iteration - prev_iteration
growth_per_cycle = tower_height(grid) + total_pruned - prev_height
length_of_repeated_section = (iterations - prev_iteration)
number_of_cycles = length_of_repeated_section // cycle_length
iterations_after_repeated_section = length_of_repeated_section % cycle_length
return sum([
height_at[prev_iteration],
number_of_cycles * growth_per_cycle,
height_at[prev_iteration + iterations_after_repeated_section] - height_at[prev_iteration]
])
else:
seen_before[key] = (iteration, tower_height(grid) + total_pruned)
add_to_grid(row, shape, grid)
grid, pruned = prune(grid)
total_pruned += pruned
return -1 # should be unreachable. should be ...
assert 3068 == part_one(test_input_str)
print("part one:", part_one(puzzle_input_str))
part one: 3219
def part_two(input_str):
return part_one(input_str, 1000000000000)
assert 1514285714288 == part_two(test_input_str)
print("part two:", part_two(puzzle_input_str))
part two: 1582758620701