Advent of Code 2022 day 17
Part 1 is now redundant because Part 2 also covers its case. Part 1 also takes an incredibly long time. Part 2 uses two tricks to speed up the calculation:
it only looks at the top part of the grid which is still relevant;
once a loop has been found, it works out how often the loop would occur and ‘skips’ doing them.
Part 1
def create_shape(data: str) -> dict:
return {
"height": data.count("\n")+1,
"width": len(data.split("\n")[0]),
"shape": data.replace("#", "@")
}
def grid_top(grid: str) -> int:
return ((len(grid.rstrip("."))-1) // grid_width) + 1
def display_grid(grid: str) -> None:
print(
"\n".join(
"|"+grid[i*grid_width:(i+1)*grid_width]+"|"
for i in reversed(range(grid_top(grid)+1))
) +
"\n" +
"+" + "-"*grid_width + "+"
)
def initialise_shape(shape: dict, grid: str) -> str:
end_height = init_x + grid_top(grid)
start_height = shape["height"] + end_height
return (
grid[:end_height*grid_width] +
"".join(
"."*init_y +
shape["shape"].split("\n")[i] +
"."*(grid_width - init_y - shape["width"])
for i in reversed(range(shape["height"]))
) +
grid[start_height*grid_width:]
)
def can_move_left(grid: str) -> bool:
return all(
i % grid_width != 0 and grid[i-1] != "#"
for i, x in enumerate(grid) if x == "@"
)
def can_move_right(grid: str) -> bool:
return all(
i % grid_width != grid_width-1 and grid[i+1] != "#"
for i, x in enumerate(grid) if x == "@"
)
def can_move_down(grid: str) -> bool:
return all(
i - grid_width > 0 and grid[i-grid_width] != "#"
for i, x in enumerate(grid) if x == "@"
)
def move_left(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i-1] + "@" + new_grid[i:]
)
return new_grid
def move_right(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i+1] + "@" + new_grid[i+2:]
)
return new_grid
def move_down(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i-grid_width] + "@" + new_grid[i-grid_width+1:]
)
return new_grid
def move_left_right(grid: str) -> str:
next_move = move()
if next_move == "<" and can_move_left(grid):
grid = move_left(grid)
elif next_move == ">" and can_move_right(grid):
grid = move_right(grid)
return grid
def turn_stationary(grid: str) -> str:
return grid.replace("@", "#")
shapes_raw = """####
.#.
###
.#.
..#
..#
###
#
#
#
#
##
##"""
shapes = [create_shape(x) for x in shapes_raw.split("\n\n")]
number_of_rocks = 2022
# number_of_rocks = 20
grid_height = number_of_rocks*4
grid_width = 7
init_x = 3
init_y = 2
grid = "."*grid_width*grid_height
with open("17.txt") as f:
jet_stream = f.read().strip()
print(jet_stream)
move_i = -1
def move() -> str:
global move_i
move_i += 1
return jet_stream[move_i % len(jet_stream)]
for i in range(number_of_rocks):
print(i)
shape = shapes[i % len(shapes)]
grid = initialise_shape(shape, grid)
# display_grid(grid)
grid = move_left_right(grid)
while can_move_down(grid):
grid = move_down(grid)
grid = move_left_right(grid)
grid = turn_stationary(grid)
print(grid_top(grid))
Part 2
def create_shape(data: str) -> dict:
return {
"height": data.count("\n")+1,
"width": len(data.split("\n")[0]),
"shape": data.replace("#", "@")
}
def grid_top(grid: str) -> int:
return ((len(grid.rstrip("."))-1) // grid_width) + 1
def display_grid(grid: str) -> None:
print(
"\n".join(
"|"+grid[i*grid_width:(i+1)*grid_width]+"|"
for i in reversed(range(grid_top(grid)+1))
) +
"\n" +
"+" + "-"*grid_width + "+"
)
def cut_grid(grid: str, grid_height: int) -> (str, int):
at = len(grid)
for i in range(grid_width):
row = 0
for j in range(grid_top(grid)):
if grid[j*grid_width+i] == "#":
row = j
if row < at:
at = row
if at == len(grid):
return grid+grid_addon, grid_height
return (grid[at*grid_width:]+grid_addon, grid_height+at)
def trim_grid(grid: str) -> str:
return grid[:(grid_top(grid)+1)*grid_width]
def initialise_shape(shape: dict, grid: str) -> str:
end_height = init_x + grid_top(grid)
start_height = shape["height"] + end_height
return (
grid[:end_height*grid_width] +
"".join(
"."*init_y +
shape["shape"].split("\n")[i] +
"."*(grid_width - init_y - shape["width"])
for i in reversed(range(shape["height"]))
) +
grid[start_height*grid_width:]
)
def can_move_left(grid: str) -> bool:
return all(
i % grid_width != 0 and grid[i-1] != "#"
for i, x in enumerate(grid) if x == "@"
)
def can_move_right(grid: str) -> bool:
return all(
i % grid_width != grid_width-1 and grid[i+1] != "#"
for i, x in enumerate(grid) if x == "@"
)
def can_move_down(grid: str) -> bool:
return all(
i - grid_width > 0 and grid[i-grid_width] != "#"
for i, x in enumerate(grid) if x == "@"
)
def move_left(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i-1] + "@" + new_grid[i:]
)
return new_grid
def move_right(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i+1] + "@" + new_grid[i+2:]
)
return new_grid
def move_down(grid: str) -> str:
new_grid = grid.replace("@", ".")
for i, x in enumerate(grid):
if x == "@":
new_grid = (
new_grid[:i-grid_width] + "@" + new_grid[i-grid_width+1:]
)
return new_grid
def move_left_right(grid: str) -> str:
def move() -> str:
global move_i
move_i += 1
return jet_stream[move_i % len(jet_stream)]
next_move = move()
if next_move == "<" and can_move_left(grid):
grid = move_left(grid)
elif next_move == ">" and can_move_right(grid):
grid = move_right(grid)
return grid
def turn_stationary(grid: str) -> str:
return grid.replace("@", "#")
shapes_raw = """####
.#.
###
.#.
..#
..#
###
#
#
#
#
##
##"""
shapes = [create_shape(x) for x in shapes_raw.split("\n\n")]
number_of_rocks = 1000000000000
# number_of_rocks = 2022
# number_of_rocks = 100
grid_height = 0
grid_width = 7
init_x = 3
init_y = 2
with open("17.txt") as f:
# with open("17_test.txt") as f:
jet_stream = f.read().strip()
print(jet_stream)
move_i = -1
grid_addon = "."*grid_width*(init_x+1+max(shape["height"] for shape in shapes))
grid = grid_addon
grids_so_far = set()
def find_previous_turn(grids_so_far: set, grid: str, move_i: int):
for g, m, old_turn, old_grid_height in grids_so_far:
if g == grid and m == move_i:
return g, m, old_turn, old_grid_height
return None, None, None, None
# # Manually
# for i in range(number_of_rocks):
# shape = shapes[i % len(shapes)]
# grid, grid_height = cut_grid(grid, grid_height)
# grid = initialise_shape(shape, grid)
# grid = trim_grid(grid)
# # display_grid(grid)
# grid = move_left_right(grid)
# while can_move_down(grid):
# grid = move_down(grid)
# grid = move_left_right(grid)
# grid = turn_stationary(grid)
# print("Real answer", grid_height+grid_top(grid))
# display_grid(grid)
# ###################################
# # Copied over
# with open("17.txt") as f:
# # with open("17_test.txt") as f:
# jet_stream = f.read().strip()
# print(jet_stream)
# move_i = -1
# grid_addon = "."*grid_width*(init_x+1+max(shape["height"] for shape in shapes))
# grid = grid_addon
# grid_height = 0
# grids_so_far = set()
# ###################################
old_turn = 0
old_grid_height = 0
for i in range(number_of_rocks):
shape = shapes[i % len(shapes)]
grid, grid_height = cut_grid(grid, grid_height)
grid = initialise_shape(shape, grid)
grid = trim_grid(grid)
grid = move_left_right(grid)
while can_move_down(grid):
grid = move_down(grid)
grid = move_left_right(grid)
grid = turn_stationary(grid)
turns = i
if any(
grid == g and move_i % len(jet_stream) == m
for g, m, old_i, old_grid_height in grids_so_far
):
old_grid, _, old_turn, old_grid_height = find_previous_turn(
grids_so_far,
grid,
move_i % len(jet_stream)
)
break
else:
grids_so_far.add((grid, move_i % len(jet_stream), i, grid_height))
repeat = (number_of_rocks // (turns - old_turn)) - 1
grid_height = old_grid_height + ((grid_height - old_grid_height) * repeat)
for i in range(old_turn + (turns-old_turn)*repeat + 1, number_of_rocks):
shape = shapes[i % len(shapes)]
grid, grid_height = cut_grid(grid, grid_height)
grid = initialise_shape(shape, grid)
grid = trim_grid(grid)
grid = move_left_right(grid)
while can_move_down(grid):
grid = move_down(grid)
grid = move_left_right(grid)
grid = turn_stationary(grid)
print("answer", grid_height+grid_top(grid))