Day 16

Advent of Code: Worked Solutions

About
Date

December 16, 2023

Setup

# Libraries
library(tidyverse)

# Read input from file
input <- read_lines("../input/day16.txt", skip_empty_rows = TRUE)

Part 1

Convert text input to a character matrix:

mtx <- input |> 
  str_split("") |> 
  map(~ matrix(.x, nrow = 1)) |> 
  reduce(rbind)

Define a function that converts a direction, direction index, and sublocation along that direction into standard row/column form, then into a single ID value:

mtx_id <- function(dir, vec_idx, loc_idx) {
  row <- case_match(dir, c('W', 'E') ~ vec_idx, c('N', 'S') ~ loc_idx)
  col <- case_match(dir, c('W', 'E') ~ loc_idx, c('N', 'S') ~ vec_idx)
  
  (row - 1) * ncol(mtx) + col
}

Define a function that tracks the beam through the matrix and returns the IDs of the elements visited:

beam <- function(dir, vec_idx = 1, start_idx, visited) {
  
  if (!between(start_idx, 1, nrow(mtx))) return(visited)
  
  # Initialize
  vec <- if (dir %in% c('W', 'E')) mtx[vec_idx,] else mtx[,vec_idx]

  end_idx      <- case_match(dir, c('E', 'S') ~ length(vec), c('N', 'W') ~ 1)
  chr_split    <- case_match(dir, c('W', 'E') ~ '|',         c('N', 'S') ~ '-')
  chr_turn_neg <- case_match(dir, c('N', 'W') ~ '\\',        c('S', 'E') ~ '/')
  chr_turn_pos <- case_match(dir, c('N', 'W') ~ '/',         c('S', 'E') ~ '\\')
  dir_split_1  <- case_match(dir, c('W', 'E') ~ 'N',         c('N', 'S') ~ 'W')
  dir_split_2  <- case_match(dir, c('W', 'E') ~ 'S',         c('N', 'S') ~ 'E')
  dir_turn_neg <- case_match(dir, c('N', 'S') ~ 'W',         c('W', 'E') ~ 'N')
  dir_turn_pos <- case_match(dir, c('N', 'S') ~ 'E',         c('W', 'E') ~ 'S')
  
  for (i in start_idx:end_idx) {
    
    id <- mtx_id(dir, vec_idx, i)
    
    if (id %in% visited[[dir]])
      return(visited)
    else
      visited[[dir]] <- c(visited[[dir]], id)
    
    if (vec[[i]] == chr_split) {
      visited <- beam(dir_split_1, i, vec_idx - 1, visited)
      visited <- beam(dir_split_2, i, vec_idx + 1, visited)
      break
    }
    else if (vec[[i]] == chr_turn_neg) {
      visited <- beam(dir_turn_neg, i, vec_idx - 1, visited)
      break
    }
    else if (vec[[i]] == chr_turn_pos) {
      visited <- beam(dir_turn_pos, i, vec_idx + 1, visited)
      break
    }
  }
  
  visited
}

Starting from upper left, loop through the matrix and count the number of energized tiles:

init_visited <- map(set_names(c("N", "S", "W", "E")), ~ c())

beam("E", vec_idx = 1, start_idx = 1, visited = init_visited) |> 
  unlist() |> 
  unique() |> 
  length()
[1] 7046

Part 2

If the beam can enter from any edge, find the maximum number of energizable tiles:

energize_from <- function(dir, vec_idx, start_idx) {
  beam(dir, vec_idx, start_idx, init_visited) |> 
    unlist() |> 
    unique() |> 
    length()
}

expand_grid(dir = c('N', 'S', 'W', 'E'), vec = 1:nrow(mtx)) |> 
  mutate(start = case_match(dir, c('S', 'E') ~ 1, c('N', 'W') ~ nrow(mtx))) |> 
  pmap_dbl(\(dir, vec, start) energize_from(dir, vec, start)) |> 
  max()
[1] 7313