# Libraries
library(tidyverse)
# Read input from file
<- read_lines("../input/day19.txt", skip_empty_rows = TRUE) input
Day 19
Advent of Code: Worked Solutions
Setup
Part 1
Reformat the input as lists of robots with inputs and outputs for each blueprint:
<- c("ore" = 1, "clay" = 0, "obsidian" = 0, "geode" = 0)
ore <- c("ore" = 0, "clay" = 1, "obsidian" = 0, "geode" = 0)
clay <- c("ore" = 0, "clay" = 0, "obsidian" = 1, "geode" = 0)
obsidian <- c("ore" = 0, "clay" = 0, "obsidian" = 0, "geode" = 1)
geode <- c("ore" = 0, "clay" = 0, "obsidian" = 0, "geode" = 0)
empty
<- str_c(
unglue_pattern "Blueprint {blueprint}:",
"Each ore robot costs {ore_ore} ore.",
"Each clay robot costs {clay_ore} ore.",
"Each obsidian robot costs {obsidian_ore} ore and {obsidian_clay} clay.",
"Each geode robot costs {geode_ore} ore and {geode_obsidian} obsidian.",
sep = " "
)
<- input |>
blueprints ::unglue_data(unglue_pattern, convert = TRUE) |>
ungluepivot_longer(
-blueprint,
names_to = c("robot", "cost_type"),
values_to = "cost_value",
names_sep = "_"
|>
) pivot_wider(names_from = cost_type, values_from = cost_value, values_fill = 0) |>
mutate(geode = 0) |>
nest(cost = c(ore, clay, obsidian, geode)) |>
mutate(
cost = map(cost, unlist),
output = map(robot, get)
|>
) group_split(blueprint)
Define a set of functions to find the maximum number of geodes for each blueprint.
Optimizations to improve runtime were sourced from Reddit (1, 2).
<- function(blueprint, inventory, production, time_left, max_cost, cur_best = 0) {
max_geodes
# If the best possible set of geode robots in this branch can't outdo the current
# best, then don't traverse this branch.
<- inventory["geode"] +
theoretical_best "geode"] * time_left +
production[sum(1:time_left - 1)
if (time_left == 0 | theoretical_best <= cur_best)
return(inventory["geode"])
# Determine the amount of time required to build each robot next
<- map_dbl(blueprint$cost,
time_to_build ~ ceiling((.x - inventory) / production) |>
keep(.x > 0) |>
map_dbl(~ max(.x, 0) + 1) |>
max()
)
# Determine which robots are buildable within the remaining time
<- which(
idx is.finite(time_to_build) &
< time_left &
time_to_build < max_cost
production
)
# Quit if there are no remaining options
if (length(idx) == 0)
return((inventory + time_left * production)["geode"])
# Loop through each branch of options
for (i in rev(idx)) {
<- max_geodes(
branch_best blueprint = blueprint,
inventory = inventory +
* time_to_build[[i]] -
production $cost[[i]],
blueprintproduction = production + blueprint$output[[i]],
time_left = time_left - time_to_build[[i]],
max_cost = max_cost,
cur_best = cur_best
)if (branch_best > cur_best)
<- branch_best
cur_best
}
return(cur_best)
}
<- function(blueprints, total_time) {
blueprint_geodes
<- c()
best
for (blueprint in blueprints) {
<- blueprint |>
max_cost pull(cost) |>
do.call(what = pmax) |>
modify_at(.at = "geode", ~ Inf)
<- c(
best
best,max_geodes(
blueprint = blueprint,
inventory = empty,
production = ore,
time_left = total_time,
max_cost = max_cost,
cur_best = 0
)
)
}
unname(best)
}
Compute total quality score by multiplying the max geodes for each blueprint by its index:
blueprint_geodes(blueprints, total_time = 24) |>
imap_dbl(\(geodes, i) i * geodes) |>
sum()
[1] 2160
Part 2
Filter to the first 3 blueprints, increase the total time to 32 seconds, and take the product of the max geodes:
|>
blueprints keep_at(1:3) |>
blueprint_geodes(total_time = 32) |>
prod()
[1] 13340