This was my first year trying Advent of Code. I originally intended to do all the solutions in both R and python. However, as they got more complicated, I decided to prioritize R, sticking with the tidyverse as much as possible. I put all of the RMarkdown and Jupyter notebooks in a public git repo.
Overall, it was a great learning experience. I didn't finish all of the days, both because of travel and because of difficulties working with 3D matrices in tidyverse.
I think the best part was finally have an excuse to work with classes in R. Obviously, R's approach is very different from python, but you can still get a lot of the same functionality.
For instance, on Day 4, you have to keep track of a collection of Bingo game boards, and determine when a board obtains a Bingo. I created a new class called "Board" along with methods for marking a board when a number was drawn, checking a board to see if it had a Bingo, and scoring a board according to the AoC rules.
Here's the code I ended up using:
# read in list of draws
require(tidyverse)
draws <- read_csv('data/aoc-4_draws.txt', col_names=FALSE)
draws <- draws %>%
pivot_longer(everything(), names_to = 'Draw', values_to = 'Number')
# read in boards data
boards <- read_delim('data/aoc-4_boards.txt', delim=" ",
col_names=FALSE)
n_board <- nrow(boards)/5
boards$board <- rep(1:n_board, each=5)
boards <- boards %>%
mutate_if(is.character, as.numeric)
# create list to store all of the boards
B <- vector(mode = 'list', length = n_board)
hits <- matrix(rep(0,25), nrow=5)
for (i in 1:n_board) {
b <- boards %>%
filter(board == i) %>%
select(-board)
bd <- list('bid' = i, 'board' = b, 'hits' = hits, 'score' = 0)
class(bd) <- 'Board'
B[[i]] <- bd
}
# prep methods for class Board
mark <- function(object, ...) {
UseMethod('mark')
}
check <- function(object) {
UseMethod('check')
}
score <- function(object, ...) {
UseMethod('score')
}
draw <- function(object, ...) {
UseMethod('draw')
}
# define methods of class Board
mark.Board <- function(object, num) {
# check for the given number and add it to hits if it exists
found <- FALSE
row <- NULL
col <- NULL
for (i in 1:5) {
if (num %in% object$board[i,]) {
row <- i
for (j in 1:5) {
if (num == object$board[i,j]) {
col <- j
found <- TRUE
}
}
}
}
if (found) {
object$hits[row,col] <- 1
}
return(object)
}
check.Board <- function(object) {
# Does Board have a bingo?
bingo <- FALSE
# check rows
if (any(rowSums(object$hits) == 5)) {
bingo <- TRUE
} else if (any(colSums(object$hits) == 5)) {
bingo <- TRUE
}
return(bingo)
}
score.Board <- function(object, num) {
# sum unmarked numbers and multiply by last pulled number
object$score <- num * sum(object$board[object$hits == 0])
return(object)
}
draw.Board <- function(object, num) {
# update a Board based on the given draw number
object <- mark(object, num)
bingo <- check(object)
if (bingo) {
object <- score(object, num)
}
return(object)
}
# Loop through draws until a board wins
all_boards <- B
winner <- FALSE
for (ii in 1:nrow(draws)) {
pull <- draws[ii,2]
for (bi in 1:n_board) {
bd <- draw(all_boards[[bi]], pull)
if (bd$score > 0) {
print(str_c('Board ', bd$bid, ' won! Pull ', pull, ', score = ', bd$score))
winner <- TRUE
}
all_boards[[bi]] <- bd
}
if (winner) break
}