# Libraries
library(tidyverse)
# Read input from file
<- read_lines("../input/day17.txt", skip_empty_rows = TRUE) |>
input str_split_1("")
Day 17
Advent of Code: Worked Solutions
Setup
Part 1
Representing obstacles as 1s and empty space as 0s, represent the shapes of the falling blocks, the floor, and the walls as bitwise integers:
<- strtoi(c("000111100" ), base = 2)
block1 <- strtoi(c("000010000","000111000","000010000" ), base = 2)
block2 <- strtoi(c("000111000","000001000","000001000" ), base = 2)
block3 <- strtoi(c("000100000","000100000","000100000","000100000"), base = 2)
block4 <- strtoi(c("000110000","000110000" ), base = 2)
block5
<- strtoi("100000001", base = 2)
walls <- strtoi("111111111", base = 2)
floor
<- list(block1, block2, block3, block4, block5) blocks
Define functions that give properties of the tower:
# Print tower to terminal (for debugging)
<- function(tower) {
print_tower |>
tower imap_chr(\(row, name) {
|>
row intToBits() |>
rev() |>
tail(9) |>
as.integer() |>
case_match(0 ~ "·", 1 ~ "#") |>
modify_at(.at = c(1L, 9L), .f = ~ "|") |>
str_c(collapse = "") |>
str_c(name, sep = " ")
|>
}) modify_at(.at = 1L, .f = ~ if_else(.x == "|#######| 0", "+-------+ 0", .x)) |>
rev() |>
cat(sep = "\n")
}
<- function(tower) {
tower_height <- max(which(tower != walls))
idx |>
tower[idx] names() |>
as.double()
}
<- function(tower) {
tower_base <- max(which(accumulate(tower, bitwOr, .dir = "backward") == floor))
idx |>
tower[idx] names() |>
as.double()
}
<- function(tower) {
trim_tower <- tower_base(tower)
base <- tower_height(tower)
top as.character(base:top)]
tower[ }
Define functions that move blocks and check if the move is valid:
<- function(block, dir) {
shift_block <- switch(dir,
f "<" = bitwShiftL,
">" = bitwShiftR
)f(block, 1)
}
<- function(block, tower_slice) {
is_collision any(bitwAnd(block, tower_slice) > 0)
}
# Try to move the block L/R if the move is valid, or return the old one if not
<- function(block, dir, tower_slice) {
try_shift_block <- shift_block(block, dir)
new if (is_collision(new, tower_slice))
blockelse
new }
Define a function to drop blocks onto a tower:
<- function(jets, n_blocks) {
drop_blocks
<- floor
tower names(tower) <- 1:length(tower) - 1
<- length(jets)
n_jets <- 0
time
# Cycle through the list of blocks and drop them in order
for (i in 1:n_blocks) {
<- (i - 1) %% length(blocks) + 1
block_idx <- blocks[[block_idx]]
block
# Initialize the vertical location of the block
<- 1:length(block) + 3 + tower_height(tower)
block_loc
# Add empty wall space to the top of the tower
<- rep(walls, length(block) + 3) |>
add_walls set_names(c(min(block_loc) - 3:1, block_loc))
<- c(tower, add_walls)
tower
# Drop block until it comes to rest
repeat {
<- time %% n_jets + 1
jet_idx
# Apply jet blast & increment time
<- try_shift_block(block, jets[jet_idx], tower[as.character(block_loc)])
block <- time + 1
time
# Check if block has come to rest; if so, add block to tower
if (is_collision(block, tower[as.character(block_loc - 1)])) {
as.character(block_loc)] <- bitwOr(tower[as.character(block_loc)], block)
tower[<- trim_tower(tower)
tower break
} # Otherwise drop block one unit and repeat the block jet seq
else {
<- block_loc - 1
block_loc
}
}
}
tower }
Run on puzzle input:
|>
input drop_blocks(2022) |>
tower_height()
[1] 3133
Part 2
Modify the drop_blocks function to loop until a cycle is found and return cycle info:
<- function(jets) {
find_cycle
<- floor
tower names(tower) <- 1:length(tower) - 1
<- length(jets)
n_jets <- 0
time <- tibble(
states n_blocks = numeric(0),
tower_idx = list(),
tower_val = list(),
block_idx = numeric(0),
jet_idx = numeric(0)
)
<- 1
i # Cycle through the list of blocks and drop them in order
repeat {
<- (i - 1) %% length(blocks) + 1
block_idx <- blocks[[block_idx]]
block
# Initialize the vertical location of the block
<- 1:length(block) + 3 + tower_height(tower)
block_loc
# Add empty wall space to the top of the tower
<- rep(walls, length(block) + 3) |>
add_walls set_names(c(min(block_loc) - 3:1, block_loc))
<- c(tower, add_walls)
tower
# Drop block until it comes to rest
repeat {
<- time %% n_jets + 1
jet_idx
# Apply jet blast & increment time
<- try_shift_block(block, jets[jet_idx], tower[as.character(block_loc)])
block <- time + 1
time
# Check if block has come to rest; if so, add block to tower
if (is_collision(block, tower[as.character(block_loc - 1)])) {
as.character(block_loc)] <- bitwOr(tower[as.character(block_loc)], block)
tower[<- trim_tower(tower)
tower
# Add block and jet index to the states list
<- states |>
states add_row(
n_blocks = i,
tower_idx = list(names(tower)),
tower_val = list(unname(tower)),
block_idx = block_idx,
jet_idx = jet_idx
)# print(tower)
break
} # Otherwise drop block one unit and repeat the block jet seq
else {
<- block_loc - 1
block_loc
}
}
<- i + 1
i
# After each block is dropped, check if a cycle has been found and return it
<- states |>
dupes filter(n_distinct(tower_val) != n(), .by = c(block_idx, jet_idx))
if (nrow(dupes) > 0) {
<- dupes |>
cycle_length pull(n_blocks) |>
reduce(`-`) |>
abs()
<- dupes |>
cycle_start pull(n_blocks) |>
min()
<- dupes |>
cycle_height pull(tower_idx) |>
map(as.numeric) |>
reduce(`-`) |>
unique() |>
abs()
return(list(length = cycle_length, start = cycle_start, height = cycle_height))
return(dupes)
}
} }
Get the cycle of the puzzle input:
<- find_cycle(input) cycle
Using the cycle info from the output, compute the majority of the height using the cycle, then and add the height of the remaineder:
<- floor((1000000000000 - cycle$start) / cycle$length)
n_cycles <- (1000000000000 - cycle$start) %% cycle$length + cycle$start
n_blocks
* cycle$height) + tower_height(drop_blocks(input, n_blocks))) |>
((n_cycles format(scientific = FALSE)
[1] "1547953216393"