Every year, the great team at Advent of Code lead by ericwastl develops an “Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.”

Well, I like R, so in this post I will provide my solutions to the puzzles each day. I plan on using this post to learn and teach and hopefully I am able to stay up-to-date!

A few notes as we get started:

On to the solutions…

Code supporting all solutions

library(tidyverse)
library(furrr)
plan(multicore)

Day 17

Part 1

min_x <- 20; max_x <- 30; min_y <- -10; max_y <- -5

start <- tibble(x = 0, y = 0)

move_one <- function(start, initial_x, initial_y){
  end <- start %>%  mutate(x = x+initial_x, y = y + initial_y)
  return(end)
}

check_trajectory_part_1 <- function(x_init = 7, y_init = -1){

initial_velocity_x <- x_init
final_velocity_x <- initial_velocity_x-max_x
initial_velocity_y <- y_init

x_velocity_vec <- initial_velocity_x:final_velocity_x
x_velocity_vec[which(x_velocity_vec<0)]=0
y_velocity_vec <- (initial_velocity_y:-200)[1:length(x_velocity_vec)]

trajectory <-
  accumulate2(.x = x_velocity_vec, .y = y_velocity_vec, .f = move_one, .init = start) %>%
  bind_rows()

out<-
  trajectory %>%
  mutate(all_in = x %in% min_x:max_x + y %in% max_y:min_y == 2) %>%
  mutate(one_in = sum(all_in)) %>% filter(one_in !=0) %>%
  arrange(desc(y)) %>% slice(2) %>%
  mutate(initial_x = x_init, initial_y = y_init)
return(out)
}

x_poss <- map_dbl(1:max_x, ~choose(.x,2))
x_ans <- c(match(x_poss[which(x_poss %in% min_x:max_x)], x_poss)) - 1

y_ans <- abs(min_y)-1

possible_x_vec <- x_ans[1]:max_x
possible_y_vec <- y_ans:min_y
possibilities <- crossing(possible_x_vec, possible_y_vec) 

map2_dfr(
  .x = possibilities$possible_x_vec,
  .y = possibilities$possible_y_vec,
  .f = ~ check_trajectory_part_1(x_init = .x, y_init = .y)
) %>% nrow()

Part 2

min_x <- 20; max_x <- 30; min_y <- -10; max_y <- -5

check_trajectory <- function(x_init = 7, y_init = -1){

  initial_velocity_x <- x_init
  final_velocity_x <- initial_velocity_x-max_x
  initial_velocity_y <- y_init

  x_velocity_vec <- initial_velocity_x:final_velocity_x
  x_velocity_vec[which(x_velocity_vec<0)]=0
  y_velocity_vec <- (initial_velocity_y:-200)[1:length(x_velocity_vec)]

  if(sum(cumsum(x_velocity_vec) %in% min_x:max_x) == 0){
    out <- tibble(it_works = FALSE, x_init = x_init, y_init =y_init)
    return(out)
  }
  if(sum(cumsum(y_velocity_vec) %in% min_y:max_y) == 0){
    out <- tibble(it_works = FALSE, x_init = x_init, y_init =y_init)
    return(out)
  }
  if(sum((cumsum(x_velocity_vec) %in% min_x:max_x)[which(cumsum(y_velocity_vec) %in% min_y:max_y == TRUE)])>0){
    out <- tibble(it_works = TRUE, x_init = x_init, y_init =y_init)
    return(out)
  }
  out <- tibble(it_works = FALSE, x_init = x_init, y_init =y_init)
  return(out)
}

possible_x_vec <- x_ans[1]:max_x
possible_y_vec <- y_ans:min_y

possibilities <- crossing(possible_x_vec, possible_y_vec) 

we_know_these_exist <- crossing(possible_x_vec = min_x:max_x, possible_y_vec = min_y:max_y)

possibilities <- possibilities %>% anti_join(we_know_these_exist) 

answers <-
  map2(.x = possibilities$possible_x_vec,
       .y = possibilities$possible_y_vec,
       .f = ~ check_trajectory(x_init = .x, y_init = .y))

answers %>% bind_rows(.id = "id") %>% filter(it_works) %>%  nrow() +
  we_know_these_exist %>% nrow()

Day 16

Part 1

code <-
  tibble(lines = read_lines("02_data/day_16_code.txt")) %>% 
  separate(col = lines, into = c("letter","bin"))

rest <-
  tibble(letter = read_lines("02_data/day_16.txt")) %>%
  separate_rows(letter, sep = "") %>% filter(letter != "") %>%
  left_join(code) %>%
  summarise(string = str_c(bin, collapse = "")) %>% pull(string)

parse <- function(rest = rest, LV = LV, PV = PV, TID = TID){
  if(nchar(rest) < 6){
    out <- list(LV = LV, PV = PV, TID = TID)
    return(out)
  }
  first_3 <- str_pad(str_sub(rest,1,3),width = 4, side = "left", pad = 0)
  second_3 <- str_pad(str_sub(rest,4,6),width = 4, side = "left", pad = 0)
  packet_version <- code[code$bin==first_3,]$letter
  type_id <- code[code$bin==second_3,]$letter
  rest <- str_sub(rest, 7,-1)
  PV <- append(PV, packet_version)  
  TID <- append(TID, type_id)
  if(type_id!=4){
    route <- str_sub(rest,1,1)
    rest <- str_sub(rest,2,-1)
    if(route ==1){
      num_iter <- strtoi(str_sub(rest,1,11), base = 2)
      rest <- str_sub(rest,12,-1)
      for (i in 1:num_iter) {
        return(parse(rest = rest, LV = LV, PV = PV, TID = TID))
      }
    } else if(route==0){
      lenths_string <- strtoi(str_sub(rest,1,15),base = 2)
      rest <- str_sub(rest,16,-1)
      return(parse(rest = rest, LV = LV, PV = PV, TID = TID))
    }
  } else if(type_id==4){
    id_nums <- strsplit(rest,"")[[1]][rep(c(T,F,F,F,F), nchar(rest)/5)]
    num_iter <- which(as.numeric(id_nums)==0)[1]
    temp_list <- vector()
    for (i in 1:num_iter) {
      temp <- str_sub(rest, 2, 5)
      rest <- str_sub(rest, 6, -1)
      temp_list <- str_c(temp_list,temp)
    }
    literal_value <- strtoi(temp_list,base = 2)
    LV <- append(LV, literal_value)
    return(parse(rest = rest, LV = LV, PV = PV , TID = TID))
  }
  out <- list(LV = LV, PV = PV, TID = TID)
  return(out)
}

final <- parse(rest = rest, LV = NULL, PV = NULL, TID = NULL)

final$PV %>% as.integer() %>% sum()

Part 2 Pending

Day 15

Part 1

My first instinct was to make this an integer program. I wasn’t able to execute it. Admittedly, I had to get some ideas from others. Once started, I was able to do both parts.

library(igraph)

file_path <- "02_data/day_15.txt"

width <- read_lines(file = file_path)[1] %>% nchar()

l <-  matrix(1:(width * width), byrow = T, ncol = width)

w <- tibble(weights = read_lines(file = file_path)) %>%
  mutate(id = row_number()) %>%
  separate_rows(weights, sep = "", convert = T) %>%
  filter(str_detect(weights, "")) %>%
  pull(weights) %>% matrix(nrow = width, byrow = T)

my_graph <-
  tibble(x = map(.x = 0:(nrow(l) - 1), ~ l[1, ][-c(nrow(l))] + (.x * nrow(l))) %>% unlist(),
         y = map(.x = 0:(nrow(l) - 1), ~ l[1, ][-1] + (.x * nrow(l))) %>% unlist()) %>%
  mutate(weight = as.vector(t(w[, -1]))) %>%
  bind_rows(
    tibble(x = map(.x = 0:(ncol(l) - 1), ~ l[1, ][-1] + (.x * ncol(l))) %>% unlist(),
           y = map(.x = 0:(ncol(l) - 1), ~ l[1, ][-c(ncol(l))] + (.x * ncol(l))) %>% unlist()
           ) %>% 
      mutate(weight = as.vector(t(w[, -nrow(w)])))) %>%
  bind_rows(
    tibble(x = as.vector(l[-c(nrow(l)), ]),
           y = as.vector(l[-1, ])
           ) %>%
      mutate(weight = as.vector(w[-1, ]))) %>%
  bind_rows(
    tibble(x = as.vector(l[-1, ]),
           y = as.vector(l[-c(nrow(l)), ])) %>%
      mutate(weight = as.vector(w[-nrow(w), ]))
    ) 

g2 <- add_edges(make_empty_graph(n = width * width),
                as.matrix(t(my_graph[1:nrow(my_graph), 1:2])),
                weight = my_graph[, 3]$weight
                )

ans <- igraph::shortest_paths(g2, from = 1, to = width * width)

tibble(l = l %>% as.vector(),
       w = w %>% as.vector()) %>%
  filter(l %in% ans$vpath[[1]]) %>%
  summarise(sum = sum(w)) - w[1, 1]

Part 2

create_mat <- 
  function(mat, iter) {if_else(mat + 1 > 9, 1, mat + 1) %>%
    matrix(nrow = width, byrow  = F)}

mats <- 1:9 %>% accumulate(.f = create_mat, .init = w) %>% tail(9)

w <- rbind(
  cbind(w, mats[[1]], mats[[2]], mats[[3]], mats[[4]]),
  cbind(mats[[1]], mats[[2]], mats[[3]], mats[[4]], mats[[5]]),
  cbind(mats[[2]], mats[[3]], mats[[4]], mats[[5]], mats[[6]]),
  cbind(mats[[3]], mats[[4]], mats[[5]], mats[[6]], mats[[7]]),
  cbind(mats[[4]], mats[[5]], mats[[6]], mats[[7]], mats[[8]]
  )
)

l <-  matrix(1:(width * 5 * width * 5), byrow = T, ncol = width * 5)

my_graph <-
  tibble(x = map(.x = 0:(nrow(l) - 1), ~ l[1, ][-c(nrow(l))] + (.x * nrow(l))) %>% unlist(),
         y = map(.x = 0:(nrow(l) - 1), ~ l[1, ][-1] + (.x * nrow(l))) %>% unlist()
         ) %>%
  mutate(weight = as.vector(t(w[, -1]))) %>%
  bind_rows(
    tibble(x = map(.x = 0:(ncol(l) - 1), ~ l[1, ][-1] + (.x * ncol(l))) %>% unlist(),
           y = map(.x = 0:(ncol(l) - 1), ~ l[1, ][-c(ncol(l))] + (.x * ncol(l))) %>% unlist()
           ) %>%
      mutate(weight = as.vector(t(w[, -nrow(w)])))) %>%
  bind_rows(
    tibble(x = as.vector(l[-c(nrow(l)), ]),
           y = as.vector(l[-1, ])
           ) %>%
      mutate(weight = as.vector(w[-1, ]))) %>%
  bind_rows(
    tibble(x = as.vector(l[-1, ]),
           y = as.vector(l[-c(nrow(l)), ])
           ) %>%
      mutate(weight = as.vector(w[-nrow(w), ]))
    )

g2 <-  add_edges(make_empty_graph(n = width * 5 * width * 5),
                 as.matrix(t(my_graph[1:nrow(my_graph), 1:2])),
                 weight = my_graph[, 3]$weight
                 )
ans <- igraph::shortest_paths(g2, from = 1, to = width * 5 * width * 5)

tibble(l = l %>% as.vector(),
       w = w %>% as.vector()) %>%
  filter(l %in% ans$vpath[[1]]) %>%
  summarise(sum = sum(w)) - w[1, 1]

Day 14

Strike 1

As with previous days, tracking the entirety of the growing string works for part 1, but not part 2.

Here I grow the string according to the directions, count up all characters, and find the solution. It works fine for 10 iterations.

pair_insertion_rules <-
  tibble(loc = read_lines(file = "02_data/day_14.txt")) %>% 
  slice(-c(1:2)) %>% 
  separate(loc, into = c("polymer", "insert"))

polymer_template <-
  tibble(polymer = read_lines(file = "02_data/day_14.txt")) %>% slice(1) 

grow_polymer <- function(polymer = polymer_template, iter){
  out <-
    polymer %>%
    separate_rows(polymer, sep = "") %>%
    transmute(polymer = str_c(lag(polymer, 1), polymer)) %>%
    filter(!is.na(polymer)) %>%
    left_join(pair_insertion_rules, by = "polymer") %>%
    transmute(add = replace_na(str_c(
      str_sub(polymer, 1, 1), insert, str_sub(polymer, 2, 2)
    ), str_c(" ", polymer[1]))) %>% 
    mutate(add = str_sub(add, 2, 3)) %>%
    summarise(polymer = str_c(add, collapse = ""))
  return(out)
}

1:10 %>% reduce(.f = grow_polymer, .init = polymer_template) %>% 
  separate_rows(polymer, sep = "") %>% 
  count(polymer) %>% 
  filter(polymer !="") %>% 
  summarise(answer = max(n)-min(n))

Part 1/2 Success

But when time comes to do this for 40 days, everything stalls out around 21 days. However, instead of growing the string, count up all pairs as they happen.

first <-
  polymer_template %>% 
  separate_rows(polymer, sep = "") %>% 
  transmute(polymer = str_c(lag(polymer,1), polymer)) %>% 
  filter(nchar(polymer)==2) %>% 
  count(polymer)

iterate_step <-
  function(first = hold, iter) {
    out <-
      first %>%
      left_join(pair_insertion_rules, by = "polymer") %>%
      mutate(add = replace_na(str_c(
        str_sub(polymer, 1, 1), insert, str_sub(polymer, 2, 2)
      ), str_c(" ", polymer[1]))) %>% 
      separate_rows(add, sep = "") %>%
      mutate(add = str_c(lag(add, 1), add)) %>%
      filter(nchar(add) == 2) %>%
      group_by(add) %>%
      summarise(n = sum(n)) %>%
      select(polymer = add, n)
    return(out)
  }

reduce(1:40, .f = iterate_step, .init = first) %>% 
  separate_rows(polymer, sep = "") %>%
  filter(polymer != "") %>% 
  count(polymer, n) %>% 
  group_by(polymer) %>% 
  summarise(total = ceiling(sum(n*nn)/2)) %>% 
  ungroup() %>% 
  summarise(answer = max(total)-min(total))

Day 13

Part 1/2

Today’s was a lot of fun. After parsing the data, use reduce2 to iteratvly fold the paper. ggplot helps to read the final solution.

data <-
  tibble(loc = read_lines(file = "day_13.txt")) %>%
  filter(!str_detect(loc, "fold")) %>% filter(loc != "") %>%
  separate(col = loc,into = c("x", "y"),sep = ",",convert = T) 

lines <-
  tibble(loc = read_lines(file = "day_13.txt")) %>%
  filter(str_detect(loc, "fold")) %>% filter(loc != "") %>% 
  mutate(loc = str_remove(loc, "fold along ")) %>% 
  separate(loc, c("axis","num"), "=", convert = T) %>% 
  mutate(num = as.double(num))

fold <- function(data, axis, num) {
  out <-
    data %>%
    mutate(x = if_else(x > num & axis == "x", num - (x - num), as.double(x))) %>%
    mutate(y = if_else(y > num & axis == "y", num - (y - num), as.double(y)))
  return(out)
  }

reduce2(.x = lines$axis, .y = lines$num, .f = fold, .init = data) %>% 
  count(x,y)  %>% 
  mutate(y = -1*y) %>% 
  ggplot(aes(x = x, y = y)) +
  geom_tile() + theme_void()

ggplot2::theme_void()
## List of 92
##  $ line                      : list()
##   ..- attr(*, "class")= chr [1:2] "element_blank" "element"
##  $ rect                      : list()
##   ..- attr(*, "class")= chr [1:2] "element_blank" "element"
##  $ text                      :List of 11
##   ..$ family       : chr ""
##   ..$ face         : chr "plain"
##   ..$ colour       : chr "black"
##   ..$ size         : num 11
##   ..$ hjust        : num 0.5
##   ..$ vjust        : num 0.5
##   ..$ angle        : num 0
##   ..$ lineheight   : num 0.9
##   ..$ margin       : 'margin' num [1:4] 0points 0points 0points 0points
##   .. ..- attr(*, "unit")= int 8
##   ..$ debug        : logi FALSE
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ title                     : NULL
##  $ aspect.ratio              : NULL
##  $ axis.title                : list()
##   ..- attr(*, "class")= chr [1:2] "element_blank" "element"
##  $ axis.title.x              : NULL
##  $ axis.title.x.top          : NULL
##  $ axis.title.x.bottom       : NULL
##  $ axis.title.y              : NULL
##  $ axis.title.y.left         : NULL
##  $ axis.title.y.right        : NULL
##  $ axis.text                 : list()
##   ..- attr(*, "class")= chr [1:2] "element_blank" "element"
##  $ axis.text.x               : NULL
##  $ axis.text.x.top           : NULL
##  $ axis.text.x.bottom        : NULL
##  $ axis.text.y               : NULL
##  $ axis.text.y.left          : NULL
##  $ axis.text.y.right         : NULL
##  $ axis.ticks                : NULL
##  $ axis.ticks.x              : NULL
##  $ axis.ticks.x.top          : NULL
##  $ axis.ticks.x.bottom       : NULL
##  $ axis.ticks.y              : NULL
##  $ axis.ticks.y.left         : NULL
##  $ axis.ticks.y.right        : NULL
##  $ axis.ticks.length         : 'simpleUnit' num 0points
##   ..- attr(*, "unit")= int 8
##  $ axis.ticks.length.x       : NULL
##  $ axis.ticks.length.x.top   : NULL
##  $ axis.ticks.length.x.bottom: NULL
##  $ axis.ticks.length.y       : NULL
##  $ axis.ticks.length.y.left  : NULL
##  $ axis.ticks.length.y.right : NULL
##  $ axis.line                 : NULL
##  $ axis.line.x               : NULL
##  $ axis.line.x.top           : NULL
##  $ axis.line.x.bottom        : NULL
##  $ axis.line.y               : NULL
##  $ axis.line.y.left          : NULL
##  $ axis.line.y.right         : NULL
##  $ legend.background         : NULL
##  $ legend.margin             : NULL
##  $ legend.spacing            : NULL
##  $ legend.spacing.x          : NULL
##  $ legend.spacing.y          : NULL
##  $ legend.key                : NULL
##  $ legend.key.size           : 'simpleUnit' num 1.2lines
##   ..- attr(*, "unit")= int 3
##  $ legend.key.height         : NULL
##  $ legend.key.width          : NULL
##  $ legend.text               :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : 'rel' num 0.8
##   ..$ hjust        : NULL
##   ..$ vjust        : NULL
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : NULL
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ legend.text.align         : NULL
##  $ legend.title              :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : NULL
##   ..$ hjust        : num 0
##   ..$ vjust        : NULL
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : NULL
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ legend.title.align        : NULL
##  $ legend.position           : chr "right"
##  $ legend.direction          : NULL
##  $ legend.justification      : NULL
##  $ legend.box                : NULL
##  $ legend.box.just           : NULL
##  $ legend.box.margin         : NULL
##  $ legend.box.background     : NULL
##  $ legend.box.spacing        : NULL
##  $ panel.background          : NULL
##  $ panel.border              : NULL
##  $ panel.spacing             : 'simpleUnit' num 5.5points
##   ..- attr(*, "unit")= int 8
##  $ panel.spacing.x           : NULL
##  $ panel.spacing.y           : NULL
##  $ panel.grid                : NULL
##  $ panel.grid.major          : NULL
##  $ panel.grid.minor          : NULL
##  $ panel.grid.major.x        : NULL
##  $ panel.grid.major.y        : NULL
##  $ panel.grid.minor.x        : NULL
##  $ panel.grid.minor.y        : NULL
##  $ panel.ontop               : logi FALSE
##  $ plot.background           : NULL
##  $ plot.title                :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : 'rel' num 1.2
##   ..$ hjust        : num 0
##   ..$ vjust        : num 1
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : 'margin' num [1:4] 5.5points 0points 0points 0points
##   .. ..- attr(*, "unit")= int 8
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ plot.title.position       : chr "panel"
##  $ plot.subtitle             :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : NULL
##   ..$ hjust        : num 0
##   ..$ vjust        : num 1
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : 'margin' num [1:4] 5.5points 0points 0points 0points
##   .. ..- attr(*, "unit")= int 8
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ plot.caption              :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : 'rel' num 0.8
##   ..$ hjust        : num 1
##   ..$ vjust        : num 1
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : 'margin' num [1:4] 5.5points 0points 0points 0points
##   .. ..- attr(*, "unit")= int 8
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ plot.caption.position     : chr "panel"
##  $ plot.tag                  :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : 'rel' num 1.2
##   ..$ hjust        : num 0.5
##   ..$ vjust        : num 0.5
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : NULL
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ plot.tag.position         : chr "topleft"
##  $ plot.margin               : 'simpleUnit' num [1:4] 0lines 0lines 0lines 0lines
##   ..- attr(*, "unit")= int 3
##  $ strip.background          : NULL
##  $ strip.background.x        : NULL
##  $ strip.background.y        : NULL
##  $ strip.placement           : NULL
##  $ strip.text                :List of 11
##   ..$ family       : NULL
##   ..$ face         : NULL
##   ..$ colour       : NULL
##   ..$ size         : 'rel' num 0.8
##   ..$ hjust        : NULL
##   ..$ vjust        : NULL
##   ..$ angle        : NULL
##   ..$ lineheight   : NULL
##   ..$ margin       : NULL
##   ..$ debug        : NULL
##   ..$ inherit.blank: logi TRUE
##   ..- attr(*, "class")= chr [1:2] "element_text" "element"
##  $ strip.text.x              : NULL
##  $ strip.text.y              : NULL
##  $ strip.switch.pad.grid     : 'simpleUnit' num 2.75points
##   ..- attr(*, "unit")= int 8
##  $ strip.switch.pad.wrap     : 'simpleUnit' num 2.75points
##   ..- attr(*, "unit")= int 8
##  - attr(*, "class")= chr [1:2] "theme" "gg"
##  - attr(*, "complete")= logi TRUE
##  - attr(*, "validate")= logi TRUE

Day 12

Part 1 / 2

Today was difficult to keep up with all the rules. Some of the caves were different in the examples (such as the small dead end cave had a large cave to return to in the final problem). Had to do some data filtereing mid way to help with runtime issues.

x <- ## read in and clean data
  tibble(paths = read_lines(file = "day_12.txt")) %>% 
  separate(col = paths, into = c("start", "end"), sep = "-") %>% 
  rename_with(toupper) %>% 
  mutate(start = if_else(END == "start" | START == "end", END, START)) %>% 
  mutate(end = if_else(start == END, START, END)) %>% 
  select(start,end) 

upr_rev_paths <- x %>% ## large caves can go back and forth
  filter(str_detect(start, '[:upper:]')|str_detect(end, '[:upper:]')) %>%
  filter(end != "end" & start != "start") %>%
  select(start = end, end = start)

remove_these <- x %>% ## caves that are a dead end
  pivot_longer(everything()) %>% 
  filter(!str_detect(value, '[:upper:]')) %>% count(value) %>% filter(n==1) %>% pull(value)

lwr_rev_paths <- x %>% ## small caves reverse path
  filter(end != "end" & start != "start") %>% 
  filter(!str_detect(start, '[:upper:]')) %>% 
  filter(!str_detect(end, '[:upper:]')) %>% 
  select(start = end, end = start) %>% 
  filter(!start %in% remove_these | !end %in% remove_these)

y <- x %>% bind_rows(upr_rev_paths) %>% bind_rows(lwr_rev_paths) %>% distinct() ## all cave paths

first <- x %>% filter(start == "start") # from the start

join_change_names <- function(x = out, iter_num = 1, part_2 = FALSE){ 
  hold <- x %>% filter(end == "end") ## splits data to speed up code
  progress <- x %>% filter(end != "end") ## this is the only data needing processing
  out_1 <- progress %>% 
  left_join(y, by = c("end" = "start")) %>% 
    rename_with(~str_c(.,"_x")) %>% 
    rename_with(~str_c("end"), .cols = last_col()) %>% 
    rename_with(~str_c("start"), .cols = starts_with("start")) %>% 
    unite(all, everything(), remove = F) 
  if(part_2==FALSE){ ## part 1 criteria
    out <- out_1 %>% 
      filter(str_count(all, "ws")<=1) %>%
      filter(str_count(all, "kq")<=1) %>%
      filter(str_count(all, "yr")<=1) %>%
      filter(str_count(all, "zo")<=1) %>%
      filter(str_count(all, "np")<=1) %>%
      filter(str_count(all, "xq")<=1) %>%
      filter(str_count(all, "ra")<=1) %>%
      select(-all) %>% 
      bind_rows(hold)}
  if(part_2==TRUE){ ### part 2 criteria
    out <- out_1 %>% 
      filter(str_count(all, "ws")<=2) %>%
      filter(str_count(all, "kq")<=2) %>%
      filter(str_count(all, "yr")<=2) %>%
      filter(str_count(all, "zo")<=2) %>%
      filter(str_count(all, "np")<=2) %>%
      filter(str_count(all, "xq")<=2) %>%
      filter(str_count(all, "ra")<=2) %>%
      mutate(ws = str_count(all, "ws")==2) %>%
      mutate(kq = str_count(all, "kq")==2) %>%
      mutate(yr = str_count(all, "yr")==2) %>%
      mutate(zo = str_count(all, "zo")==2) %>%
      mutate(np = str_count(all, "np")==2) %>%
      mutate(xq = str_count(all, "xq")==2) %>%
      mutate(ra = str_count(all, "ra")==2) %>%
      mutate(total = ws + kq + yr + zo + np + xq + ra) %>% 
      filter(total <= 1) %>% 
      select(-all,-ws,-kq,-yr,-zo,-np,-xq,-ra,-total) %>% 
      bind_rows(hold)}
  return(out)}

1:20 %>% reduce(.f = join_change_names, .init = first) 

Day 11

Part 1

In previous attempts, I’ve moved through the entire plane making changes as I get to them. This time, I took a page from David Robinsion’s playbook and used crossing(). I then used a while loop to progress through the octopus stages

x <-
  tibble(x = (read_lines("day_11.txt"))) %>% 
  mutate(row = row_number()) %>% 
  mutate(value = str_split(x,"")) %>% 
  unnest(value) %>% 
  mutate(value = as.numeric(value)) %>% 
  group_by(row) %>% 
  mutate(col = row_number()) %>% 
  ungroup() %>% 
  relocate(value, .after = col) %>% 
  select(-x)

adj <- expand.grid(xd = c(-1,0,1), yd = c(-1,0,1))

adjacent <- function(x){
  d <- x %>% distinct(row,col,value)
  d2 <- x %>% distinct(row,col,value)
  d %>% 
    crossing(adj) %>% 
    mutate(row2 = row +xd,
           col2 = col + yd) %>%
    arrange(row,col) %>% 
    inner_join(d2, by = c(row2 = "row", col2 = "col"), suffix = c("", "2")) %>% 
    filter(row != row2 | col != col2)  
}

flash_octopus <- function(x = x, iteration = 1) {
  x <- x$x %>%
    adjacent() %>%
    mutate(across(.cols = c("value", "value2"), .fns = ~ . + 1))
  tens <- 1
  while (tens != 0) {
    x <- x %>%
      group_by(row, col) %>%  mutate(add_this_many = sum(value2 >= 10)) %>% ungroup() %>%
      mutate(value = ifelse(value >= 10,-100, value)) %>%
      mutate(value = value + add_this_many) %>%
      adjacent()
    tens <- x %>% distinct(row, col, value) %>% filter(value >= 10) %>% nrow()
  }
  flashes <- x %>% distinct(row, col, value) %>% count(did_flash = value < 0) %>% filter(did_flash) %>% pull(n)
  if (length(flashes) == 0) {flashes <- 0}
  x <- x %>% mutate(value = ifelse(value < 0, 0, value))
  x <- list(x = x, flashes = flashes)
  return(x)
}

x <- list(x = x, flashes = 0)

## Answer 1
accumulate(1:100, .f = flash_octopus, .init = x) %>% one_hundred %>% map(2) %>% unlist() %>% sum()

Part 2

For part 2, let it run longer and find the first element that has 100 flashes.

three_hundred <- accumulate(1:300, .f = flash_octopus, .init = x)

which(unlist(map(three_hundred, 2)) == 100)[1] - 1

I also want to provide this function that proved helpful in viewing the matrix throughout.

see_matrix <- function(data = x){
  data %>% 
    distinct(row, col, value) %>% 
    select(row,col,value) %>% 
    pull(value) %>% 
    matrix(nrow = 10, byrow = T)
}

Day 10

Part 1

I use a while loop to remove all ‘open’ ‘close’ pairings. This helps find both corrupted and broken chunks. I find the corrupted chunks and apply the scoring mechanism.

input <-
  tibble(chunks = read_lines("day_10.txt"))

syntax_error_points <- ## points for each bracket type
  tibble(chunks = c(")", "]", "}", ">"),
         points = c(3, 57, 1197, 25137)
         )

input_list <- list(NULL, input) ## prepare list for iteration

i <- 2 ## initialize iterator 
while (!isTRUE(all.equal(input_list[i], input_list[i - 1]))) {
  input <-  input %>%
    mutate(chunks = str_remove_all(string = chunks, pattern = "<>|\\[\\]|\\(\\)|\\{\\}"))
  i = i + 1
  input_list[[i]] <- input
} ## this iteratively removes all open/closed pairings until the df does not change between iterations

input_list %>% tail(1) %>% pluck(1) %>% ## select the last list
  mutate(chunks = str_remove_all(string = chunks, pattern = "<|\\[|\\(|\\{")) %>% ## remove openings
  filter(chunks != "") %>% ## remove extraneous lines
  mutate(chunks = str_sub(chunks, 1, 1)) %>% ## select the first opening
  count(chunks) %>% ## count them up!
  left_join(syntax_error_points, by = "chunks") %>%  ## join in points
  summarise(score = sum(n * points)) ## score the process

Part 2

I take the incomplete chunks from the previous part and map over the score_points function for the final answer.

incomplete_df <- ## identify chunks with incomplete strings
  input_list %>% tail(1) %>% pluck(1) %>% ## selects list element with all open/closed pairs removed
  mutate(chunks = str_remove_all(string = chunks, pattern = "<|\\[|\\(|\\{")) %>% ## removes opens
  mutate(incomplete_strings = row_number()) %>% ## ids each pattern
  filter(chunks == "") %>% ## removes extraneous lines
  select(id = chunks, incomplete_strings) ## now have df with incomplete strings identified

matching_df <- ## create scoring df for joining later
  tibble(
    chunks = c("(", "[", "{", "<"),
    right = c(")", "]", "}", ">"),
    points = c(1, 2, 3, 4))

incomplete_list <-  ## creates list of points
  input %>%  mutate(incomplete_strings = row_number()) %>%
  semi_join(incomplete_df, by = "incomplete_strings") %>% ## only keep the chunks that are incomplete
  separate_rows(chunks, sep = "") %>% 
  left_join(matching_df, by = "chunks") %>% ## add in points
  filter(!is.na(right)) %>%
  group_by(incomplete_strings) %>% mutate(row_num = row_number()) %>% arrange(desc(row_num)) %>%
  select(-row_num) %>% ## puts the chunks in the right order
  group_split() %>% ## splits them for future map function
  map(.f = ~ pull(.data = .x, points)) ## only keeps the column we want

score_points <- function(current_vector = points, index) {
    current_vector[index] <- current_vector[index - 1] * 5 + current_vector[index]
    return(current_vector)} ## function to execute partial scoring function

score_everything <- function(data) {
  2:length(data) %>%
    reduce(.f = score_points, .init = data) %>%
    tail(1)} ## function which scores entire vector

incomplete_list %>%
  map_dbl(.f = ~ score_everything(data = .x)) %>%
  sort() %>% median() ## maps all lists over scoring function

Day 9

Part 1

To find the number of low points I map across a function that determines if each place is a depression. After the identification, I use the problems formula to sum up the risk levels.

input <-
  matrix(read_lines("02_data/day_9.txt")) %>% 
  strsplit("") %>% unlist() %>% as.numeric() %>% matrix(byrow = T, nrow = 100) 

checker <- function(row, col){
  if(col < ncol(input)){right <- input[row,col]-input[row,col+1] < 0}else{right<-1} # check right
  if(col != 1)         {left  <- input[row,col]-input[row,col-1] < 0}else{left<-1}  # check left
  if(row != 1)         {upper <- input[row,col]-input[row-1,col] < 0}else{upper<-1} # check up
  if(row < nrow(input)){lower <- input[row,col]-input[row+1,col] < 0}else{lower<-1} # check down
  
  out <- tibble(number = input[row,col], row = row, col = col, 
                num_increase = sum(right,left,upper,lower)) # this sums every direction that has an increase
  return(out)
}

future_map2_dfr(.x = sort(rep(1:nrow(input),ncol(input))),
                .y = rep(1:ncol(input),nrow(input)),
                .f = ~checker(row = .x,col = .y), .progress = T) %>% 
  filter(num_increase == 4) %>% 
  mutate(number = number + 1) %>% 
  summarise(answer = sum(number))

Part 2

I again map over a function that looks in all directions and determines if it is touching a place that is not a 9. I count the basins one at a time and add up the size of the basins.

id_reduce_count_replace <- function(input){
  input <- unique.matrix(input$input, MARGIN = 1) ## gets rid of rows already searched to speed up code
  input <- t(input) %>% str_c(collapse = "") %>% str_replace(pattern = "[^X]", replacement = "B") %>% 
    str_split(pattern = "") %>% unlist() %>% matrix(byrow = T, nrow = nrow(input))
    ## above determines first spot of basin in top row
  identified_basin <- reduce2(.x = ## there is probably a better way to come up with these vectors to map over
                                c(sort(rep(1:nrow(input),ncol(input))),
                                  rev(sort(rep(1:nrow(input),ncol(input)))),
                                  sort(rep(1:nrow(input),ncol(input))),
                                  rev(sort(rep(1:nrow(input),ncol(input))))),
                              .y = 
                                c(rep(1:ncol(input),nrow(input)),
                                  rep(1:ncol(input),nrow(input)),
                                  rev(rep(1:ncol(input),nrow(input)),
                                  rep(1:ncol(input),nrow(input)))), 
                              .f = sweep, .init = input) ## iterates over all places on matrix to determine if basin
  
  basin_size <- identified_basin %>%  str_count("B") %>% sum() # identify to of new basin

  input <- str_replace_all(identified_basin,"B", "X") %>% matrix(byrow = F, nrow = nrow(input)) # delete basin tracking
  
  if(sum(str_count(input, pattern = "X"))==sum(nchar(input))){message("Finished")} # create an error if complete

  return(list(input = input, basin_size = basin_size))
}

initial <-
  matrix(read_lines("02_data/day_9.txt")) %>%
  strsplit("") %>% unlist() %>% str_replace_all(pattern = "9","X") %>% 
  matrix(byrow = T, nrow = 100) 

input <- list(input = initial, basin_size = NULL)

sweep <- function(input, row, col){
  if(col != ncol(input))  {input[row,col][input[row,col+1]=="B" && input[row,col] !="X"] <- "B"} # look right
  if(row != nrow(input))  {input[row,col][input[row+1,col]=="B" && input[row,col] !="X"] <- "B"} # look down
  if(col != 1)            {input[row,col][input[row,col-1]=="B" && input[row,col] !="X"] <- "B"} # look left
  if(row != 1)            {input[row,col][input[row-1,col]=="B" && input[row,col] !="X"] <- "B"} # look up
  return(input)
}

tictoc::tic()
accumulate(.x = 1:225, .f = id_reduce_count_replace, .init = input) %>% map(2) %>% unlist() %>% sort() %>% tail(3) %>% prod()
tictoc::toc()
## took just under 300 seconds

Day 8

Part 1

For part 1, I separate the string to grab the element of interest, I count the length of the unique output values, and count how many time this happens.

tibble(entry = read_lines("day_8.txt")) %>%
  separate(col = entry, into = c("NA","output"),sep = " \\| ") %>% 
  mutate(id = row_number()) %>%
  separate_rows(output, sep = " ") %>% 
  mutate(length = nchar(output)) %>% 
  count(length) %>%
  filter(length %in% c(2,3,4,7)) %>%
  summarise(sum = sum(n))

Part 2

Part 2 is a little less elegant. I extract the signal patterns and use a set of functions to identify which strings become each number. I then map over all entrys to identify the letters. Next I join in the output values to determine the numerical output values.

initial <-
  tibble(entry = read_lines("day_8.txt")) %>%
  separate(col = entry, into = c("signal","output"),sep = " \\| ") %>% 
  mutate(id = row_number()) %>%
  separate_rows(signal, sep = " ") %>% 
  rowwise() %>% 
  mutate(signal = str_c(sort(unlist(str_split(signal,""))), collapse = "")) %>% ungroup() %>% 
  select(-output)

output_tbl <-
  tibble(entry = read_lines("day_8.txt")) %>%
  separate(col = entry, into = c("signal","output"),sep = " \\| ") %>% 
  mutate(id = row_number()) %>%
  separate_rows(output, sep = " ") %>% 
  rowwise() %>% 
  mutate(output = str_c(sort(unlist(str_split(output,""))), collapse = "")) %>% ungroup() %>% 
  select(-signal) %>% 
  group_by(id) %>% 
  summarise(output = str_c(output, collapse = " ")) %>% 
  ungroup() %>% 
  separate_rows(output, sep = " ") %>% 
  mutate(order = row_number())

identify_obvious <- function(initial){
  out <- initial %>% 
    mutate(number = case_when(nchar(signal) == 2 ~ 1,
                              nchar(signal) == 3 ~ 7,
                              nchar(signal) == 4 ~ 4,
                              nchar(signal) == 7 ~ 8,
                              TRUE ~ 0
                              )) %>% 
    filter(number !=0) %>% 
    arrange(number)
  return(out)
}
  
identify_3 <- function(known, initial){
  seven <-
    known  %>% filter(number %in% c(7)) %>% 
    pull(signal) %>% str_split("") %>% unlist()
  three <-  
    initial %>% 
    filter(nchar(signal) == 5) %>% 
    mutate(split = signal) %>% 
    separate_rows(split, sep = "") %>% 
    filter(split %in% seven) %>% 
    group_by(signal) %>% filter(n() == 3) %>% ungroup(signal) %>% 
    distinct(signal, .keep_all = T) %>% 
    select(-split) %>% 
    mutate(number = 3) %>% 
    bind_rows(known) %>% 
    arrange(number)
  return(three)
}

identify_9 <- function(known, initial){
  three <-
    known  %>% filter(number %in% c(3)) %>% 
    pull(signal) %>% str_split("") %>% unlist()
  nine <-
    initial %>% 
    filter(nchar(signal) == 6) %>% 
    mutate(split = signal) %>% 
    separate_rows(split, sep = "") %>% 
    filter(split %in% three) %>% 
    group_by(signal) %>% filter(n() == 5) %>% ungroup(signal) %>% 
    distinct(signal, .keep_all = T) %>% 
    select(-split) %>% 
    mutate(number = 9) %>% 
    bind_rows(known) %>% 
    arrange(number)
  return(nine)
}

identify_0 <- function(known, initial){
  one <-
    known  %>% 
    filter(number %in% c(1)) %>% 
    pull(signal) %>% 
    str_split("") %>% unlist()
  zero <-
    initial %>% 
    anti_join(known, by = c("signal", "id")) %>% 
    filter(nchar(signal) == 6) %>%   
    mutate(split = signal) %>% 
    separate_rows(split, sep = "") %>% 
    filter(split %in% one) %>% 
    group_by(signal) %>% filter(n() == 2) %>% ungroup(signal) %>% 
    distinct(signal, .keep_all = T) %>% 
    select(-split) %>% 
    mutate(number = 0) %>% 
    bind_rows(known) %>% 
    arrange(number)
  return(zero)
}

identify_6 <- function(known, initial){
  known <-
    initial %>% 
    anti_join(known, by = c("signal", "id")) %>% 
    filter(nchar(signal) == 6) %>%
    mutate(number = 6) %>% 
    bind_rows(known) %>% 
    arrange(number)
  return(known)
}

identify_5_2 <- function(known, initial){
  part <-
    known %>% 
    filter(number %in% c(1,4)) %>% 
    separate_rows(signal, sep = "") %>% 
    group_by(signal) %>% filter(n()==1) %>% 
    pull(signal) %>% str_split("") %>% unlist()
  last <- 
    initial %>% 
    anti_join(known, by = c("signal", "id")) %>% 
    mutate(side = signal) %>% 
    separate_rows(side, sep = "")  %>% 
    filter(side %in% part) %>% 
    group_by(signal) %>% 
    mutate(number = if_else(n()==2,5,2)) %>% 
    ungroup() %>% select(-side) %>% distinct() %>% 
    bind_rows(known) %>% 
    arrange(number)
return(last)
}

all_together <- function(initial) {
  all <-
    initial %>%
    identify_obvious() %>%
    identify_3(initial = initial) %>%
    identify_9(initial = initial) %>%
    identify_0(initial = initial) %>%
    identify_6(initial = initial) %>%
    identify_5_2(initial = initial)
  return(all)
}

initial %>% group_split(id) %>% 
  map_df(~all_together(initial = .x)) %>% 
  left_join(output_tbl, by = c("signal" = "output", "id" = "id")) %>% 
  filter(!is.na(order)) %>% arrange(order) %>% 
  group_by(id) %>% 
  summarise(signal = str_c(signal, collapse = " "), 
            number = as.numeric(str_c(number, collapse = ""))) %>% 
  summarise(solution = sum(number))

Day 7

Part 1

To solve part 1, I create a function that finds the distance between all points to a specific point, map across all specific points, and then find the minimum distance.

dat <- as.numeric(unlist(str_split(read_lines("02_data/day_7.txt"),","))) 

distance <- function(data = dat, position) {
  out <- sum(abs(data - position))
  return(out)
}

min(dat):max(dat) %>% 
  map_dbl(.f = ~distance(data = dat, position = .x)) %>% min()

Part 2

I alter the distance formula to find the triangular number between two numbers. This can also be factored into the binomial coefficient with the upper index being the distance between the numbers and 2 as the lower index.

distance_plus <- function(data = dat, position) {
  n <-abs(data-position)+1
  out <- sum(choose(n,2))
  return(out)
}

min(dat):max(dat) %>% 
  map_dbl(.f = ~distance_plus(data = dat, position = .x)) %>% min()

Code Golfing

This last code chunk is my attempt at ‘code golfing’ - aka - how succinct can I make my code. I can answer both parts with fairly brief code, but I think its less understandable.

fuel_calculation <- function(data = dat, position, type) {
  if(type == "distance"){out <- tibble(fuel = sum(abs(data - position)), position = position, type)}
  if(type == "triangular"){out <- tibble(fuel = sum(choose(abs(data-position)+1,2)), position = position, type)}
  return(out)
}

rep(min(dat):max(dat), 2) %>%
  map2_dfr(.y = c(rep("distance", length(min(dat):max(dat))), rep("triangular", length(min(dat):max(dat)))),
    .f = ~ fuel_calculation(data = dat,position = .x, type = .y)) %>%
  group_split(type) %>%
  map( ~ filter(.x, fuel == min(fuel)))

Day 6

Part 1 and 2

My gut instinct is to always think in ‘tidy’ coding concepts. This will get the answer, but when moving from part 1 to part 2, the time it takes to run the code becomes computationally infeasible with my computer. I’ll show my 3 attempts.

Strike 1

initial_fish <- tibble(fish = read_lines("day_6.txt")) %>%
  separate_rows(fish, sep = ",", convert = T) 

progress_day <- function(fish = initial_fish, after_day = 1){
fish <-
  fish %>% 
  mutate(fish = ifelse(fish <= 0 , "6,8", fish-1)) %>% 
  separate_rows(fish, sep = ",", convert = T) 
return(fish)
}

tictoc::tic()
ans <- 1:80 %>% 
# ans <- 1:256 %>% nope!
  reduce(.f = progress_day, .init = initial_fish) %>% nrow()
tictoc::toc()
## 37.14 sec elapsed

My second attempt was a ‘non-tidy’ solution, but part 2 also took too long to run.

Strike 2

initial_fish <- as.numeric(unlist(strsplit(readr::read_lines("day_6.txt"),",")))

progress_day <- function(fish = initial_fish, after_day = 1) {
  
  add_fish <- sum(fish == 0)
  next_day <- fish - 1
  next_day[next_day < 0] <- 6

  out <- c(next_day, rep(8, add_fish))
return(out)  
}

tictoc::tic()
ans <- 1:150 %>% 
# ans <- 1:256 %>% faster, but nope! 
  reduce(.f = progress_day, .init = initial_fish) %>% length()
tictoc::toc() 
## 33.5 sec elapsed

Lastly, instead of building out a vector for each fish, I decided to count the number of fish at each state of their birthing countdown. This make things work nice and quick.

Success!

fish <- as.numeric(unlist(strsplit(read_lines("day_6.txt"),",")))
 
initial_fish <- c(
length(fish[fish==8]),
length(fish[fish==7]),
length(fish[fish==6]),
length(fish[fish==5]),
length(fish[fish==4]),
length(fish[fish==3]),
length(fish[fish==2]),
length(fish[fish==1]),
length(fish[fish==0]))

progress_day <- function(fish = initial_fish, after_day = 1){

births = fish[9]
get_older <- replace_na(lag(fish),0)
out <- c(births, get_older[2], get_older[3]+births, get_older[-c(1:3)])
return(out)
}

tictoc::tic()
ans <- 1:256 %>% 
  reduce(.f = progress_day, .init = initial_fish) %>% sum()
tictoc::toc()
## 0.02 sec elapsed

Day 5

Part 1

For this problem, I created a matrix to track the lines of the hydrothermic vents. In part one, each line was straight so I was able to build this matrix fairly simply. I had to create a toggle for vents that were horizontal vs vertical.

movements <-
  tibble(directions = read_lines("day_5.txt")) %>%
  mutate(directions = str_replace_all(string = directions, pattern = " -> ", ",")) %>% 
  separate(col = directions, into = c("x1", "y1", "x2", "y2"), sep = ",", convert = T) %>% 
  select(x1,x2,y1,y2) 

create_lines <- function(lines){

move <- lines

if(move$y1==move$y2) {
  xlow  <- min(move$x1, min(move$x2))
  xhigh <- max(move$x1, min(move$x2))
  ylow  <- min(move$y1, min(move$y2))
  yhigh <- max(move$y1, min(move$y2))
  byrow <- T
} else if (move$x1 == move$x2) {
  ylow  <- min(move$x1, min(move$x2))
  yhigh <- max(move$x1, min(move$x2))
  xlow  <- min(move$y1, min(move$y2))
  xhigh <- max(move$y1, min(move$y2))
  byrow <- F
}

out <-
  matrix(
    c(
      rep(rep(0,1000),ylow), ## zeroes before row/col
      c(rep(0,xlow),rep(1,xhigh-xlow+1), rep(0,999-xhigh)), ## row/col of 1s
      rep(rep(0,1000),999-ylow) ## zeroes after row/col
      ), byrow = byrow, nrow = 1000, ncol = 1000
    )

return(out)

}

movements %>% 
  filter(x1==x2 | y1==y2) %>% 
  group_split(row_number(), .keep = F) %>% 
  purrr::map(~create_lines(lines = .x)) %>% 
  reduce(`+`) %>% 
  as_tibble() %>% 
  pivot_longer(cols = everything()) %>% 
  filter(value >=2) %>% 
  count()

Part 2

In this part some of the vents were diagonal. I used the first function to mark the straight vents and created a second function to trace the ones at an angle. I had to condition this off the top right to bottom left lines verses the top left to bottom right lines.

cross_routes <- function(move){

  xlow  <- min(move$x1, min(move$x2))
  xhigh <- max(move$x1, min(move$x2))
  ylow  <- min(move$y1, min(move$y2))
  yhigh <- max(move$y1, min(move$y2))

mat <- matrix(rep(0,1000000), ncol = 1000) # create a 1000 x 1000 matrix
if((move$x2>move$x1 & move$y2>move$y1) | (move$x2<move$x1 & move$y2<move$y1)){
  for (i in 1:length(xlow:xhigh)) {
    mat[ylow+i,xlow+i] <- 1
  }
} else{ ## vent is top left to bottom right
  for (i in 1:length(xlow:xhigh)) {
    mat[ylow+i,xhigh+2-i] <- 1
  }
}## determine if vent is top right to bottom left
return(mat)
}

striaght_lines <- # from part 1
  movements %>% 
  filter(x1==x2 | y1==y2) %>% # straight lines
  group_split(row_number(), .keep = F) %>% 
  purrr::map(~create_lines(lines = .x)) %>% 
  reduce(`+`) 

diagonal_lines <-
  movements %>%
  filter(x1!=x2 & y1!=y2) %>% # diagonal lines
  group_split(row_number(), .keep = F) %>% 
  purrr::map(~cross_routes(move = .x)) %>% 
  reduce(`+`) 
 
as_tibble(striaght_lines + diagonal_lines) %>% 
  pivot_longer(cols = everything()) %>%
  filter(value >=2) %>%
  count()

Day 4

I feel like this could be done more simply, but today I create a function that take the bingo number, adjudicates each board, determines if there is a winner, then outputs the next board. Then we use purrr::accumulate() and purrr::possibly() to handle errors that I intentionally create.

Part 1

data <- read_lines("day_4.txt")

selections <-
  data[1] %>% str_split(",") %>% unlist() %>% str_pad(2,"left") %>% str_pad(3,"right")

boards <-
  tibble(cards = data[-1]) %>% 
  filter(cards != "") %>%
  mutate(cards = str_squish(cards)) %>% 
  transmute(col = str_split_fixed(cards, pattern = " ", n = 5)) %>% 
  pull(col) %>% as_tibble() %>% 
  mutate(across(.cols = everything(),.fns = ~str_pad(str_pad(.,2,"left"),3,"right"))) %>% 
  mutate(card_number = sort(rep(1:(n()/5),5))) %>% 
  mutate(winner = 0)

boards_list <- list(boards = boards, win_card = integer(0))

mark_board <- function(boards_list = boards_list, selection = selections[1]){
  if(length(boards_list$win_card) > 0){stop(boards_list)}
  boards <- boards_list$boards
  marked <-
    boards %>% 
    mutate(across(.cols = 1:5, .fns = ~str_replace_all(string = ., pattern = selection, replacement = "X")))
  row_win_card <-
    marked %>%
    mutate(row_bingo = str_count(str_c(V1,V2,V3,V4,V5),"X")) %>% 
    filter(row_bingo==5)  %>% 
    pull(card_number)
  col_win_card <-
    marked %>% 
    mutate(across(.cols = V1:V5, .fns = ~ifelse(.=="X",1,0))) %>% 
    group_by(card_number) %>% 
    summarise(across(everything(), ~sum(.))) %>% 
    filter(V1 ==5|V2 ==5|V3 ==5|V4 ==5|V5 ==5) %>% 
    pull(card_number)
  card_number <- ifelse(length(row_win_card) > 0, row_win_card, ifelse(length(col_win_card) > 0, col_win_card, integer(0)))
  if(is.na(card_number)){card_number <- integer(0)}
  out <- list(boards = marked, win_card = card_number, selection = selection)
  return(out)
}

mark_board_poss <- possibly(mark_board, otherwise = NULL)

winning_card <- 
selections %>% 
  accumulate(.f = mark_board_poss, .init = boards_list) %>% 
  discard(is.null) %>% 
  pluck(length(.)) 

winning_card$boards %>% 
  filter(card_number == winning_card$win_card) %>% 
  select(-card_number) %>% 
  mutate(across(everything(), ~as.numeric(.))) %>% 
  as.matrix() %>% sum(na.rm = T) %>% prod(as.numeric(winning_card$selection))

Part 2

boards_list <- list(boards = boards)

mark_board_2 <- function(boards_list = boards_list, selection = selections[41]){

  if(max(boards_list$boards$winner)==100){stop(boards_list)}
  
  boards <- boards_list$boards
  
  marked <-
    boards %>% 
    mutate(across(.cols = 1:5, .fns = ~str_replace_all(string = ., pattern = selection, replacement = "X")))
  
  row_win_card <-
    marked %>%
    mutate(row_bingo = str_count(str_c(V1,V2,V3,V4,V5),"X")) %>% 
    filter(row_bingo==5)  %>% 
    pull(card_number)
  
  col_win_card <-
    marked %>% 
    mutate(across(.cols = V1:V5, .fns = ~ifelse(.=="X",1,0))) %>% 
    group_by(card_number) %>% 
    summarise(across(everything(), ~sum(.))) %>% 
    filter(V1 ==5|V2 ==5|V3 ==5|V4 ==5|V5 ==5) %>% 
    pull(card_number)
  
  if(length(row_win_card)>0 & nrow(marked) == 5){
    marked <- 
      marked %>% 
      mutate(winner = 100)
    return(out <- list(boards = marked, selection = selection))
    } else if(length(row_win_card)>0){
    marked <- 
    marked %>% 
      filter(!card_number %in% row_win_card)
  }
  if(length(col_win_card)>0 & nrow(marked) == 5){
    marked <- 
    marked %>% 
      mutate(winner = 100)
    return(out <- list(boards = marked, selection = selection))
  } else if(length(col_win_card)>0){
    marked <- 
    marked %>% 
      filter(!card_number %in% col_win_card)
  }
  
  out <- list(boards = marked, selection = selection)
  
  return(out)
  
}

mark_board_2_poss <- possibly(mark_board_2, otherwise = NULL)

winning_card <-
selections %>% 
  accumulate(mark_board_2_poss, .init = boards_list) %>% 
  discard(is.null) %>% 
  pluck(length(.))

winning_card$boards %>% 
  select(V1:V5) %>% 
  mutate(across(everything(), ~as.numeric(.))) %>% 
  as.matrix() %>% sum(na.rm = T) %>% prod(as.numeric(winning_card$selection))

Day 3

I rely on several pivots, unite(), and the iterative function purrr::reduce() for this problem.

Part 1

tibble(binary = read_lines("day_3.txt")) %>% 
  transmute(col = str_split_fixed(binary, pattern = "", n = nchar(binary))) %>% 
  pull(col) %>% as_tibble() %>% 
  summarise(across(.cols = everything(), .fns = ~if_else(mean(as.numeric(.))>.5,1,0))) %>% 
  pivot_longer(cols = everything(), values_to = "gamma", names_to = "slot")  %>% 
  mutate(epsilon = if_else(gamma == 1, 0, 1)) %>% 
  pivot_longer(cols = -slot) %>% 
  pivot_wider(names_from = slot, values_from = value) %>% 
  unite(col = "binary",  where(is.double), sep = "")  %>% 
  mutate(decimal = strtoi(as.double(binary), base = 2)) %>% 
  pull(decimal) %>% prod()

Part 2

data <-
tibble(binary = read_lines("day_3.txt")) %>% 
  transmute(col = str_split_fixed(binary, pattern = "", n = nchar(binary))) %>% 
  pull(col) %>% as_tibble() %>% 
  summarise(across(.cols = everything(), .fns = ~as.numeric(.))) 

filter_rating <- function(data, col = "V1", type = "oxygen"){
  if(nrow(data)==1){out <- data} 
  else if(type == "oxygen"){out <- data[data[,col]==if_else(mean(data[,col][[1]])>=.5,1,0),]}
  else if(type == "co2"){out <- data[data[,col]==if_else(mean(data[,col][[1]])<.5,1,0),]}
}
  
reduce(.x = names(data), .f = filter_rating, .init = data, "oxygen") %>%
  unite(col = "binary",everything(), sep = "") %>% 
  pull() %>% strtoi(base = 2) *

reduce(.x = names(data), .f = filter_rating, .init = data, "co2") %>%
  unite(col = "binary",everything(), sep = "") %>% 
  pull() %>% strtoi(base = 2)

Day 2

Today I used seperate() and cumsum() to work this problem.

Part 1

tibble(x = read_lines("day_2.txt")) %>% 
  separate(col = x, into = c("direction","distance"), convert = TRUE) %>% 
  group_by(real_direction = direction == "forward") %>% 
  mutate(distance = ifelse(direction == "up", distance * -1, distance)) %>% 
  summarise(movement = sum(distance)) %>% 
  pull(movement) %>% prod()

Part 2

tibble(x = read_lines("day_2.txt")) %>% 
  separate(col = x, into = c("direction","distance"), convert = TRUE) %>%
  mutate(distance = as.double(distance)) %>% 
  mutate(aim_change = case_when(direction == "forward" ~ 0,
                                direction == "down" ~ distance,
                                direction == "up" ~ -1 * distance)) %>% 
  mutate(aim = cumsum(aim_change)) %>% 
  mutate(horizontal_position_increase = ifelse(direction == "forward", distance, 0)) %>% 
  mutate(depth_position_increase = ifelse(direction == "forward", aim * distance,0)) %>% 
  mutate(horizontal_position = cumsum(horizontal_position_increase)) %>% 
  mutate(depth_position = cumsum(depth_position_increase)) %>% 
  slice_tail(n = 1) %>% 
  summarise(answer = horizontal_position * depth_position) 

Day 1

Using lag() and zoo::rollsum() we are able to accomplish day one pretty easily.

Part 1

tibble(measurements = as.numeric(read_lines("day1.txt"))) %>%  
  filter(measurements > lag(measurements,1)) %>% 
  nrow()

Part 2

tibble(measurements = as.numeric(read_lines("day1.txt"))) %>%
  mutate(roll_sum = zoo::rollsum(measurements,3, fill = NA, align = "right")) %>%
  filter(roll_sum > lag(roll_sum,1)) %>%
  nrow()