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)
}
res <- text_block %>%
str_split(",") %>%
extract2(1L)
str_sub(res, 2L) %>%
str_split("/") %>%
map(~ c(.x, NA)[1:2]) %>%
do.call(rbind, .) %>%
set_colnames(paste0("arg", 1:2)) %>%
as_tibble() %>%
mutate(op = str_sub(res, 1L, 1L), .before = 1L) %>%
mutate(orig_op = res)
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 16
2.1 Part 1
2.1.1 Description
— Day 16: Permutation Promenade —
You come upon a very unusual sight; a group of programs here appear to be dancing.
There are sixteen programs in total, named a
through p
. They start by standing in a line: a
stands in position 0
, b
stands in position 1
, and so on until p
, which stands in position 15
.
The programs’ dance consists of a sequence of dance moves:
-
Spin, written
sX
, makesX
programs move from the end to the front, but maintain their order otherwise. (For example,s3
onabcde
producescdeab
). -
Exchange, written
xA/B
, makes the programs at positionsA
andB
swap places. -
Partner, written
pA/B
, makes the programs namedA
andB
swap places.
For example, with only five programs standing in a line (abcde
), they could do the following dance:
-
s1
, a spin of size1
:eabcd
. -
x3/4
, swapping the last two programs:eabdc
. -
pe/b
, swapping programse
andb
:baedc
.
After finishing their dance, the programs end up in order baedc
.
You watch the dance for a while and record their dance moves (your puzzle input). In what order are the programs standing after their dance?
2.1.2 Solution
We implement a simple iterative approach, executing one line after the other.
dance <- function(ops, n_dancers = 16L, dancers = letters[1:n_dancers]) {
p <- function(arg1, arg2) {
pos <- which(dancers %in% c(arg1, arg2))
dancers[pos] <<- dancers[rev(pos)]
}
x <- function(arg1, arg2) {
arg1 <- as.integer(arg1) + 1L
arg2 <- as.integer(arg2) + 1L
dancers[c(arg1, arg2)] <<- dancers[c(arg2, arg1)]
}
s <- function(arg1, arg2) {
dancers <<- dancers[(seq_along(dancers) - 1L +
n_dancers - as.integer(arg1)) %% n_dancers + 1L]
}
for (i in 1:nrow(ops)) {
call <- ops %>%
slice(i) %>%
select(op:arg2) %>%
as.list()
do.call(call[[1L]], call[-1L])
}
dancers
}
dance(puzzle_data) %>%
paste(collapse = "")
## [1] "padheomkgjfnblic"
2.2 Part 2
2.2.1 Description
— Part Two —
Now that you’re starting to get a feel for the dance moves, you turn your attention to the dance as a whole.
Keeping the positions they ended up in from their previous dance, the programs perform it again and again: including the first dance, a total of one billion (1000000000
) times.
In the example above, their second dance would begin with the order baedc
, and use the same dance moves:
-
s1
, a spin of size1
:cbaed
. -
x3/4
, swapping the last two programs:cbade
. -
pe/b
, swapping programse
andb
:ceadb
.
In what order are the programs standing after their billion dances?
2.2.2 Solution
For the second part we want to detect cycles. If we find a cycle and the remaining iterations are a multiple of the cycle length we can skip right away.
Again R
is too slow for this, so we fall - once more - back to C++
. The R algorithm
can be found in the appendix.
#ifndef STANDALONE
#include <Rcpp.h>
using namespace Rcpp;
#else
#include <iostream>
#endif
#include <algorithm>
#include <array>
#include <unordered_map>
#include <vector>
class LetterContainer {
std::array<char, 16> data;
std::unordered_map<char, int> pos;
public:
LetterContainer(const std::array<char, 16>& init)
: data(init) {
rebuild_map();
}
void swap_by_value(char a, char b) {
int i = pos[a];
int j = pos[b];
std::swap(data[i], data[j]);
pos[a] = j;
pos[b] = i;
}
void swap_by_index(int i, int j) {
char a = data[i], b = data[j];
std::swap(data[i], data[j]);
pos[a] = j;
pos[b] = i;
}
void rotate(int n) {
std::rotate(data.begin(), data.begin() + (n % 16), data.end());
rebuild_map();
}
void reset() {
std::sort(data.begin(), data.end());
rebuild_map();
}
std::string str() const { return std::string(data.begin(), data.end()); }
std::string dance(const std::vector<std::string>& moves, int nr_dances = 1) {
std::unordered_map<std::string, int> seen;
std::string state;
for (int i = 1; i <= nr_dances; i++) {
state = do_dance(moves);
if (seen.count(state)) {
// we have seen this state before
// now check if it the remainng steps are a multiple of the cycle length
if ((nr_dances - i) % (i - seen[state]) == 0) {
break;
}
}
seen[state] = i;
}
return state;
}
private:
void rebuild_map() {
pos.clear();
for (int i = 0; i < 16; i++)
pos[data[i]] = i;
}
std::string do_dance(const std::vector<std::string>& moves) {
for (const std::string& move : moves) {
char type = move[0];
if (type == 's') {
int n = std::stoi(move.substr(1));
rotate(16 - n);
} else if (type == 'x') {
size_t slash = move.find('/');
int a = std::stoi(move.substr(1, slash - 1));
int b = std::stoi(move.substr(slash + 1));
swap_by_index(a, b);
} else if (type == 'p') {
char a = move[1];
char b = move[3];
swap_by_value(a, b);
}
}
return str();
}
};
#ifndef STANDALONE
// [[Rcpp::export]]
std::string dance(CharacterVector& moves, int nr_dances = 1) {
LetterContainer lc(std::array<char, 16>(
{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'}));
return lc.dance(as<std::vector<std::string>>(moves), nr_dances);
}
#else
int main() {
std::vector<std::string> tokens = {"s1", "x3/4", "pe/b"};
LetterContainer lc(std::array<char, 16>(
{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'}));
std::cout << lc.dance(tokens) << std::endl;
return 0;
}
#endif
dance(puzzle_data %>%
pull(orig_op), 1000000000)
## [1] "bfcdeakhijmlgopn"
2.3 R - Version
repeat_dance <- function(ops, nr_dances) {
seen <- list()
state <- letters[1:16]
for (i in 1:nr_dances) {
state <- dance(ops, dancers = state)
state_id <- paste(state, collapse = "")
if (!is.null(seen[[state_id]])) {
if ((nr_dances - i) %% (i - seen[[state_id]]) == 0L) {
break
}
}
seen[[state_id]]
}
state_id
}