# Libraries
library(tidyverse)
# Read input from file
<- read_lines("../input/day15.txt")
input <- keep(input, ~ str_detect(.x, "#"))
input_wh <- keep(input, ~ str_detect(.x, "<|>|\\^|v")) input_mv
Day 15
Advent of Code: Worked Solutions
Setup
Part 1
Convert warehouse input to a row/column indexed dataframe
# Convert WH input text to a matrix
<- input_wh |>
mtx str_split("") |>
matrix() |>
unlist() |>
matrix(byrow = TRUE, nrow = length(input_wh))
# Convert movement sequence to a seq of characters
<- input_mv |>
move_seq str_c(collapse = "") |>
str_split_1("")
Define function to update the map based on a single movement of the robot::
<- function(mtx, dir) {
move_robot # Get the current coordinates of the robot
<- which(mtx == "@", arr.ind = TRUE)
robot <- robot[1, "row"]
row <- robot[1, "col"]
col
# Define the range of matrix values to adjust according to the movement dir
<- case_match(dir, c("<", ">") ~ row, "^" ~ 1, "v" ~ nrow(mtx))
row_end <- case_match(dir, c("^", "v") ~ col, "<" ~ 1, ">" ~ nrow(mtx))
col_end
# Using obstacle logic, determine the set of new characters
<- tibble(
new orig = mtx[row:row_end, col:col_end],
lag = lag(orig)
|>
) mutate(
is_empty = orig == ".",
is_wall = orig == "#",
is_blocked = accumulate(is_wall, `|`),
is_fillable = is_empty & !is_blocked,
first_fillable = is_fillable & !lag(accumulate(is_fillable, `|`)),
can_move = accumulate(first_fillable, `|`, .dir = "backward"),
new = if_else(can_move, coalesce(lag, "."), orig)
|>
) pull(new)
# Replace the affected characters in the matrix and return
:row_end, col:col_end] <- new
mtx[rowreturn(mtx)
}
Define a function to iteratively run the set of movements
<- function(mtx, move_seq) {
run_simulation <- mtx
mtx_prv for (dir in move_seq) {
<- move_robot(mtx_prv, dir)
mtx_new <- mtx_new
mtx_prv
}return(mtx_new)
}
Define a function to determine the GPS coordinates of all boxes
<- function(mtx) {
get_boxes_gps which(mtx == "O", arr.ind = TRUE) |>
as_tibble() |>
mutate(
gps = 100 * (row - 1) + (col - 1)
|>
) pull(gps)
}
Run puzzle input:
|>
mtx run_simulation(move_seq) |>
get_boxes_gps() |>
sum()
[1] 1457740
Part 2
Widen the map:
# Convert WH input text to a matrix
<- input_wh |>
mtx str_replace_all("#", "##") |>
str_replace_all("O", "[]") |>
str_replace_all("\\.", "..") |>
str_replace_all("@", "@.") |>
str_split("") |>
matrix() |>
unlist() |>
matrix(byrow = TRUE, nrow = length(input_wh))
Define functions to move boxes around the map:
<- function(mtx, box_num) {
get_box_coords as_tibble(which(mtx == box_num, arr.ind = TRUE))
}
<- function(cur_coords, dir = c("<", "^", ">", "v")) {
get_next_coords |>
cur_coords mutate(
row = row + case_match(dir, "^" ~ -1, "v" ~ 1, .default = 0),
col = col + case_match(dir, "<" ~ -1, ">" ~ 1, .default = 0),
)
}
<- function(mtx, cur_coords, dir = c("<", "^", ">", "v")) {
get_next_chrs # Pull the values of the next cells in the intended direction
|>
cur_coords get_next_coords(dir) |>
mutate(chr = map2_chr(row, col, ~ mtx[.x, .y])) |>
anti_join(cur_coords, join_by(row, col)) |>
pull(chr) |>
unique()
}
<- function(mtx, box_num, dir = c("<", "^", ">", "v")) {
is_blocked
<- get_box_coords(mtx, box_num)
cur <- get_next_chrs(mtx, cur, dir)
nxt_chrs
# Test if the current box is completely blocked or completely free
if (any(nxt_chrs == '#'))
return(TRUE)
else if (all(nxt_chrs == '.'))
return(FALSE)
# Recurse across all later boxes
|>
nxt_chrs keep(~ str_detect(.x, "^\\d+$")) |>
map_lgl(~ is_blocked(mtx, .x, dir)) |>
any()
}
<- function(mtx, box_num, dir = c("<", "^", ">", "v")) {
move_box # Get the coordinates of the current box and the place it'll move to
<- get_box_coords(mtx, box_num)
cur <- get_next_coords(cur, dir)
nxt
# Move all downstream boxes before moving self
<- get_next_chrs(mtx, cur, dir) |>
next_boxes keep(~ str_detect(.x, "^\\d+$"))
for (box in next_boxes) {
<- move_box(mtx, box, dir)
mtx
}
# # Replace the current coords with "." and the next coords with the box
$row, cur$col] <- "."
mtx[cur$row, nxt$col] <- box_num
mtx[nxt
return(mtx)
}
Loop through puzzle input:
<- function(mtx, move_seq) {
run_simulation # Convert boxes from format "[]" into ID numbers unique to each box:
<- list(l = which(mtx == "["), r = which(mtx == "]"))
coords for (i in 1:length(coords$l)) {
$l[i]] <- i
mtx[coords$r[i]] <- i
mtx[coords
}
# Loop through sequence of moves and apply to the map
<- mtx
mtx_prv for (dir in move_seq) {
if (!is_blocked(mtx_prv, box_num = "@", dir = dir)) {
<- move_box(mtx_prv, box_num = "@", dir = dir)
mtx_new <- mtx_new
mtx_prv
}
}
return(mtx_new)
}
<- run_simulation(mtx, move_seq) output
Convert the result to GPS coordinates:
|>
output as_tibble() |>
mutate(row = row_number(), .before = everything()) |>
pivot_longer(
-row,
names_to = "col",
names_prefix = "V",
names_transform = as.integer
|>
) # Select only the leftmost cell of each boxes
filter(str_detect(value, "\\d+")) |>
slice_min(col, by = value) |>
mutate(
dist_top = row - 1,
dist_left = col - 1,
gps = 100 * dist_top + dist_left
|>
) pull(gps) |>
sum()
[1] 1467145