Task 21

Thorn Thaler - <

2025-10-01

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)
  }
  line2matrix <- function(line) {
    nr <- str_count(line, "/") + 1L
    line %>% 
      str_remove_all(fixed("/")) %>% 
      str_split("") %>% 
      extract2(1L) %>% 
      matrix(nrow = nr, byrow = TRUE)
  }
  text_block %>% 
    str_split(fixed(" => ")) %>% 
    map(~ list(
      input = line2matrix(.x[[1L]]),
      output = line2matrix(.x[[2L]])
      )
    )
}

puzzle_data <- local({
  GET(paste0(base_url, "/input"),
      session_cookie) %>% 
    content(encoding = "UTF-8") %>% 
    parse_puzzle_data()
})

2 Puzzle Day 21

2.1 Part 1

2.1.1 Description

— Day 21: Fractal Art —

You find a program trying to generate some art. It uses a strange process that involves repeatedly enhancing the detail of an image through a set of rules.

The image consists of a two-dimensional square grid of pixels that are either on (#) or off (.). The program always begins with this pattern:

.#.
..#
###

Because the pattern is both 3 pixels wide and 3 pixels tall, it is said to have a size of 3.

Then, the program repeats the following process:

  • If the size is evenly divisible by 2, break the pixels up into 2x2 squares, and convert each 2x2 square into a 3x3 square by following the corresponding enhancement rule.
  • Otherwise, the size is evenly divisible by 3; break the pixels up into 3x3 squares, and convert each 3x3 square into a 4x4 square by following the corresponding enhancement rule.

Because each square of pixels is replaced by a larger one, the image gains pixels and so its size increases.

The artist’s book of enhancement rules is nearby (your puzzle input); however, it seems to be missing rules. The artist explains that sometimes, one must rotate or flip the input pattern to find a match. (Never rotate or flip the output pattern, though.) Each pattern is written concisely: rows are listed as single units, ordered top-down, and separated by slashes. For example, the following rules correspond to the adjacent patterns:

../.#  =  ..
          .#

                .#.
.#./..#/###  =  ..#
                ###

                        #..#
#..#/..../#..#/.##.  =  ....
                        #..#
                        .##.

When searching for a rule to use, rotate and flip the pattern as necessary. For example, all of the following patterns match the same rule:

.#.   .#.   #..   ###
..#   #..   #.#   ..#
###   ###   ##.   .#.

Suppose the book contained the following two rules:

../.# => ##./#../...
.#./..#/### => #..#/..../..../#..#

As before, the program begins with this pattern:

.#.
..#
###

The size of the grid (3) is not divisible by 2, but it is divisible by 3. It divides evenly into a single square; the square matches the second rule, which produces:

#..#
....
....
#..#

The size of this enhanced grid (4) is evenly divisible by 2, so that rule is used. It divides evenly into four squares:

#.|.#
..|..
--+--
..|..
#.|.#

Each of these squares matches the same rule (../.# => ##./#../…), three of which require some flipping and rotation to line up with the rule. The output for the rule is the same in all four cases:

##.|##.
#..|#..
...|...
---+---
##.|##.
#..|#..
...|...

Finally, the squares are joined into a new grid:

##.##.
#..#..
......
##.##.
#..#..
......

Thus, after 2 iterations, the grid contains 12 pixels that are on.

How many pixels stay on after 5 iterations?

2.1.2 Solution

First of all, we amend the rule book by all rotations and flips on the input. Then, we loop through the initial picture and apply all rules that apply.

transform <- function(input) {
  n <- nrow(input)
  
  # Define flip & rotation indices once
  if (n == 2L) {
    flip_idx <- list(
      h = c(2L, 1L, 4L, 3L),
      v = c(3L, 4L, 1L, 2L)
    )
    rot_idx <- c(2L, 4L, 1L, 3L)
  } else if (n == 3L) {
    flip_idx <- list(
      h = c(3L, 2L, 1L, 6L, 5L, 4L, 9L, 8L, 7L),
      v = c(7L, 8L, 9L, 4L, 5L, 6L, 1L, 2L, 3L)
    )
    rot_idx <- c(3L, 6L, 9L, 2L, 5L, 8L, 1L, 4L, 7L)
  }
  
  base <- c(input)
  res <- character(0L)
  
  cur <- base
  for (i in 1:4) {  # 4 rotations
    res <- c(res, paste(cur, collapse = ""))
    res <- c(res, vapply(flip_idx, function(idx) paste(cur[idx], collapse = ""), 
                         character(1L)))
    cur <- cur[rot_idx]
  }
  unique(res)
}


create_rule_book <- function(basic_rules) {
  rules <- list()
  for (rule in basic_rules) {
    trafs <- transform(rule$input)
    for (new_rule in trafs) {
      stopifnot("ambigious rule" = !new_rule %in% names(rules))
      rules[[new_rule]] <- rule$output
    }
  }
  rules
}

paint <- function(n, basic_rules = puzzle_data) {
  rule_book <- create_rule_book(basic_rules)
  res <- matrix(c(".", ".", "#", "#", ".", "#", ".", "#", "#"), 3L)
  ## prefer base R to speed up
  for (i in 1:n) {
    size <- nrow(res)
    width <- if_else(size %% 2L == 0L, 2L, 3L)
    I <- seq(1L, size, by = width)
    J <- 0:(width - 1L)
    dirs <- as.matrix(expand.grid(J, J))
    k <- size / width
    new_res <- vector("list", k * k)
    idx <- as.matrix(expand.grid(I, I))
    for (block in seq_len(nrow(idx))) {
      start <- idx[block, ]
      pos <- t(t(dirs) + start)
      key <- paste(res[pos], collapse = "")
      if (!is.null(rule_book[[key]])) {
        new <- rule_book[[key]]
      } else {
        new <- matrix(str_split(key, "")[[1L]], width)
      }
      new_res[[block]] <- new
    }
    cols <- lapply(1:k, function(col) 
      do.call(rbind, new_res[((col - 1L) * k + 1L):(col * k)]))
    res <- do.call(cbind, cols)
  }
  sum(res == "#")
}
paint(5L, puzzle_data)
## [1] 158

2.2 Part 2

2.2.1 Description

— Part Two —

How many pixels stay on after 18 iterations?

2.2.2 Solution

We simply have to increase the number of iterations.

paint(18L, puzzle_data)
## [1] 2301762