Task 23

Thorn Thaler - <

2025-10-02

1 Setup

1.1 Libraries

library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
library(numbers)

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 %>% 
    str_split(" ") %>% 
    map(~ c(.x, NA_character_)[1:3]) %>% 
    do.call(rbind, .) %>% 
    set_colnames(c("op", "arg1", "arg2")) %>% 
    as_tibble() %>% 
    mutate(op_original = text_block, .before = 1L)
}

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

2 Puzzle Day 23

2.1 Part 1

2.1.1 Description

— Day 23: Coprocessor Conflagration —

You decide to head directly to the CPU and fix the printer from there. As you get close, you find an experimental coprocessor doing so much work that the local programs are afraid it will halt and catch fire. This would cause serious issues for the rest of the computer, so you head in and see what you can do.

The code it’s running seems to be a variant of the kind you saw recently on that tablet. The general functionality seems very similar, but some of the instructions are different:

  • set X Y sets register X to the value of Y.
  • sub X Y decreases register X by the value of Y.
  • mul X Y sets register X to the result of multiplying the value contained in register X by the value of Y.
  • jnz X Y jumps with an offset of the value of Y, but only if the value of X is not zero. (An offset of 2 skips the next instruction, an offset of -1 jumps to the previous instruction, and so on.)
  • Only the instructions listed above are used. The eight registers here, named a through h, all start at 0.

The coprocessor is currently set to some kind of debug mode, which allows for testing, but prevents it from doing any meaningful work.

If you run the program (your puzzle input), how many times is the mul instruction invoked?

2.1.2 Solution

Let’s first translate the assembly code line by line into a real (unoptimized) R program:

cp <- function() {
  # Initialize all variables to 0
  a <- b <- c <- d <- e <- f <- g <- h <- 0
  
  # Set b and c to 99
  b <- 99
  c <- b
  
  # This block is skipped because a == 0
  if (a != 0) {
    # if a == 1 we use different starting values: b = 109900 and c = 126900
    b <- 100 * b        
    n <- 1
    b <- b + 100000     
    c <- b
    c <- c + 17000      
  }
  
  # Initialize counter for operations (not in the assembly code but needed for the answer)
  n <- 0 
  
  # Infinite loop until return
  while(TRUE) {
    f <- 1  # flag to indicate if current b is prime (1 = prime, 0 = composite)
    d <- 2  # first factor to test
    
    # Outer loop over possible factors d = 2...b - 1
    repeat {
      e <- 2  # inner factor to test
      # Inner loop over possible factors e = 2...b - 1
      repeat {
        g <- d
        g <- g * e         # compute product of d and e
        n <- n + 1         # count this operation
        g <- g - b         # check if product equals b
        if (g == 0) {
          f <- 0           # set flag to 0 if b is not prime
        }
        e <- e + 1         # increment inner factor
        g <- e
        g <- g - b
        if(g == 0) break   # break inner loop if e == b
      }
      d <- d + 1           # increment outer factor
      g <- d
      g <- g - b
      if(g == 0) break     # break outer loop if d == b
    }
    
    if (f == 0) {          # if b is not prime
      h <- h + 1           # increment not prime counter
    }
    
    g <- b
    g <- g - c
    if (g == 0)            # if b equals c
      return(n)            # return number of operations
    b <- b + 17            # increment b by 17 for next iteration
  }
}

That is for the chosen input of b = 99 the algorithm multiplies each d ∈ {2, …, 98} with each e ∈ {2, …, 98} and checks if this multiplication yields b. If so, we set a flag. The code could test more than one value (in fact it would test all values between b and c with a step size of 17) and eventually register h contains the number of non prime numbers within this range.

For the first part, however, we need to determine how many multiplications there are. As we are testing all possible combinations from 2 to b − 1 we get (b − 1 − 1) ⋅ (b − 1 − 1) multiplications.

get_nr_of_mult <- function(b) {
  (b - 2) ^ 2
}
get_nr_of_mult(99)
## [1] 9409

2.2 Part 2

2.2.1 Description

— Part Two —

Now, it’s time to fix the problem.

The debug mode switch is wired directly to register a. You flip the switch, which makes register a now start at 1 when the program is executed.

Immediately, the coprocessor begins to overheat. Whoever wrote this program obviously didn’t choose a very efficient implementation. You’ll need to optimize the program if it has any hope of completing before Santa needs that printer working.

The coprocessor’s ultimate goal is to determine the final value left in register h once the program completes. Technically, if it had that… it wouldn’t even need to run the program.

After setting register a to 1, if the program were to run to completion, what value would be left in register h?

2.2.2 Solution

With the code demystified, we can conclude that setting a = 1 yields different starting values. This time we loop through b = 109900 until c = 126900 by steps of 17. Furthermore, this time we are not interested in the number of multiplications, but in register h, which counts the number of non primes within this range.

get_non_primes <- function(b, c, step) {
  nrs <- seq(b, c, step)
  sum(!isPrime(nrs))
}
get_non_primes(109900L, 126900L, 17L)
## [1] 913