grids
graphs
tidyverse
igraph
adventdrob
Author

Ella Kaye

Published

December 23, 2023

Setup

The original challenge

My data

Part 1

Toggle the code
library(aochelpers)
input <- aoc_input_data_frame(23, 2023) 
head(input)
# A tibble: 6 × 1
  X1                                                                            
  <chr>                                                                         
1 #.###########################################################################…
2 #...###...#...#.......#.....#...#...#...###...#...#...###...#.....#.....#...#…
3 ###.###.#.#.#.#.#####.#.###.#.#.#.#.#.#.###.#.#.#.#.#.###.#.#.###.#.###.#.#.#…
4 ###...#.#.#.#.#.....#.#...#.#.#.#.#.#.#.###.#.#.#.#.#.....#.#...#.#...#.#.#.#…
5 #####.#.#.#.#.#####.#.###.#.#.#.#.#.#.#.###.#.#.#.#.#######.###.#.###.#.#.#.#…
6 #...#...#.#.#...###.#.#...#.#.#...#...#.>.>.#.#.#...#.....#.#...#.#...#.#.#..…
The crux of the puzzle

Find the longest path on a grid between given start and end points.

Toggle the code
library(tidyverse)
library(adventdrob)
library(igraph)

grid <- input |> 
  adventdrob::grid_tidy(X1) |> 
  mutate(name = row_number()) 

start_node <- grid |> 
  filter(row == 1 & value == ".") |> 
  pull(name)

end_node <- grid |> 
  filter(row == nrow(input) & value == ".") |> 
  pull(name)

grid_adj <- adventdrob::adjacent_join(grid, grid)

grid_adj_connected <- grid_adj |> 
  mutate(is_connected = case_when(
    value == "." & value2 == "." ~ TRUE,
    value == ">" & row2 == row & col2 == (col + 1) & value2 == "." ~ TRUE,
    value == "<" & row2 == row & col2 == (col - 1) & value2 == "." ~ TRUE,  
    value == "^" & row2 == (row - 1) & col2 == col & value2 == "." ~ TRUE,
    value == "v" & row2 == (row + 1) & col2 == col & value2 == "." ~ TRUE,
    value == "." & value2 == "v" & col2 == col & row2 == (row + 1) ~ TRUE,
    value == "." & value2 == "^" & col2 == col & row2 == (row - 1) ~ TRUE,
    value == "." & value2 == "<" & col2 == (col - 1) & row2 == row ~ TRUE,
    value == "." & value2 == ">" & col2 == (col + 1) & row2 == row ~ TRUE,
    .default = FALSE
  ))

grid_edges <- grid_adj_connected |> 
  filter(is_connected) |> 
  select(from = name, to = name2) |> 
  as.matrix()

forest_graph <- graph_from_edgelist(grid_edges) 

all_paths_lengths <- all_simple_paths(forest_graph, start_node, end_node) |> 
  lengths() 

# need to subtract one as paths include start node,
# whereas we want number of steps, not number of nodes in the path
max(all_paths_lengths - 1)
[1] 2170

The majority of the code today follows a pattern familiar from Day 10 and Day 21. I’d used the tidygraph package on both those days, but today it didn’t have the functionality we need. With tidygraph, we can find the shortest path, but here we need the longest. We’re told that we can never step on the same node twice, which means that the path we’re looking for is the longest simple path. The igraph package has a function all_simple_paths(), which returns a list, with one element per simple path between the given nodes. Each element is a vector containing all nodes in that path. Since we’re using igraph for the calculation, we’re also using it to create the graph. We can do that from a edge list, which requires a 2-column matrix of the edges. For this puzzle, we use lengths() to find the length of each of those vectors, which is a convenient shorthand for sapply(list, length). We then need to subtract one from each, as we need the number of steps, not number of nodes. The max of those gets us one gold star.

Part 2

The crux of the puzzle

As Part 1, but with many more possible paths.

I had thought (i.e. optimistically hoped) it would be straightforward to adapt the code to change the slopes to regular paths, then run the same code again, something like:

Toggle the code
grid_no_slopes <- grid |> 
  mutate(value = if_else(value %in% c("<", ">", "^", "v"), ".", value)) 

grid_no_slopes_adj <- adventdrob::adjacent_join(grid_no_slopes, grid_no_slopes)

grid_no_slopes_adj_connected <- grid_no_slopes_adj |> 
  mutate(is_connected = if_else(value == "." & value2 == ".", TRUE, FALSE))

grid_no_slopes_edges <- grid_no_slopes_adj_connected |> 
  filter(is_connected) |> 
  select(from = name, to = name2) |> 
  as.matrix()

forest_no_slopes_graph <- graph_from_edgelist(grid_no_slopes_edges) 

all_paths_no_slopes_lengths <- all_simple_paths(forest_no_slopes_graph, start_node, end_node) |> 
  lengths() 

max(all_paths_no_slopes_lengths - 1)

That ran speedily on the example input, and gave the example answer. However, on the full input, after the computer chugged away for a few minutes, I got the error vector memory exhausted (limit reached?).

There must be a more efficient way, but that’s beyond what I currently know how to do. It’s becoming increasingly clear to me that to be successful in the latter stages of Advent of Code, I’m going to need a much better understanding of data structures and algorithms.

Session info

Toggle
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.2 (2023-10-31)
 os       macOS Sonoma 14.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Europe/London
 date     2023-12-24
 pandoc   3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
 quarto   1.4.526 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 package     * version    date (UTC) lib source
 adventdrob  * 0.0.1      2023-11-19 [1] Github (dgrtwo/adventdrob@491050f)
 aochelpers  * 0.1.0.9000 2023-12-06 [1] local
 dplyr       * 1.1.4      2023-11-17 [1] CRAN (R 4.3.1)
 forcats     * 1.0.0      2023-01-29 [1] CRAN (R 4.3.0)
 ggplot2     * 3.4.4      2023-10-12 [1] CRAN (R 4.3.1)
 igraph      * 1.5.1      2023-08-10 [1] CRAN (R 4.3.0)
 lubridate   * 1.9.3      2023-09-27 [1] CRAN (R 4.3.1)
 purrr       * 1.0.2      2023-08-10 [1] CRAN (R 4.3.0)
 readr       * 2.1.4      2023-02-10 [1] CRAN (R 4.3.0)
 sessioninfo * 1.2.2      2021-12-06 [1] CRAN (R 4.3.0)
 stringr     * 1.5.1      2023-11-14 [1] CRAN (R 4.3.1)
 tibble      * 3.2.1      2023-03-20 [1] CRAN (R 4.3.0)
 tidyr       * 1.3.0      2023-01-24 [1] CRAN (R 4.3.0)
 tidyverse   * 2.0.0      2023-02-22 [1] CRAN (R 4.3.0)

 [1] /Users/ellakaye/Library/R/arm64/4.3/library
 [2] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────