Task 19

Thorn Thaler - <

2025-01-10

1 Setup

1.1 Libraries

library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
library(stringi)
library(knitr)
library(cli)
library(bit64)
library(igraph)

1.2 Retrieve Data from AoC

session_cookie <- set_cookies(session = keyring::key_get("AoC-GitHub-Cookie"))
base_url <- paste0("https://adventofcode.com/2024/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)
  }
  list(
    towels = str_split(text_block[1L], ", ?") %>% 
      extract2(1L),
    patterns = text_block[-1L] %>% 
      keep(nzchar)
  )
}

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

2 Puzzle Day 19

2.1 Part 1

2.1.1 Description

— Day 19: Linen Layout —

Today, The Historians take you up to the hot springs on Gear Island! Very suspiciously, absolutely nothing goes wrong as they begin their careful search of the vast field of helixes.

Could this finally be your chance to visit the onsen next door? Only one way to find out.

After a brief conversation with the reception staff at the onsen front desk, you discover that you don’t have the right kind of money to pay the admission fee. However, before you can leave, the staff get your attention. Apparently, they’ve heard about how you helped at the hot springs, and they’re willing to make a deal: if you can simply help them arrange their towels, they’ll let you in for free!

Every towel at this onsen is marked with a pattern of colored stripes. There are only a few patterns, but for any particular pattern, the staff can get you as many towels with that pattern as you need. Each stripe can be white (w), blue (u), black (b), red (r), or green (g). So, a towel with the pattern ggr would have a green stripe, a green stripe, and then a red stripe, in that order. (You can’t reverse a pattern by flipping a towel upside-down, as that would cause the onsen logo to face the wrong way.)

The Official Onsen Branding Expert has produced a list of designs - each a long sequence of stripe colors - that they would like to be able to display. You can use any towels you want, but all of the towels’ stripes must exactly match the desired design. So, to display the design rgrgr, you could use two rg towels and then an r towel, an rgr towel and then a gr towel, or even a single massive rgrgr towel (assuming such towel patterns were actually available).

To start, collect together all of the available towel patterns and the list of desired designs (your puzzle input). For example:

r, wr, b, g, bwu, rb, gb, br

brwrr
bggr
gbbr
rrbgbr
ubwu
bwurrg
brgr
bbrgwb

The first line indicates the available towel patterns; in this example, the onsen has unlimited towels with a single red stripe (r), unlimited towels with a white stripe and then a red stripe (wr), and so on.

After the blank line, the remaining lines each describe a design the onsen would like to be able to display. In this example, the first design (brwrr) indicates that the onsen would like to be able to display a black stripe, a red stripe, a white stripe, and then two red stripes, in that order.

Not all designs will be possible with the available towels. In the above example, the designs are possible or impossible as follows:

  • brwrr can be made with a br towel, then a wr towel, and then finally an r towel.
  • bggr can be made with a b towel, two g towels, and then an r towel.
  • gbbr can be made with a gb towel and then a br towel.
  • rrbgbr can be made with r, rb, g, and br.
  • ubwu is impossible.
  • bwurrg can be made with bwu, r, r, and g.
  • brgr can be made with br, g, and r.
  • bbrgwb is impossible.

In this example, 6 of the eight designs are possible with the available towel patterns.

To get into the onsen as soon as possible, consult your list of towel patterns and desired designs carefully. How many designs are possible?

2.1.2 Solution

Looking at the available towels we observe a couple of things:

  1. All letters except b are available as single letters:

    str_subset(puzzle_data$towels, "^.$") %>% 
      sort()
    ## [1] "g" "r" "u" "w"
  2. All digrams with b are evailable except bg:

    str_subset(puzzle_data$towels, ".b|b.") %>% 
      str_subset("^..$") %>% 
      sort()
    ## [1] "bb" "bg" "br" "bu" "bw" "rb" "ub" "wb"
  3. A gb pattern not occuring at the end of the string is never a problem, as it can be split up, e.g. ugbbbu => u g bb bu (single letters are all available and so are all b digrams except bg).

  4. Thus, only gb patterns at the end pose a potential problem, unless there is a special (longer) towel with gb in the end:

    puzzle_data$towels %>% 
      str_subset("gb$")
    ##  [1] "uugb"     "bggb"     "uuwrggb"  "ggbgb"    "bbgb"     "rbgb"    
    ##  [7] "wgb"      "urwgrgb"  "rgwgb"    "uuubwrgb" "urgbgb"   "gwwbgb"  
    ## [13] "bugb"     "bgb"      "gwgb"     "ggruwgb"  "ugb"      "grurrgb" 
    ## [19] "ubwrugb"  "uguwgb"   "rrwwugb"  "rgb"
  5. However, if there is a special token fitting the end of a pattern, we must not end with gb without a matching token for the same reasons.

This allows to construct an algorithm which iteratively remove patterns which end with gb without a matching towel.

get_solvable_patterns <- function(data) {
  life_savers <- data$towels %>% 
    str_subset("gb$")
  has_token <- paste0("(", paste(life_savers, collapse = "|"), ")$")
  keep(data$patterns, function(pattern) {
    res <- TRUE
    while (res && str_detect(pattern, "gb$")) {
      if (str_detect(pattern, has_token)) {
        # Remove the matched token from the end
        pattern <- str_remove(pattern, has_token)
      } else {
        # No token can save this pattern
        res <- FALSE
      }
    }
    res
  }) 
}

valid_patterns <- get_solvable_patterns(puzzle_data)
length(valid_patterns)
## [1] 290

2.2 Part 2

2.2.1 Description

— Part Two —

The staff don’t really like some of the towel arrangements you came up with. To avoid an endless cycle of towel rearrangement, maybe you should just give them every possible option.

Here are all of the different ways the above example’s designs can be made:

brwrr can be made in two different ways: b, r, wr, r or br, wr, r.

bggr can only be made with b, g, g, and r.

gbbr can be made 4 different ways:

  • g, b, b, r
  • g, b, br
  • gb, b, r
  • gb, br

rrbgbr can be made 6 different ways:

  • r, r, b, g, b, r
  • r, r, b, g, br
  • r, r, b, gb, r
  • r, rb, g, b, r
  • r, rb, g, br
  • r, rb, gb, r

bwurrg can only be made with bwu, r, r, and g.

brgr can be made in two different ways: b, r, g, r or br, g, r.

ubwu and bbrgwb are still impossible.

Adding up all of the ways the towels in this example could be arranged into the desired designs yields 16 (2 + 1 + 4 + 6 + 1 + 2).

They’ll let you into the onsen as soon as you have the list. What do you get if you add up the number of different ways you could make each design?

2.2.2 Solution

We solve this part by means of dynamic programming.

  1. Inititalize a vector (ways_to_arrange) with the same length as the pattern. Each entry xi in this vector counts the number of arrangements for a substring of length i. The final reuslt will eventually be found at position xn.
  2. Iterate over all character positions i in the pattern and form all substrings up to the position i. For instance rrbg => [r], [rr, r], [rrb, rb, b], [rrbg, rbg, bg, g].
  3. At each iteration check if the sub string represents an existing towel.
  4. If this is the case, there are 2 possibilities:
    1. It is the complete substring from the beginning. That is the first part of the pattern. In this case set xi to 1. This represents the fact that we can match this pattern in one way simply by using the fitting towel.
    2. If we look at a substring not starting at the beginning, we simply add the xi − 1 to xi. The idea is that with this new towel we add new possibilities to the arrangement which correspond to the number of arrangements which we had before adding this very towel.

For instance consider the example towels r, wr, b, g, bwu, rb, gb, br and the pattern rrbgbr. The algorithm would work like this:

  1. We initialize x to [0,0,0,0,0,0]
  2. i = 1:
    • The first (and only) substring is r. There is a towel for r: x = [1,0,0,0,0,0]
  3. i = 2:
    • First substring is rr. There is no matching towel.
    • Second substring is r. There is a matching towel. We add the number of ways to arrange a pattern of length 1 to x2: x = [1,1,0,0,0,0,0]
  4. i = 3:
    • First substring is rrb. There is no matching towel.
    • Second substring is rb. There is a matching towel, use the number of arrangement for a string of length 1: x = [1,1,1,0,0,0,0].
    • Third substring is b. Again there is a matching towel, add the number of arrangements for a string of length 2: x = [1,1,2,0,0,0,0].
  5. i = 4 (N.B. I skip irrelevant iterations, especially looking further back than maxi|ti| positions, where ti is towel i does not make sense, as there cannot be a matching towel of this length):
    • rbg does not match.
    • bg does not match.
    • g matches: x = [1,1,2,2,0,0].
  6. i = 5:
    • bgb does not match.
    • gb matches: x = [1,1,2,2,2,0].
    • b matches: x = [1,1,2,2,4,0].
  7. i = 6:
    • gbr does not match.
    • br matches: x = [1,1,2,2,4,2].
    • r matches: x = [1,1,2,2,4,6].
  8. Final result is 6.
count_arrangements <- function(data) {
  max_len <- data$towels %>% 
    nchar() %>% 
    max()
  count <- function(pattern, towels) {
    idx <- seq(1L, nchar(pattern))
    ways_to_arrange <- rep(as.integer64(0L), nchar(pattern))
    for (end_pos in seq(1L, nchar(pattern))) {
      for (start_pos in seq(max(1L, end_pos - max_len + 1L), end_pos)) {
        sub_string <- substr(pattern, start_pos, end_pos)
        if (sub_string %in% towels) {
          if (start_pos == 1L) {
            ways_to_arrange[end_pos] <- 1L
          } else {
            ways_to_arrange[end_pos] <- ways_to_arrange[end_pos] + 
              ways_to_arrange[start_pos - 1L]
          }
        }
      }
    }
    tail(ways_to_arrange, 1L)
  }
  map(data$patterns, count, data$towels) %>% 
    do.call(c, .) %>% 
    sum()
}
count_arrangements(puzzle_data)
## integer64
## [1] 712058625427487