Task 16

Thorn Thaler - <

2025-09-28

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, makes X programs move from the end to the front, but maintain their order otherwise. (For example, s3 on abcde produces cdeab).
  • Exchange, written xA/B, makes the programs at positions A and B swap places.
  • Partner, written pA/B, makes the programs named A and B swap places.

For example, with only five programs standing in a line (abcde), they could do the following dance:

  • s1, a spin of size 1: eabcd.
  • x3/4, swapping the last two programs: eabdc.
  • pe/b, swapping programs e and b: 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 size 1: cbaed.
  • x3/4, swapping the last two programs: cbade.
  • pe/b, swapping programs e and b: 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
}