Advent of Code 2021

knitr::opts_chunk$set(echo = TRUE)
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──

## ✔ ggplot2 3.3.5     ✔ purrr   0.3.4
## ✔ tibble  3.1.6     ✔ dplyr   1.0.7
## ✔ tidyr   1.1.4     ✔ stringr 1.4.0
## ✔ readr   2.1.1     ✔ forcats 0.5.1

## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
walk(list.files(here::here("R"), full.names = TRUE), source)

Here’s my work on Advent of Code 2021.

Day 1

Part 1

d1_sonar <- scan(here::here("data/day01.txt"))
sum(diff(d1_sonar) > 0)
## [1] 1475

Part 2

d1_idx <- seq_len(length(d1_sonar) - 3)
(
  (d1_sonar[d1_idx] + d1_sonar[d1_idx + 1] + d1_sonar[d1_idx + 2]) < 
    (d1_sonar[d1_idx + 1] + d1_sonar[d1_idx + 2] + d1_sonar[d1_idx + 3])
) %>% 
  sum()
## [1] 1516

Day 2

Part 1

d2_sub <- read_delim(
  "data/day02.txt", 
  delim = " ", 
  col_names = c("direction", "value"), 
  col_types = cols(col_character(), col_integer())
)

d2_sub %>% 
  mutate(
    axis = if_else(
      direction %in% c("forward", "back"), 
      "horizontal", 
      "vertical"
    ), 
    sign = if_else(
      direction %in% c("forward", "down"), 
      1L, 
      -1L
    )
  ) %>% 
  group_by(axis) %>% 
  summarise(position = sum(sign * value), .groups = "drop") %>% 
  summarise(total = prod(position))
## # A tibble: 1 × 1
##     total
##     <dbl>
## 1 1636725

Part 2

NB. Turns out there are no back steps in the input, which is why the instructions make sense.

d2_sub %>% 
  count(direction)
## # A tibble: 3 × 2
##   direction     n
##   <chr>     <int>
## 1 down        373
## 2 forward     417
## 3 up          210
d2_sub %>% 
  mutate(
    aim = cumsum(
      ((direction == "down") * value) - ((direction == "up") * value)
    ), 
    forward = (direction == "forward")
  ) %>% 
  mutate(
    horizontal = cumsum(forward * value), 
    depth = cumsum(forward * aim * value)
  ) %>% 
  slice_tail(n = 1) %>% 
  transmute(answer = horizontal * depth)
## # A tibble: 1 × 1
##       answer
##        <int>
## 1 1872757425

Day 3

Part 1

d3_input <- readLines(here::here("data/day03.txt")) %>% 
  str_split("") %>% 
  map(as.integer) %>% 
  reduce(rbind)
  
d3_sums <- (colSums(d3_input) > (nrow(d3_input) / 2))
d3_get_prod <- function(x, y) {
  map(
  list(x, y), 
  ~ .x %>% 
    as.integer() %>% 
    as.character() %>% 
    str_c(collapse = "") %>% 
    strtoi(base = 2L)
  ) %>% 
  reduce(prod)
}
d3_get_prod(d3_sums, !d3_sums)
## [1] 2972336

Part 2

d3_oxygen <- d3_input
for (j in seq_len(ncol(d3_input))) {
  most_common <- as.integer(sum(d3_oxygen[, j]) >= (nrow(d3_oxygen) / 2))
  d3_oxygen <- d3_oxygen[d3_oxygen[, j] == most_common, ]
  if (is.null(dim(d3_oxygen))) {
    break
  }
}

d3_co2 <- d3_input
for (j in seq_len(ncol(d3_input))) {
  least_common <- as.integer(!(sum(d3_co2[, j]) >= (nrow(d3_co2) / 2)))
  d3_co2 <- d3_co2[d3_co2[, j] == least_common, ]
  if (is.null(dim(d3_co2))) {
    break
  }
}
d3_get_prod(d3_oxygen, d3_co2)
## [1] 3368358

Day 4

Part 1

d4_input <- readLines(here::here("data/day04.txt"))

d4_order <- d4_input[[1]] %>% 
  str_split(",") %>% 
  pluck(1) %>% 
  as.integer()

d4_numbers <- d4_input[map(0:99, ~ 3:7 + (6 * .x)) %>% unlist() %>% sort()] %>%
  str_trim() %>% 
  str_split(" +") %>% 
  unlist() %>% 
  as.integer()

d4_cards <- d4_numbers %>% 
  array(dim = c(5, 5, 100))

d4_position <- match(d4_numbers, d4_order) %>% 
  array(dim = c(5, 5, 100))

d4_rows <- d4_position %>% 
  apply(MARGIN = c(1, 3), FUN = max) %>% 
  apply(MARGIN = 2, FUN = min)
d4_cols <- d4_position %>% 
  apply(MARGIN = c(2, 3), FUN = max) %>% 
  apply(MARGIN = 2, FUN = min)
d4_win_times <- pmin(d4_rows, d4_cols)

d4_scores <- (
  (
  d4_cards * (
    d4_position > (
      rep(d4_win_times, each = 25) %>% 
        array(dim = c(5, 5, 100))
    )
  )
) %>% 
  apply(MARGIN = 3, sum)
) * (d4_order[d4_win_times])

d4_scores[which.min(d4_win_times)]
## [1] 2496

Part 2

d4_scores[which.max(d4_win_times)]
## [1] 25925

Day 5

Part 1

d5_input <- tibble(
  raw = readLines(here::here("data/day05.txt"))
) %>% 
  separate(
    raw, 
    into = c("x1", "y1", "x2", "y2"), 
    convert = TRUE
  ) %>% 
  rowid_to_column() %>% 
  mutate(
    direction = case_when(
      x1 == x2 ~ "vertical", 
      y1 == y2 ~ "horizontal", 
      TRUE     ~ "other"
    )
  ) %>% 
  mutate(
    all_pts = pmap(
      ., 
      ~ str_c(seq(..2, ..4), ",", seq(..3, ..5))
    )
  )
d5_input %>% 
  filter(direction != "other") %>% 
  select(rowid, all_pts) %>% 
  unnest(all_pts) %>% 
  group_by(all_pts) %>% 
  summarise(pt_count = n_distinct(rowid)) %>% 
  count(pt_count > 1)
## # A tibble: 2 × 2
##   `pt_count > 1`      n
##   <lgl>           <int>
## 1 FALSE          101541
## 2 TRUE             5147

Part 2

d5_input %>% 
  select(rowid, all_pts) %>% 
  unnest(all_pts) %>% 
  group_by(all_pts) %>% 
  summarise(pt_count = n_distinct(rowid)) %>% 
  count(pt_count > 1)
## # A tibble: 2 × 2
##   `pt_count > 1`      n
##   <lgl>           <int>
## 1 FALSE          152072
## 2 TRUE            16925

Day 6

Part 1

Brute force worked fine for Part 1 but then died on Part 2. Left here for posterity.

d6_fish <- scan(here::here("data/day06.txt"), what = integer(), sep = ",")

d6_increment_fish <- function(timer) {
  if (timer == 0L) {
    return(c(6L, 8L))
  } else {
    return(timer - 1L)
  }
}
d6_sim_fish <- function(timers, N) {
  res <- timers
  for (n in seq_len(N)) {
    res <- map(res, d6_increment_fish) %>% 
      unlist()
  }
  res
}
d6_sim_fish(d6_fish, N = 80L) %>% 
  length()

Need to switch to keeping track of counts of fish.

d6_fish <- scan(here::here("data/day06.txt"), what = double(), sep = ",")
# There are no zero values in the initial states, so need to add that to the
# front
d6_counts <- c(0, tabulate(d6_fish, 8))
d6_sim_fish <- function(counts, N) {
  res <- counts
  for (n in seq_len(N)) {
    res <- c(res[2:9], res[1]) + c(rep(0, 6), res[1], rep(0, 2))
  }
  res
}
d6_sim_fish(d6_counts, N = 80) %>% 
  sum()
## [1] 395627

Part 2

options(scipen = 999)
d6_sim_fish(d6_counts, N = 256) %>% 
  sum()
## [1] 1767323539209

Day 7

Part 1

d7_input <- scan(here::here("data/day07.txt"), sep = ",")
sum(abs(d7_input - median(d7_input)))
## [1] 356992

Part 2

d7_increase_fuel_cost <- function(n) {
  (n * (n + 1)) / 2
}
d7_all_poss_dist <- seq(min(d7_input), max(d7_input))
d7_fuel_cost <- map_dbl(
  d7_all_poss_dist, 
  ~ sum(d7_increase_fuel_cost(abs(d7_input - .x)))
)
d7_fuel_cost[which.min(d7_fuel_cost)]
## [1] 101268110

Day 8

Part 1

d8_input <- read_delim(
  here::here("data/day08.txt"),
  delim = " | ",
  col_names = c("signals", "output"),
  col_types = cols(.default = col_character())
) %>%
  mutate(across(.fns = str_trim))

d8_input %>% 
  mutate(
    counts = map(
      output, 
      ~ str_split(.x, " ") %>% 
        pluck(1) %>% 
        map_int(nchar)
    )
  ) %>% 
  mutate(
    total = map_int(
      counts, 
      ~ sum(.x %in% c(2L, 4L, 3L, 7L))
    )
  ) %>% 
  summarise(answer = sum(total))
## # A tibble: 1 × 1
##   answer
##    <int>
## 1    294

Part 2

Easier to wrap the required code in a function.

parse_display
## function (signals, output) 
## {
##     sig_len <- lengths(signals)
##     check_setdiff <- function(signals, digit, len = 1) {
##         map_int(signals, ~length(setdiff(signals[[digit]], .x))) == 
##             1
##     }
##     d1 <- which(sig_len == 2)
##     d4 <- which(sig_len == 4)
##     d7 <- which(sig_len == 3)
##     d8 <- which(sig_len == 7)
##     d6 <- which(check_setdiff(signals, d1) & sig_len == 6)
##     d0 <- which(check_setdiff(signals, d4) & sig_len == 6) %>% 
##         setdiff(d6)
##     d9 <- which(sig_len == 6) %>% setdiff(d6) %>% setdiff(d0)
##     d5 <- which(check_setdiff(signals, d6) & sig_len == 5)
##     d3 <- which(check_setdiff(signals, d9) & sig_len == 5) %>% 
##         setdiff(d5)
##     d2 <- which(sig_len == 5) %>% setdiff(d5) %>% setdiff(d3)
##     output %>% match(signals[c(d0, d1, d2, d3, d4, d5, d6, d7, 
##         d8, d9)] %>% map(sort)) %>% {
##         . - 1
##     } %>% str_c(collapse = "") %>% as.integer()
## }
d8_input %>%
  mutate(
    across(
      .fns = ~ str_split(.x, " ") %>%
        map(str_split, "") %>%
        map(map, sort)
    )
  ) %>%
  mutate(parsed = map2_int(signals, output, parse_display)) %>%
  summarise(answer = sum(parsed))
## # A tibble: 1 × 1
##   answer
##    <int>
## 1 973292