1 Setup
1.1 Libraries
library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
1.2 Retrieve Data from AoC
session_cookie <- set_cookies(session = keyring::key_get("AoC-GitHub-Cookie"))
base_url <- paste0("https://adventofcode.com/", params$year, "/day/", params$task_nr)
puzzle <- GET(base_url,
session_cookie) %>%
content(encoding = "UTF-8") %>%
xml_find_all("///article") %>%
lapply(as.character)
parse_puzzle_data <- function(text_block = readClipboard()) {
if (length(text_block) == 1L) {
text_block <- text_block %>%
str_split("\n") %>%
extract2(1L) %>%
keep(nzchar)
}
text_block %>%
as.integer()
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 14
2.1 Part 1
2.1.1 Description
— Day 14: Chocolate Charts —
You finally have a chance to look at all of the produce moving around. Chocolate, cinnamon, mint, chili peppers, nutmeg, vanilla… the Elves must be growing these plants to make hot chocolate! As you realize this, you hear a conversation in the distance. When you go to investigate, you discover two Elves in what appears to be a makeshift underground kitchen/laboratory.
The Elves are trying to come up with the ultimate hot chocolate recipe; they’re even maintaining a scoreboard which tracks the quality score (0
-9
) of each recipe.
Only two recipes are on the board: the first recipe got a score of 3
, the second, 7
. Each of the two Elves has a current recipe: the first Elf starts with the first recipe, and the second Elf starts with the second recipe.
To create new recipes, the two Elves combine their current recipes. This creates new recipes from the digits of the sum of the current recipes’ scores. With the current recipes’ scores of 3
and 7
, their sum is 10
, and so two new recipes would be created: the first with score 1
and the second with score 0
. If the current recipes’ scores were 2
and 3
, the sum, 5
, would only create one recipe (with a score of 5
) with its single digit.
The new recipes are added to the end of the scoreboard in the order they are created. So, after the first round, the scoreboard is 3, 7, 1, 0
.
After all new recipes are added to the scoreboard, each Elf picks a new current recipe. To do this, the Elf steps forward through the scoreboard a number of recipes equal to 1 plus the score of their current recipe. So, after the first round, the first Elf moves forward 1 + 3 = 4
times, while the second Elf moves forward 1 + 7 = 8
times. If they run out of recipes, they loop back around to the beginning. After the first round, both Elves happen to loop around until they land on the same recipe that they had in the beginning; in general, they will move to different recipes.
Drawing the first Elf as parentheses and the second Elf as square brackets, they continue this process:
(3)[7]
(3)[7] 1 0
3 7 1 [0](1) 0
3 7 1 0 [1] 0 (1)
(3) 7 1 0 1 0 [1] 2
3 7 1 0 (1) 0 1 2 [4]
3 7 1 [0] 1 0 (1) 2 4 5
3 7 1 0 [1] 0 1 2 (4) 5 1
3 (7) 1 0 1 0 [1] 2 4 5 1 5
3 7 1 0 1 0 1 2 [4](5) 1 5 8
3 (7) 1 0 1 0 1 2 4 5 1 5 8 [9]
3 7 1 0 1 0 1 [2] 4 (5) 1 5 8 9 1 6
3 7 1 0 1 0 1 2 4 5 [1] 5 8 9 1 (6) 7
3 7 1 0 (1) 0 1 2 4 5 1 5 [8] 9 1 6 7 7
3 7 [1] 0 1 0 (1) 2 4 5 1 5 8 9 1 6 7 7 9
3 7 1 0 [1] 0 1 2 (4) 5 1 5 8 9 1 6 7 7 9 2
The Elves think their skill will improve after making a few recipes (your puzzle input). However, that could take ages; you can speed this up considerably by identifying the scores of the ten recipes after that. For example:
-
If the Elves think their skill will improve after making
9
recipes, the scores of the ten recipes after the first nine on the scoreboard would be5158916779
(highlighted in the last line of the diagram). -
After
5
recipes, the scores of the next ten would be0124515891
. -
After
18
recipes, the scores of the next ten would be9251071085
. -
After
2018
recipes, the scores of the next ten would be5941429882
.
What are the scores of the ten recipes immediately after the number of recipes in your puzzle input?
2.1.2 Solution
For the first part, we create the vector as requested. The vector can grow by 2 fields in each iteration. Thus to avoid physically growing the vector in each iteration, we create a large enough vector in the beginning and keep track about its real length.
brew_cacao <- function(n = puzzle_input) {
cacao_quality <- rep(NA_integer_, n + 10L)
cacao_quality[1:2] <- c(3L, 7L)
k <- 2L
i <- 1L
j <- 2L
while (k < n + 10L) {
new_score <- sum(cacao_quality[c(i, j)])
if (new_score >= 10L) {
new_score <- c(1L, new_score - 10L)
}
cacao_quality[k + seq_along(new_score)] <- new_score
k <- k + length(new_score)
i <- (i + cacao_quality[i]) %% k + 1L
j <- (j + cacao_quality[j]) %% k + 1L
}
cacao_quality[seq(n + 1L, length.out = 10L)] %>%
paste(collapse = "")
}
brew_cacao(puzzle_data)
## [1] "1741551073"
2.2 Part 2
2.2.1 Description
— Part Two —
As it turns out, you got the Elves’ plan backwards. They actually want to know how many recipes appear on the scoreboard to the left of the first recipes whose scores are the digits from your puzzle input.
-
51589
first appears after9
recipes. -
01245
first appears after5
recipes. -
92510
first appears after18
recipes. -
59414
first appears after2018
recipes.
How many recipes appear on the scoreboard to the left of the score sequence in your puzzle input?
2.2.2 Solution
We use again an iterative approach looking for the first match. But since R
is a bit
slow, we fall back to C++
(the R
version can be found in the appendix).
#ifndef STANDALONE
#include <Rcpp.h>
using namespace Rcpp;
#else
#include <iostream>
#endif
#include <vector>
#include <algorithm>
// [[Rcpp::export]]
int find_cacao(int n) {
std::vector<int> target;
for (int temp = n; temp > 0; temp /= 10) {
target.push_back(temp % 10);
}
std::reverse(target.begin(), target.end());
std::vector<int> quality = {3, 7};
size_t i = 0, j = 1;
while (true) {
int sum = quality[i] + quality[j];
if (sum >= 10) {
quality.push_back(sum / 10);
}
quality.push_back(sum % 10);
i = (i + 1 + quality[i]) % quality.size();
j = (j + 1 + quality[j]) % quality.size();
// Check for match at the end
if (quality.size() >= target.size()) {
size_t sz = quality.size();
if (std::equal(target.begin(), target.end(), quality.end() - target.size()))
return sz - target.size();
if (sum >= 10 && sz >= target.size() + 1) {
if (std::equal(target.begin(), target.end(), quality.end() - target.size() - 1))
return sz - target.size() - 1;
}
}
}
}
#ifdef STANDALONE
int main() {
std::cout << find_match(704321) << std::endl;
return 0;
}
#endif
find_cacao(puzzle_data)
## [1] 20322683
2.3 R - Version
find_cacao <- function(n = puzzle_data) {
target <- as.character(n)
n_target <- nchar(target)
cacao_quality <- rep(NA_integer_, 256L)
cacao_quality[1:2] <- c(3L, 7L)
k <- 2L
i <- 1L
j <- 2L
found <- FALSE
while (!found || k < n_target) {
if (length(cacao_quality) < k + 2L) {
cacao_quality <- c(cacao_quality, rep(NA_integer_, length(cacao_quality)))
}
new_score <- cacao_quality[i] + cacao_quality[j]
if (new_score >= 10L) {
new_score <- c(1L, new_score - 10L)
}
cacao_quality[k + seq_along(new_score)] <- new_score
k <- k + length(new_score)
i <- (i + cacao_quality[i]) %% k + 1L
j <- (j + cacao_quality[j]) %% k + 1L
if (k >= n_target) {
idx <- seq(k - n_target + 1L, length.out = n_target)
found <- paste(cacao_quality[idx], collapse = "") == target
if (found) {
return(k - n_target)
} else if (length(new_score == 2L)) {
found <- paste(cacao_quality[idx - 1L], collapse = "") == target
if (found) {
return(k - n_target - 1L)
}
}
}
}
}