# Libraries
library(tidyverse)
# Read input from file
<- read_lines("../input/day21.txt", skip_empty_rows = FALSE) input
Day 21
Advent of Code: Worked Solutions
Setup
Part 1
Convert text input to a graph:
# Convert text input to grid coordinates
<- input |>
df str_split("") |>
enframe(name = "row") |>
unnest_longer(value, indices_to = "col") |>
mutate(id = row_number())
<- pull(df, value)
vertices
# Compute edges for all cells to convert to a graph
<- df |>
neighbors mutate(
n = case_when(lag(value) != '#' ~ lag(id)),
s = case_when(lead(value) != '#' ~ lead(id)),
.by = col
|>
) mutate(
w = case_when(lag(value) != '#' ~ lag(id)),
e = case_when(lead(value) != '#' ~ lead(id)),
.by = row
|>
) filter(value != '#') |>
select(id, n, s, w, e) |>
pivot_longer(
c(n, s, w, e),
names_to = "dir",
values_to = "neighbor",
values_drop_na = TRUE
|>
) summarize(neighbors = list(neighbor), .by = id) |>
complete(id = 1:length(vertices)) |>
pull(neighbors) |>
map(sort)
For 64 steps, loop through neighbors to find possible locations:
<- function(start, n_steps) {
num_plots <- start
locs for (i in 1:n_steps) {
<- neighbors |>
locs keep_at(locs) |>
unlist() |>
unique()
}length(locs)
}
num_plots(match('S', vertices), 64)
[1] 3532
Part 2
Since there are no obstacles in the center row or column (where the starting position is), we can easily compute how quickly it takes to enter a given “box” (repeat of the map) and from which direction we enter.
First, we compute the number of steps it takes for a given box to enter a periodic cycle, depending on whether its starting location is from the center starting point, the middle of an edge, or a corner:
<- function(start_vtx) {
find_cycle <- NULL
locs_prv <- start_vtx
locs_cur <- 0
i
repeat {
<- neighbors |>
locs_new keep_at(locs_cur) |>
unlist() |>
unique() |>
sort()
if (identical(locs_prv, locs_new))
return(i - 1)
<- locs_cur
locs_prv <- locs_new
locs_cur <- i + 1
i
}
}
<- max(df$col)
w_max <- ceiling(w_max / 2)
w_mid
<- df |>
starting_points mutate(name = case_when(
== 'S' ~ 'center',
value == w_mid & row == 1 ~ 'n',
col == w_mid & row == w_max ~ 's',
col == w_mid & col == 1 ~ 'w',
row == w_mid & col == w_max ~ 'e',
row == 1 & row == 1 ~ 'nw',
col == w_max & row == 1 ~ 'ne',
col == 1 & row == w_max ~ 'sw',
col == w_max & row == w_max ~ 'se'
col |>
)) filter(!is.na(name)) |>
select(name, id) |>
deframe()
# Determine when a cycle first occurs for the center, mid-edge, and corner pieces
<- map(
cycles c(
corner = starting_points[["nw"]],
mid_edge = starting_points[["n"]],
center = starting_points[["center"]]
),
find_cycle )
Next, we split our total map into different box types. Boxes along the same horizontal/vertical lines as the initial starting point will always be entered from a mid-edge plot. All other boxes – those along the diagonal/off-diagonal – will be entered from the corner closest to the origin.
We can map the non-straight-line boxes as concentric rings indicating their Manhattan distance from the origin:
3X3
32X23
321X123
XXX.XXX
321X123
32X23
3X3
Note that in each quadrant, there’s 1 box 1 unit from the origin, 2 boxes 2 units away, 3 boxes 3 units away, etc. Meanwhile, for the straight-line boxes, there’s only one box along each axis of its distance from the origin.
Finally, we also have to take into account the fact that the periodic cycles alternate between odd and even steps. The number of plots in a box at a given step number depends on whether we entered that box on an even or an odd step. Below, all boxes entered on an odd step are labeled as A-type boxes, and those entered on an even step are B-type.
The number of plots in a cycling box are straightforward to compute. However, when a box is entered near the last of the steps and doesn’t have enough time to enter a cycle, we have to compute its current number of plots separately at the time the simulation ends.
We begin with the diagonal/off-diagonal boxes:
<- 26501365
n
<- ceiling(n / w_max - 1)
rings_reached <- ceiling((n - cycles$corner) / w_max - 1)
rings_cycling <- (rings_cycling + 1):rings_reached
rings_in_progress
# Compute the current value for the non-cycling diags
<- starting_points |>
plots_in_progress keep_at(c("nw", "ne", "sw", "se")) |>
map(\(init_idx) {
|>
rings_in_progress # Compute the step when each ring was entered from its corner
map_dbl(~ w_max * .x + 1) |>
# Compute how many steps into the cycle each box currently is
map_dbl(~ n - .x) |>
map_dbl(~ num_plots(init_idx, .x))
|>
}) map(~ .x * rings_in_progress) |>
map_dbl(sum)
# Compute the current value for the cycling diags
<- (1:rings_cycling) |>
cycling_a keep(~ .x %% 2 == 0) |>
sum()
<- (1:rings_cycling) |>
cycling_b keep(~ .x %% 2 == 1) |>
sum()
<- cycles$corner + if_else(cycles$corner %% 2 == 0, 0, 1)
cycle_steps_even <- cycles$corner + if_else(cycles$corner %% 2 == 0, 1, 0)
cycle_steps_odd
<- cycling_a * num_plots(1, cycle_steps_even)
plots_a <- cycling_b * num_plots(1, cycle_steps_odd)
plots_b
<- plots_a + plots_b
plots_cycling
# Compute total value of all diagonal boxes
<- 4 * plots_cycling + sum(plots_in_progress) plots_diag
Now repeat for the straight-line boxes:
<- ceiling((n + w_mid) / w_max - 1)
straights_reached <- ceiling((n + w_mid - cycles$mid_edge) / w_max - 1)
straights_cycling <- (straights_cycling + 1):straights_reached
straights_in_progress
# Compute the current value for the non-cycling straights
<- starting_points |>
plots_in_progress keep_at(c("n", "s", "w", "e")) |>
map_dbl(\(init_idx) {
|>
straights_in_progress # Compute the step number when each box was entered from the edge
map_dbl(~ w_max * .x - w_mid + 1) |>
# Compute how many steps into the cycle each box currently is
map_dbl(~ n - .x) |>
map_dbl(~ num_plots(init_idx, .x)) |>
sum()
})
# Compute the current value for the cycling straights
<- (1:straights_cycling) |>
cycling_a keep(~ .x %% 2 == 0) |>
length()
<- (1:straights_cycling) |>
cycling_b keep(~ .x %% 2 == 1) |>
length()
<- cycles$mid_edge + if_else(cycles$mid_edge %% 2 == 0, 0, 1)
cycle_steps_even <- cycles$mid_edge + if_else(cycles$mid_edge %% 2 == 0, 1, 0)
cycle_steps_odd
<- cycling_a * num_plots(w_mid, cycle_steps_even)
plots_a <- cycling_b * num_plots(w_mid, cycle_steps_odd)
plots_b
<- plots_a + plots_b
plots_cycling
# Compute total value of all straight-line boxes
<- 4 * plots_cycling + sum(plots_in_progress) plots_straight
Finally, compute the value for the center box:
<- if_else(cycles$center %% 2 == 0, 1, 0) + cycles$center
cycle_steps_odd <- num_plots(match('S', vertices), cycle_steps_odd) plots_center
Sum to get the final result:
format(plots_center + plots_straight + plots_diag, scientific = FALSE)
[1] "590104708070703"