diff --git a/2024/R/day17.qmd b/2024/R/day17.qmd new file mode 100644 index 0000000..689e314 --- /dev/null +++ b/2024/R/day17.qmd @@ -0,0 +1,165 @@ +--- +title: "Day 17" +date: 2024-12-17 +author: + name: https://adventofcode.com/2024/day/17 + url: https://adventofcode.com/2024/day/17 +--- + +## Setup + +```{r setup} +# Libraries +library(tidyverse) +library(unglue) + +# Read input from file +input <- read_lines("../input/day17.txt", skip_empty_rows = TRUE) |> + unglue_data(patterns = c( + "{label}: {value}" + )) + +``` + +## Part 1 + +Initialize the machine from the text input: + +```{r} + +program <- input |> + filter(label == "Program") |> + pull(value) |> + str_split_1(",") |> + as.integer() + +A <- input |> + filter(label == "Register A") |> + pull(value) |> + as.integer() + +B <- input |> + filter(label == "Register B") |> + pull(value) |> + as.integer() + +C <- input |> + filter(label == "Register C") |> + pull(value) |> + as.integer() + +machine <- list(program = program, A = A, B = B, C = C, pointer = 0L, output = NULL) + +``` + +Define machine's helper functions: + +```{r} + +combo <- function(machine, operand) { + case_match(operand, + 0 ~ 0, + 1 ~ 1, + 2 ~ 2, + 3 ~ 3, + 4 ~ machine$A, + 5 ~ machine$B, + 6 ~ machine$C + ) +} + +run_opcode <- function(machine, opcode, operand) { + func <- case_match(opcode, + 0 ~ "adv", + 1 ~ "bxl", + 2 ~ "bst", + 3 ~ "jnz", + 4 ~ "bxc", + 5 ~ "out", + 6 ~ "bdv", + 7 ~ "cdv" + ) + + get(func)(machine, operand) +} + +run_machine <- function(machine) { + while (machine$pointer < length(machine$program)) { + opcode <- machine$program[machine$pointer + 1] + operand <- machine$program[machine$pointer + 2] + machine <- run_opcode(machine, opcode, operand) + } + print(machine$output) +} + +``` + +Define the opcode functions: + +```{r} + +adv <- function(machine, operand) { + machine$A <- floor(machine$A / 2^combo(machine, operand)) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +bxl <- function(machine, operand) { + machine$B <- bitwXor(machine$B, operand) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +bst <- function(machine, operand) { + machine$B <- combo(machine, operand) %% 8 + machine$pointer <- machine$pointer + 2 + return(machine) +} + +jnz <- function(machine, operand) { + if (machine$A != 0) + machine$pointer <- operand + else + machine$pointer <- machine$pointer + 2 + return(machine) +} + +bxc <- function(machine, operand) { + machine$B <- bitwXor(machine$B, machine$C) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +out <- function(machine, operand) { + machine$output <- str_c( + machine$output, + combo(machine, operand) %% 8, + sep = "," + ) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +bdv <- function(machine, operand) { + machine$B <- floor(machine$A / 2^combo(machine, operand)) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +cdv <- function(machine, operand) { + machine$C <- floor(machine$A / 2^combo(machine, operand)) + machine$pointer <- machine$pointer + 2 + return(machine) +} + +``` + +Run on puzzle input: + +```{r} + +run_machine(machine) + +``` + + diff --git a/2024/input/day17.txt b/2024/input/day17.txt new file mode 100644 index 0000000..02a6fbe --- /dev/null +++ b/2024/input/day17.txt @@ -0,0 +1,5 @@ +Register A: 64751475 +Register B: 0 +Register C: 0 + +Program: 2,4,1,2,7,5,4,5,1,3,5,5,0,3,3,0 diff --git a/_freeze/2024/R/day17/execute-results/html.json b/_freeze/2024/R/day17/execute-results/html.json new file mode 100644 index 0000000..f65e422 --- /dev/null +++ b/_freeze/2024/R/day17/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "1189b9f3ffd6e66d870d14df1cad1b74", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Day 17\"\ndate: 2024-12-17\nauthor:\n name: https://adventofcode.com/2024/day/17\n url: https://adventofcode.com/2024/day/17\n---\n\n\n\n\n## Setup\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day17.txt\", skip_empty_rows = TRUE) |> \n unglue_data(patterns = c(\n \"{label}: {value}\"\n ))\n```\n:::\n\n\n\n\n## Part 1\n\nInitialize the machine from the text input:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nprogram <- input |> \n filter(label == \"Program\") |> \n pull(value) |> \n str_split_1(\",\") |> \n as.integer()\n\nA <- input |> \n filter(label == \"Register A\") |> \n pull(value) |> \n as.integer()\n\nB <- input |> \n filter(label == \"Register B\") |> \n pull(value) |> \n as.integer()\n\nC <- input |> \n filter(label == \"Register C\") |> \n pull(value) |> \n as.integer()\n\nmachine <- list(program = program, A = A, B = B, C = C, pointer = 0L, output = NULL)\n```\n:::\n\n\n\n\nDefine machine's helper functions:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncombo <- function(machine, operand) {\n case_match(operand,\n 0 ~ 0,\n 1 ~ 1,\n 2 ~ 2,\n 3 ~ 3,\n 4 ~ machine$A,\n 5 ~ machine$B,\n 6 ~ machine$C\n )\n}\n\nrun_opcode <- function(machine, opcode, operand) {\n func <- case_match(opcode, \n 0 ~ \"adv\",\n 1 ~ \"bxl\",\n 2 ~ \"bst\",\n 3 ~ \"jnz\",\n 4 ~ \"bxc\",\n 5 ~ \"out\",\n 6 ~ \"bdv\",\n 7 ~ \"cdv\"\n )\n \n get(func)(machine, operand)\n}\n\nrun_machine <- function(machine) {\n while (machine$pointer < length(machine$program)) {\n opcode <- machine$program[machine$pointer + 1]\n operand <- machine$program[machine$pointer + 2]\n machine <- run_opcode(machine, opcode, operand)\n }\n print(machine$output)\n}\n```\n:::\n\n\n\n\nDefine the opcode functions:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nadv <- function(machine, operand) {\n machine$A <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbxl <- function(machine, operand) {\n machine$B <- bitwXor(machine$B, operand)\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbst <- function(machine, operand) {\n machine$B <- combo(machine, operand) %% 8\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\njnz <- function(machine, operand) {\n if (machine$A != 0) \n machine$pointer <- operand\n else \n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbxc <- function(machine, operand) {\n machine$B <- bitwXor(machine$B, machine$C)\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nout <- function(machine, operand) {\n machine$output <- str_c(\n machine$output, \n combo(machine, operand) %% 8, \n sep = \",\"\n )\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbdv <- function(machine, operand) {\n machine$B <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\ncdv <- function(machine, operand) {\n machine$C <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n```\n:::\n\n\n\n\nRun on puzzle input:\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrun_machine(machine)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"3,1,4,3,1,7,1,6,3\"\n```\n\n\n:::\n:::\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/docs/2022/R/day01.html b/docs/2022/R/day01.html index cb8941d..701db8c 100644 --- a/docs/2022/R/day01.html +++ b/docs/2022/R/day01.html @@ -67,7 +67,7 @@ - + @@ -239,6 +239,12 @@

Day 1

Day 16 + + @@ -865,8 +871,8 @@

Part 2

diff --git a/docs/2024/R/day17.html b/docs/2024/R/day17.html new file mode 100644 index 0000000..af9cc98 --- /dev/null +++ b/docs/2024/R/day17.html @@ -0,0 +1,983 @@ + + + + + + + + + + + +Day 17 – Advent of Code: Worked Solutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + +
+ + + +
+ +
+
+

Day 17

+

Advent of Code: Worked Solutions

+
+ + + +
+ +
+
About
+ +
+ +
+
Date
+
+

December 17, 2024

+
+
+ + +
+ + + +
+ + +
+

Setup

+
+
# Libraries
+library(tidyverse)
+library(unglue)
+
+# Read input from file
+input <- read_lines("../input/day17.txt", skip_empty_rows = TRUE) |> 
+  unglue_data(patterns = c(
+    "{label}: {value}"
+  ))
+
+
+
+

Part 1

+

Initialize the machine from the text input:

+
+
program <- input |> 
+  filter(label == "Program") |> 
+  pull(value) |> 
+  str_split_1(",") |> 
+  as.integer()
+
+A <- input |> 
+  filter(label == "Register A") |> 
+  pull(value) |> 
+  as.integer()
+
+B <- input |> 
+  filter(label == "Register B") |> 
+  pull(value) |> 
+  as.integer()
+
+C <- input |> 
+  filter(label == "Register C") |> 
+  pull(value) |> 
+  as.integer()
+
+machine <- list(program = program, A = A, B = B, C = C, pointer = 0L, output = NULL)
+
+

Define machine’s helper functions:

+
+
combo <- function(machine, operand) {
+  case_match(operand,
+    0 ~ 0,
+    1 ~ 1,
+    2 ~ 2,
+    3 ~ 3,
+    4 ~ machine$A,
+    5 ~ machine$B,
+    6 ~ machine$C
+   )
+}
+
+run_opcode <- function(machine, opcode, operand) {
+  func <- case_match(opcode, 
+    0 ~ "adv",
+    1 ~ "bxl",
+    2 ~ "bst",
+    3 ~ "jnz",
+    4 ~ "bxc",
+    5 ~ "out",
+    6 ~ "bdv",
+    7 ~ "cdv"
+  )
+  
+  get(func)(machine, operand)
+}
+
+run_machine <- function(machine) {
+  while (machine$pointer < length(machine$program)) {
+    opcode  <- machine$program[machine$pointer + 1]
+    operand <- machine$program[machine$pointer + 2]
+    machine <- run_opcode(machine, opcode, operand)
+  }
+  print(machine$output)
+}
+
+

Define the opcode functions:

+
+
adv <- function(machine, operand) {
+  machine$A <- floor(machine$A / 2^combo(machine, operand))
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+bxl <- function(machine, operand) {
+  machine$B <- bitwXor(machine$B, operand)
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+bst <- function(machine, operand) {
+  machine$B <- combo(machine, operand) %% 8
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+jnz <- function(machine, operand) {
+  if (machine$A != 0) 
+    machine$pointer <- operand
+  else 
+    machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+bxc <- function(machine, operand) {
+  machine$B <- bitwXor(machine$B, machine$C)
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+out <- function(machine, operand) {
+  machine$output <- str_c(
+    machine$output, 
+    combo(machine, operand) %% 8, 
+    sep = ","
+  )
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+bdv <- function(machine, operand) {
+  machine$B <- floor(machine$A / 2^combo(machine, operand))
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+cdv <- function(machine, operand) {
+  machine$C <- floor(machine$A / 2^combo(machine, operand))
+  machine$pointer <- machine$pointer + 2
+  return(machine)
+}
+
+

Run on puzzle input:

+
+
run_machine(machine)
+
+
[1] "3,1,4,3,1,7,1,6,3"
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 889a777..d834086 100644 --- a/docs/index.html +++ b/docs/index.html @@ -203,6 +203,12 @@

Advent of Code: Worked Solutions

Day 16 + + diff --git a/docs/search.json b/docs/search.json index 9dda0d3..9dbfc89 100644 --- a/docs/search.json +++ b/docs/search.json @@ -139,509 +139,531 @@ ] }, { - "objectID": "2024/R/day02.html", - "href": "2024/R/day02.html", - "title": "Day 2", + "objectID": "2024/R/day16.html", + "href": "2024/R/day16.html", + "title": "Day 16", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day02.txt\") |> \n map(~parse_number(str_split_1(.x, \" \")))", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day16.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 2" + "Day 16" ] }, { - "objectID": "2024/R/day02.html#setup", - "href": "2024/R/day02.html#setup", - "title": "Day 2", + "objectID": "2024/R/day16.html#setup", + "href": "2024/R/day16.html#setup", + "title": "Day 16", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day02.txt\") |> \n map(~parse_number(str_split_1(.x, \" \")))", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day16.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 2" + "Day 16" ] }, { - "objectID": "2024/R/day02.html#part-1", - "href": "2024/R/day02.html#part-1", - "title": "Day 2", + "objectID": "2024/R/day16.html#part-1", + "href": "2024/R/day16.html#part-1", + "title": "Day 16", "section": "Part 1", - "text": "Part 1\n\n# Compute difference between consecutive integers\nseq_gaps <- function(seq)\n head(lead(seq) - seq, -1)\n\n# Check whether the sequence is incr/decr with gaps between 1 and 3\ngaps_are_valid <- function(gaps)\n (all(gaps < 0) | all(gaps > 0)) & all(between(abs(gaps), 1, 3))\n\n# Count number of safe reports\ninput |> \n map(seq_gaps) |> \n map_lgl(gaps_are_valid) |> \n sum()\n\n[1] 306", + "text": "Part 1\nConvert text input into a weighted, undirected graph\n\n# Convert input to a data frame\ndf <- input |> \n str_split(\"\") |> \n unlist() |> \n as_tibble_col(column_name = \"cell\") |> \n mutate(\n input_id = row_number() - 1,\n row = floor(input_id / length(input)),\n col = floor(input_id %% length(input))\n )\n\n# Convert borders between grid cells to graph vertices and map edges by cell\nborders <- df |> \n mutate(border_e = (cell != \"#\" & lead(cell) != \"#\"), .by = row) |> \n mutate(border_s = (cell != \"#\" & lead(cell) != \"#\"), .by = col) |> \n mutate(\n vtx_id_e = case_when(border_e ~ cumsum(border_e)),\n vtx_id_s = case_when(border_s ~ cumsum(border_s) + max(vtx_id_e, na.rm = T))\n ) |> \n mutate(vtx_id_n = lag(vtx_id_s), .by = col) |> \n mutate(vtx_id_w = lag(vtx_id_e), .by = row) |> \n mutate(\n conn_ns = map2(vtx_id_n, vtx_id_s, ~ na.omit(c(.x, .y))),\n conn_ew = map2(vtx_id_e, vtx_id_w, ~ na.omit(c(.x, .y))),\n conn_ne = map2(vtx_id_n, vtx_id_e, ~ na.omit(c(.x, .y))),\n conn_nw = map2(vtx_id_n, vtx_id_w, ~ na.omit(c(.x, .y))),\n conn_se = map2(vtx_id_s, vtx_id_e, ~ na.omit(c(.x, .y))),\n conn_sw = map2(vtx_id_s, vtx_id_w, ~ na.omit(c(.x, .y))),\n )\n\n# Extract the list of all vertices\nvertices <- c(borders$vtx_id_e, borders$vtx_id_s) |> \n na.omit() |> \n sort()\n\n# Convert vertices and edges to an adjacency matrix\nmtx <- borders |> \n # Unnest lists of edge connections between vertices\n select(starts_with(\"conn\")) |> \n pivot_longer(everything(), names_to = \"conn\", names_prefix = \"conn_\") |> \n unnest_wider(value, names_sep = \"_\") |> \n drop_na(value_1, value_2) |> \n # Rotations get an extra 1k added to the weight\n mutate(weight = case_match(conn, c(\"ns\", \"ew\") ~ 1, .default = 1001)) |> \n select(-conn) |> \n # Convert to matrix format, where unconnected vertices have weight 0\n complete(value_1 = vertices, value_2 = vertices, fill = list(weight = 0)) |> \n arrange(value_1, value_2) |> \n pivot_wider(names_from = value_2, values_from = weight) |> \n column_to_rownames(var = \"value_1\") |> \n as.matrix()\n\n# Make matrix symmetric (for an undirected graph)\nsym_mtx <- pmax(mtx, t(mtx))\n\n# Convert adjacency matrix to a graph\ng <- graph_from_adjacency_matrix(sym_mtx, mode = \"undirected\", weighted = TRUE)\n\nDetermine possible starting and ending locations from the input\n\nspecial_cells <- borders |> \n filter(cell %in% c(\"S\", \"E\")) |> \n select(cell, starts_with(\"vtx_id\")) |> \n pivot_longer(\n starts_with(\"vtx_id\"), \n names_prefix = \"vtx_id_\",\n names_to = \"dir\",\n values_to = \"vertex\"\n ) |> \n drop_na(vertex)\n\n# Create all combinations of start & end cell borders\ncombos <- special_cells |> \n filter(cell == \"S\") |> \n mutate(\n init_rotation = case_match(dir, \"e\" ~ 0, c(\"n\", \"s\") ~ 1, \"w\" ~ 2) * 1000\n ) |>\n select(start_vertex = vertex, init_rotation) |> \n cross_join(\n special_cells |> \n filter(cell == \"E\") |> \n select(end_vertex = vertex)\n )\n\nFind the minimum path distance for each start/end vertex combo:\n\nmin_dist <- combos |> \n mutate(\n dist = map2_int(\n start_vertex, \n end_vertex, \n ~ distances(g, .x, .y)) + init_rotation + 1\n ) |> \n slice_min(dist)\n\nmin_dist |> \n pull(dist)\n\n[1] 102504", "crumbs": [ "2024", - "Day 2" + "Day 16" ] }, { - "objectID": "2024/R/day02.html#part-2", - "href": "2024/R/day02.html#part-2", - "title": "Day 2", + "objectID": "2024/R/day16.html#part-2", + "href": "2024/R/day16.html#part-2", + "title": "Day 16", "section": "Part 2", - "text": "Part 2\n\ntibble(input) |> \n \n # For each report, create a set of versions where each level is removed\n mutate(\n id = row_number(),\n mod = map(input, \\(seq) map(1:length(seq), \\(n) seq[-c(n)])),\n ) |> \n unnest(mod) |> \n \n # Check validity of each report and its altered versions\n mutate(\n report_is_safe = map_lgl(input, ~ gaps_are_valid(seq_gaps(.x))),\n mod_is_safe = map_lgl(mod, ~ gaps_are_valid(seq_gaps(.x))),\n is_safe = report_is_safe | mod_is_safe\n ) |> \n summarize(is_safe = any(is_safe), .by = id) |> \n \n # Count all safe reports\n summarize(total = sum(is_safe)) |> \n pull()\n\n[1] 366", + "text": "Part 2\nPull all paths that have the minimum distance from start to end:\n\nshortest_paths <- min_dist |> \n pmap(function(start_vertex, init_rotation, end_vertex, ...) {\n all_shortest_paths(g, start_vertex, end_vertex)$vpaths\n }) |> \n flatten() |> \n map(as.integer)\n\n\npath_vertices <- shortest_paths |> \n unlist() |> \n unique() |> \n sort()\n\nCount all non-wall cells with a border in the shortest path vertex list:\n\nborders |> \n select(cell, input_id, starts_with(\"vtx_id\")) |> \n pivot_longer(starts_with(\"vtx_id\")) |> \n drop_na(value) |> \n filter(map_lgl(value, ~ .x %in% path_vertices)) |> \n filter(cell != \"#\") |> \n distinct(input_id) |> \n nrow()\n\n[1] 535", "crumbs": [ "2024", - "Day 2" + "Day 16" ] }, { - "objectID": "2024/R/day12.html", - "href": "2024/R/day12.html", - "title": "Day 12", + "objectID": "2024/R/day06.html", + "href": "2024/R/day06.html", + "title": "Day 6", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file into a data frame\ninput <- read_table(\"../input/day12.txt\", col_names = \"chr\") |> \n mutate(\n row = row_number(),\n chr = str_split(chr, \"\")\n ) |> \n unnest(chr) |> \n mutate(col = row_number(), .by = row) |> \n mutate(idx = row_number(), .before = everything())", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 12" + "Day 6" ] }, { - "objectID": "2024/R/day12.html#setup", - "href": "2024/R/day12.html#setup", - "title": "Day 12", + "objectID": "2024/R/day06.html#setup", + "href": "2024/R/day06.html#setup", + "title": "Day 6", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file into a data frame\ninput <- read_table(\"../input/day12.txt\", col_names = \"chr\") |> \n mutate(\n row = row_number(),\n chr = str_split(chr, \"\")\n ) |> \n unnest(chr) |> \n mutate(col = row_number(), .by = row) |> \n mutate(idx = row_number(), .before = everything())", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 12" + "Day 6" ] }, { - "objectID": "2024/R/day12.html#part-1", - "href": "2024/R/day12.html#part-1", - "title": "Day 12", + "objectID": "2024/R/day06.html#part-1", + "href": "2024/R/day06.html#part-1", + "title": "Day 6", "section": "Part 1", - "text": "Part 1\nFormat the input as a graph, with edges connecting neighbors of the same type:\n\n# Flag neighboring characters of the same value that border one other\nedges_wide <- input |> \n mutate(v = case_when(row + 1 == lead(row) ~ lead(idx)), .by = c(chr, col)) |> \n mutate(h = case_when(col + 1 == lead(col) ~ lead(idx)), .by = c(chr, row))\n\nedges_long <- edges_wide |> \n pivot_longer(\n c(v, h), \n names_to = NULL, \n values_to = \"target\", \n values_drop_na = TRUE\n )\n\n# Format neighbors as a list of edges and add to add a graph\ng <- edges_long |> \n transmute(\n edge_id = row_number(),\n src = idx, \n target\n ) |> \n pivot_longer(c(src, target)) |> \n arrange(edge_id, value) |> \n pull(value) |> \n make_graph(n = nrow(input), directed = FALSE)\n\nV(g)$name <- 1:nrow(input)\n\n# Separate out the resulting graph into sub-graphs of innerconnected regions\ndg <- decompose(g)\n\nCompute the perimeter, area, and cost of each subgraph then sum the total:\n\ndg |> \n map_int(\\(subgraph) {\n perim <- sum(4 - degree(subgraph))\n area <- gorder(subgraph)\n perim * area\n }) |> \n sum()\n\n[1] 1433460", + "text": "Part 1\n\n# Guard functions --------------------------------------------------------------\nguards <- c(\"^\", \">\", \"v\", \"<\")\nguard_shift <- c(tail(guards, -1), head(guards, 1))\n\nrotate_guard <- function(cur) guard_shift[guards == cur]\n\nguard_dir <- function(char) {\n case_match(char,\n \"^\" ~ matrix(c(-1, 0), nrow = 1),\n \">\" ~ matrix(c( 0, 1), nrow = 1),\n \"v\" ~ matrix(c( 1, 0), nrow = 1),\n \"<\" ~ matrix(c( 0, -1), nrow = 1)\n )\n}\n\nin_bounds <- function(coord, mtx) {\n between(coord[1], 1, nrow(mtx)) & between(coord[2], 1, ncol(mtx))\n}\n\nmap_path <- function(mtx) {\n\n # Initiate guard's starting position and direction\n cur_char <- keep(mtx, ~ .x %in% guards)\n cur_coord <- which(mtx == cur_char, arr.ind = TRUE)\n cur_dir <- guard_dir(cur_char)\n \n # As long as the guard is in bounds, iteratively update its coords and direction\n repeat {\n next_coord <- cur_coord + cur_dir\n \n # If next step is out-of-bounds, update matrix and exit\n if (!in_bounds(next_coord, mtx)) {\n mtx[cur_coord] <- \"X\"\n break\n }\n # If next step is an obstacle, rotate the guard\n else if (mtx[next_coord] == '#') {\n cur_char <- rotate_guard(cur_char)\n cur_dir <- guard_dir(cur_char)\n }\n # Otherwise advance the guard forward\n else {\n mtx[cur_coord] <- \"X\"\n cur_coord <- next_coord\n }\n }\n \n mtx\n}\n\n\n# Convert input into a matrix\nmtx <- input |> \n str_split(\"\") |> \n unlist() |> \n matrix(nrow = length(input), byrow = TRUE)\n\n# Map the guard's path\nguard_path <- map_path(mtx)\n\n# Count distinct positions visited\nsum(guard_path == \"X\")", "crumbs": [ "2024", - "Day 12" + "Day 6" ] }, { - "objectID": "2024/R/day12.html#part-2", - "href": "2024/R/day12.html#part-2", - "title": "Day 12", + "objectID": "2024/R/day06.html#part-2", + "href": "2024/R/day06.html#part-2", + "title": "Day 6", "section": "Part 2", - "text": "Part 2\nUsed a hint from reddit: the number of corners is equal to the number of sides.\nA plot can have a convex corner or a concave corner.\n\nA cell has a convex corner for each pair of adjacent borders\nA cell has a concave corner if it has two adjacent cells of its same group, but its diagonal cell between the two has a different group.\n\n\n# Get original row/column input and join on the group output from the graph\ngroups <- left_join(\n input,\n imap_dfr(dg, \\(g, grp_idx) tibble(grp = grp_idx, idx = V(g)$name)),\n join_by(idx)\n) |> \n select(idx, grp, row, col)\n\n# For each of a cell's neighbors, flag if they're in the same group\nneighbors <- groups |> \n # Get group number of each adjacent cell (N/S/E/W)\n left_join(transmute(groups, n = grp, row = row + 1, col), join_by(row, col)) |> \n left_join(transmute(groups, w = grp, col = col + 1, row), join_by(row, col)) |> \n left_join(transmute(groups, s = grp, row = row - 1, col), join_by(row, col)) |> \n left_join(transmute(groups, e = grp, col = col - 1, row), join_by(row, col)) |> \n # Get group number of each diagonal cell (NW/NE/SW/SE)\n left_join(transmute(groups, nw = grp, row = row + 1, col = col + 1), join_by(row, col)) |> \n left_join(transmute(groups, ne = grp, row = row + 1, col = col - 1), join_by(row, col)) |> \n left_join(transmute(groups, sw = grp, row = row - 1, col = col + 1), join_by(row, col)) |> \n left_join(transmute(groups, se = grp, row = row - 1, col = col - 1), join_by(row, col)) |> \n select(-c(row, col)) |> \n # Compare group numbers of adjacent/diagonal cells to the current cell\n mutate(across(c(n, w, s, e, nw, ne, sw, se), ~ replace_na(.x == grp, FALSE)))\n\n# Compute total number of concave/convex corners for each cell\ncorners <- neighbors |> \n mutate(\n convex = (!n & !w) + (!s & !w) + (!s & !e) + (!n & !e),\n concave = (n & w & !nw) + (s & w & !sw) + (s & e & !se) + (n & e & !ne)\n )\n\nTotal the number of corners per group and multiply by the group’s area to get the total cost:\n\ncorners |> \n summarize(\n area = n(),\n num_sides = sum(convex + concave), \n .by = grp\n ) |> \n mutate(cost = area * num_sides) |> \n pull(cost) |> \n sum()\n\n[1] 855082", + "text": "Part 2\nChange the path mapping function to test for loops\n\npath_loops <- function(mtx) {\n\n # Initiate guard's starting position and direction\n cur_char <- keep(mtx, ~ .x %in% guards)\n cur_coord <- which(mtx == cur_char, arr.ind = TRUE)\n cur_dir <- guard_dir(cur_char)\n path_hist <- matrix(\"\", nrow(mtx), ncol(mtx))\n\n # As long as the guard is in bounds, iteratively update its coords and direction\n repeat {\n next_coord <- cur_coord + cur_dir\n \n # Check if the guard is looping or if they have left the area\n if (str_detect(path_hist[cur_coord], fixed(cur_char))) \n return(TRUE)\n else if (!in_bounds(next_coord, mtx)) \n return(FALSE)\n \n # If next step is an obstacle, rotate the guard\n else if (mtx[next_coord] == '#') {\n # Update path history\n path_hist[cur_coord] <- str_c(path_hist[cur_coord], cur_char)\n # Update guard\n cur_char <- rotate_guard(cur_char)\n cur_dir <- guard_dir(cur_char)\n }\n # Otherwise advance the guard forward\n else {\n # Update path history\n path_hist[cur_coord] <- str_c(path_hist[cur_coord], cur_char)\n # Update guard\n cur_coord <- next_coord\n }\n }\n}\n\n\n# Create a variation of the map for each possible obstacle location\nobstacles <- which(guard_path == \"X\" & !(mtx %in% guards))\n\n# Test each obstacle location for loops and sum result\nobstacles |> \n map(~ replace(mtx, .x, \"#\")) |> \n map_lgl(path_loops) |> \n sum()", "crumbs": [ "2024", - "Day 12" + "Day 6" ] }, { - "objectID": "2024/R/day07.html", - "href": "2024/R/day07.html", - "title": "Day 7", + "objectID": "2024/R/day13.html", + "href": "2024/R/day13.html", + "title": "Day 13", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\", skip_empty_rows = TRUE) |> \n str_split(\" \") |> \n map(parse_number)", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day13.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 7" + "Day 13" ] }, { - "objectID": "2024/R/day07.html#setup", - "href": "2024/R/day07.html#setup", - "title": "Day 7", + "objectID": "2024/R/day13.html#setup", + "href": "2024/R/day13.html#setup", + "title": "Day 13", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\", skip_empty_rows = TRUE) |> \n str_split(\" \") |> \n map(parse_number)", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day13.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 7" + "Day 13" ] }, { - "objectID": "2024/R/day07.html#part-1", - "href": "2024/R/day07.html#part-1", - "title": "Day 7", + "objectID": "2024/R/day13.html#part-1", + "href": "2024/R/day13.html#part-1", + "title": "Day 13", "section": "Part 1", - "text": "Part 1\nDefine calibration functions:\n\ncalibrate_operators <- function(seq, target, operators) {\n \n # If the end of the list has been reached or the target is already overshot, exit\n if (length(seq) == 1) \n return(seq == target)\n else if (seq[1] > target)\n return(FALSE)\n \n # Recursively compare the first two items of the seq using each operator\n map_lgl(\n operators,\n \\(f) {\n new_start <- get(f)(seq[1], seq[2])\n new_seq <- c(new_start, tail(seq, -2))\n calibrate_operators(new_seq, target, operators)\n }\n ) |> \n # If any output is true, the output has been calibrated.\n any()\n}\n\ncalibration_value <- function(input, output, operators) {\n # Compute calibration for each input-output pair\n is_calibrated <- map2_lgl(\n input, \n output, \n ~ calibrate_operators(.x, .y, operators = operators)\n )\n \n # Sum the calibrated outputs\n output |> \n keep(is_calibrated) |> \n sum() |> \n format(scientific = FALSE)\n}\n\nCompute calibration of the puzzle input:\n\ninput_values <- map(input, tail, -1)\noutput_values <- map_dbl(input, head, 1)\n \ncalibration_value(input_values, output_values, c(\"+\", \"*\"))\n\n[1] \"12940396350192\"", + "text": "Part 1\nExtract numerical values from input text:\n\ndf <- input |> \n unglue_data(c(\n \"Button {button}: X+{x=\\\\d+}, Y+{y=\\\\d+}\",\n \"{button}: X={x=\\\\d+}, Y={y=\\\\d+}\"\n )) |> \n mutate(\n machine_id = floor((row_number() - 1) / 3),\n across(c(x, y), parse_number),\n .before = everything()\n ) |> \n pivot_wider(names_from = button, values_from = c(x, y))\n\nDefine a function to convert numeric equation input and output token counts:\n\ncompute_tokens <- function(df) {\n \n # Convert each machine's properties into a system of equations and solve.\n soln <- df |> \n nest(coeff = c(x_A, x_B, y_A, y_B)) |> \n nest(intercept = c(x_Prize, y_Prize)) |> \n mutate(\n coeff = map(coeff, ~ matrix(as.numeric(.x), nrow = 2, byrow = TRUE)),\n intercept = map(intercept, as.numeric),\n soln = map2(\n coeff, \n intercept, \n ~ solve(.x, .y) |> \n set_names(\"A\", \"B\") |> \n as_tibble_row()\n )\n ) |> \n unnest(soln) |> \n select(machine_id, A, B)\n \n \n # Check that the solution is two whole numbers, then sum the token cost\n soln |> \n mutate(\n across(\n c(A, B), \n ~ near(.x, abs(round(.x)), tol = 0.001), \n .names = \"{.col}_valid\"\n ),\n win = A_valid & B_valid,\n tokens = if_else(win, 3 * A + B, 0)\n ) |> \n pull(tokens) |> \n sum()\n}\n\nRun function on puzzle input:\n\ncompute_tokens(df)\n\n[1] 31623", "crumbs": [ "2024", - "Day 7" + "Day 13" ] }, { - "objectID": "2024/R/day07.html#part-2", - "href": "2024/R/day07.html#part-2", - "title": "Day 7", + "objectID": "2024/R/day13.html#part-2", + "href": "2024/R/day13.html#part-2", + "title": "Day 13", "section": "Part 2", - "text": "Part 2\nAdd a new concatenation operator and re-run the calibration on the puzzle input\n\nconcat <- function(x, y) as.numeric(str_c(x, y))\n\ncalibration_value(input_values, output_values, c(\"+\", \"*\", \"concat\"))\n\n[1] \"106016735664498\"", + "text": "Part 2\nAdd 10000000000000 to each prize intercept and re-compute:\n\ndf |> \n mutate(across(c(x_Prize, y_Prize), ~ .x + 10000000000000)) |> \n compute_tokens() |> \n format(scientific = FALSE)\n\n[1] \"93209116744825\"", "crumbs": [ "2024", - "Day 7" + "Day 13" ] }, { - "objectID": "2024/R/day05.html", - "href": "2024/R/day05.html", - "title": "Day 5", + "objectID": "2024/R/day11.html", + "href": "2024/R/day11.html", + "title": "Day 11", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day05.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\", skip_empty_rows = TRUE) |> \n str_split_1(\" \") |> \n as.integer()", "crumbs": [ "2024", - "Day 5" + "Day 11" ] }, { - "objectID": "2024/R/day05.html#setup", - "href": "2024/R/day05.html#setup", - "title": "Day 5", + "objectID": "2024/R/day11.html#setup", + "href": "2024/R/day11.html#setup", + "title": "Day 11", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day05.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\", skip_empty_rows = TRUE) |> \n str_split_1(\" \") |> \n as.integer()", "crumbs": [ "2024", - "Day 5" + "Day 11" ] }, { - "objectID": "2024/R/day05.html#part-1", - "href": "2024/R/day05.html#part-1", - "title": "Day 5", + "objectID": "2024/R/day11.html#part-1", + "href": "2024/R/day11.html#part-1", + "title": "Day 11", "section": "Part 1", - "text": "Part 1\n\n# Extract page ordering rules from input\nrule_list <- input |> \n keep(~ str_detect(.x, \"\\\\|\")) |> \n as_tibble_col(column_name = \"rule\") |> \n separate(rule, into = c(\"p1\", \"p2\")) |> \n mutate(rule_num = row_number(), .before = everything()) |> \n mutate(across(c(p1, p2), parse_number))\n\n# Extract page sequences from input\npages <- input |> \n discard(~ str_detect(.x, \"\\\\|\")) |> \n str_split(\",\") |> \n map(parse_number) |> \n as_tibble_col(column_name = \"update\")\n\n# Sort a given vector by its applicable rules\nsort_by_rules <- function(seq) {\n active_rules <- rule_list |> \n filter(p1 %in% seq & p2 %in% seq)\n \n repeat {\n swap_occurred <- FALSE\n for (i in 1:nrow(active_rules)) {\n rule <- filter(active_rules, row_number() == i)\n idx1 <- which(seq == rule$p1)\n idx2 <- which(seq == rule$p2)\n \n if (idx1 > idx2) {\n seq[[idx1]] <- rule$p2\n seq[[idx2]] <- rule$p1\n swap_occurred <- TRUE\n }\n }\n if (!swap_occurred) return(seq)\n }\n}\n\n# Sort all page sequences and extract the center page of each result\noutput <- pages |> \n mutate(\n resorted = map(update, sort_by_rules),\n is_sorted = map2_lgl(update, resorted, identical),\n center_page = map_int(resorted, ~ .x[(length(.x) + 1) / 2])\n )\n\n\n# For the properly-ordered updates, sum the center page numbers\noutput |>\n filter(is_sorted) |> \n pull(center_page) |> \n sum()\n\n[1] 6505", + "text": "Part 1\nCreate blink functions:\n\nblink <- function(x) {\n char <- format(x, scientific = FALSE)\n n <- str_length(char)\n \n if (x == 0) \n 1\n else if (n %% 2 == 0)\n parse_number(c(\n str_sub(char, 1, n / 2),\n str_sub(char, n / 2 + 1, n)\n ))\n else \n x * 2024\n}\n\nblink_n <- function(df, n) {\n if (n == 0)\n return(sum(df$n))\n \n df |> \n mutate(stones = map(stones, blink)) |> \n unnest(stones) |> \n summarize(n = sum(n), .by = stones) |> \n blink_n(n - 1)\n}\n\nRun blink function 25 times on puzzle input:\n\ndf <- tibble(stones = input, n = 1)\n\nblink_n(df, 25)\n\n[1] 193899", "crumbs": [ "2024", - "Day 5" + "Day 11" ] }, { - "objectID": "2024/R/day05.html#part-2", - "href": "2024/R/day05.html#part-2", - "title": "Day 5", + "objectID": "2024/R/day11.html#part-2", + "href": "2024/R/day11.html#part-2", + "title": "Day 11", "section": "Part 2", - "text": "Part 2\n\n# For the improperly-ordered updates, sum their sorted center pages\noutput |>\n filter(!is_sorted) |> \n pull(center_page) |> \n sum()\n\n[1] 6897", + "text": "Part 2\nRun blink function 75 times on puzzle input:\n\nblink_n(df, 75) |> \n format(scientific = FALSE)\n\n[1] \"229682160383225\"", "crumbs": [ "2024", - "Day 5" + "Day 11" ] }, { - "objectID": "2024/R/day10.html", - "href": "2024/R/day10.html", - "title": "Day 10", + "objectID": "2024/R/day04.html", + "href": "2024/R/day04.html", + "title": "Day 4", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day10.txt\", skip_empty_rows = TRUE)\n\n# Convert input to matrix format\nmtx <- input |> \n str_split(\"\") |>\n unlist() |> \n as.integer() |> \n matrix(nrow = length(input), byrow = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day04.txt\")", "crumbs": [ "2024", - "Day 10" + "Day 4" ] }, { - "objectID": "2024/R/day10.html#setup", - "href": "2024/R/day10.html#setup", - "title": "Day 10", + "objectID": "2024/R/day04.html#setup", + "href": "2024/R/day04.html#setup", + "title": "Day 4", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day10.txt\", skip_empty_rows = TRUE)\n\n# Convert input to matrix format\nmtx <- input |> \n str_split(\"\") |>\n unlist() |> \n as.integer() |> \n matrix(nrow = length(input), byrow = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day04.txt\")", "crumbs": [ "2024", - "Day 10" + "Day 4" ] }, { - "objectID": "2024/R/day10.html#part-1", - "href": "2024/R/day10.html#part-1", - "title": "Day 10", + "objectID": "2024/R/day04.html#part-1", + "href": "2024/R/day04.html#part-1", + "title": "Day 4", "section": "Part 1", - "text": "Part 1\nDefine custom functions:\n\n# North / South / East / West\ndirs <- list(c(0, 1), c(0, -1), c(1, 0), c(-1, 0))\n\n# Check if a coordinate is in the bounds of the map\nin_bounds <- function(x, map) {\n between(x[1], 1, nrow(map)) & between(x[2], 1, ncol(map))\n}\n\nfind_trail_ends <- function(cur_coord, map) {\n \n # If the trailhead has been reached, return its coordinate.\n if (map[cur_coord] == 9)\n return(list(cur_coord))\n \n # Define the possible next steps within the map bounds\n next_steps <- dirs |> \n map(~ cur_coord + .x) |> \n keep(~ in_bounds(.x, map))\n \n trail_ends <- list()\n \n # If the next step leads to a valid path, add its terminal to the list\n for (next_coord in next_steps) {\n if (map[next_coord] == map[cur_coord] + 1) \n trail_ends <- c(trail_ends, find_trail_ends(next_coord, map))\n }\n \n return(unique(trail_ends))\n}\n\nscore_trail <- function(trailhead, map) length(find_trail_ends(trailhead, map))\n\n\n# Get a list of coordinates of all of the trailheads\ntrailheads <- which(mtx == 0, arr.ind = TRUE)\ntrailheads_list <- map(\n 1:nrow(trailheads), \n ~ array(trailheads[.x,], dim = c(1, 2))\n)\n\n# Score each trailhead and sum the total\ntrailheads_list |> \n map_int(~ score_trail(.x, mtx)) |> \n sum()\n\n[1] 531", + "text": "Part 1\n\n# Convert vector of text to matrix\ntxt_to_mtx <- function(txt) {\n txt |> \n str_split(\"\") |> \n unlist() |> \n matrix(nrow = length(txt), byrow = TRUE)\n}\n\n# Convert matrix to vector of text\nmtx_to_txt <- function(mtx) {\n mtx |> \n t() |> \n as_tibble() |> \n as.list() |> \n map(str_flatten) |> \n unlist() |> \n unname()\n}\n\n# Transpose a vector of text\ntranspose_txt <- function(txt) {\n txt |> \n txt_to_mtx() |> \n t() |> \n mtx_to_txt()\n}\n\n# Get rows and columns of input as individual text vectors\nrows <- input\ncols <- transpose_txt(rows)\n\n# Convert diagonals of input as individual text vectors\npad <- map_chr(\n 1:length(rows), \n ~ str_c(rep_len(\" \", .x - 1), collapse = \"\")\n)\n\ndiag1 <- str_c(pad, rows, rev(pad)) |> \n transpose_txt()\ndiag2 <- str_c(rev(pad), rows, pad) |> \n transpose_txt()\n\n# Loop over rows, columns, and diagnoals and count occurrences of \"XMAS\"\nmap_int(\n list(rows, cols, diag1, diag2),\n ~ sum(str_count(.x, \"XMAS\") + str_count(.x, \"SAMX\"))\n) |> \n sum()\n\n[1] 2599", "crumbs": [ "2024", - "Day 10" + "Day 4" ] }, { - "objectID": "2024/R/day10.html#part-2", - "href": "2024/R/day10.html#part-2", - "title": "Day 10", + "objectID": "2024/R/day04.html#part-2", + "href": "2024/R/day04.html#part-2", + "title": "Day 4", "section": "Part 2", - "text": "Part 2\nModify the trail rating function:\n\nrate_trail <- function(cur_coord, map) {\n # If the trailhead has been reached, increment the score and exit.\n if (map[cur_coord] == 9)\n return(1)\n \n # Define the possible next steps within the map bounds\n next_steps <- dirs |> \n map(~ cur_coord + .x) |> \n keep(~ in_bounds(.x, map))\n \n # Sum the trail ratings of all valid next steps\n map_int(\n next_steps,\n ~ if (map[.x] == map[cur_coord] + 1) rate_trail(.x, map) else 0\n ) |> \n sum()\n}\n\nRe-run the puzzle input:\n\ntrailheads_list |> \n map_int(~ rate_trail(.x, mtx)) |> \n sum()\n\n[1] 1210", + "text": "Part 2\n\n# Define the four possible XMAS patterns in a 3x3 grid as a regex string\nxmas1 <- \"M.S.A.M.S\"\nxmas4 <- \"S.M.A.S.M\"\nxmas2 <- \"S.S.A.M.M\"\nxmas3 <- \"M.M.A.S.S\"\nxmas_regex <- glue::glue(\"{xmas1}|{xmas2}|{xmas3}|{xmas4}\")\n\n# Convert input into a matrix\nmtx <- txt_to_mtx(input)\n\n# Extract every 3x3 submatrix in the input text block as a text string\nmap(\n 1:(nrow(mtx) - 2),\n function(row_start) {\n map_chr(\n 1:(ncol(mtx) - 2),\n function(col_start) {\n mtx[row_start:(row_start + 2), col_start:(col_start + 2)] |> \n mtx_to_txt() |> \n str_flatten()\n }\n )\n }\n) |> \n unlist() |> \n \n # Count the text strings with a valid XMAS pattern\n str_detect(xmas_regex) |> \n sum()\n\n[1] 1948", "crumbs": [ "2024", - "Day 10" + "Day 4" ] }, { - "objectID": "2022/R/day08.html", - "href": "2022/R/day08.html", - "title": "Day 8", + "objectID": "2022/R/day09.html", + "href": "2022/R/day09.html", + "title": "Day 9", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file and format as a matrix\ninput <- read_fwf(\"../input/day08.txt\") |> \n transmute(x = str_split(X1, \"\")) |>\n unnest_wider(x, names_sep = \"\") |>\n mutate(across(everything(), as.integer)) |>\n as.matrix()", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(ctmle, include.only = \"bound\")\n\n# Read input from file\ninput <- read_lines(\"../input/day09.txt\") |>\n str_split(\" \")", "crumbs": [ "2022", - "Day 8" + "Day 9" ] }, { - "objectID": "2022/R/day08.html#setup", - "href": "2022/R/day08.html#setup", - "title": "Day 8", + "objectID": "2022/R/day09.html#setup", + "href": "2022/R/day09.html#setup", + "title": "Day 9", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file and format as a matrix\ninput <- read_fwf(\"../input/day08.txt\") |> \n transmute(x = str_split(X1, \"\")) |>\n unnest_wider(x, names_sep = \"\") |>\n mutate(across(everything(), as.integer)) |>\n as.matrix()", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(ctmle, include.only = \"bound\")\n\n# Read input from file\ninput <- read_lines(\"../input/day09.txt\") |>\n str_split(\" \")", "crumbs": [ "2022", - "Day 8" + "Day 9" ] }, { - "objectID": "2022/R/day08.html#parts-1-2", - "href": "2022/R/day08.html#parts-1-2", - "title": "Day 8", - "section": "Parts 1 & 2", - "text": "Parts 1 & 2\n\n# Create df with one row per tree and variables for its containing row & col\nexpand_grid(\n col_pos = 1:nrow(input),\n row_pos = 1:ncol(input)\n) |>\n mutate(\n tree_idx = row_number(),\n row_list = map(col_pos, ~ unname(as.matrix(input)[.x, ])),\n col_list = map(row_pos, ~ unname(as.matrix(input)[, .x])),\n ) |>\n\n # For each tree, construct its treeline looking outward in each direction\n pivot_longer(\n c(col_pos, row_pos, col_list, row_list),\n names_to = c(\"axis\", \".value\"),\n names_sep = \"_\"\n ) |>\n mutate(\n split = map2(\n list,\n pos,\n ~ split(.x, c(rep(\"bwd\", .y - 1), \"curr_tree\", rep(\"fwd\", length(.x) - .y)))\n )\n ) |>\n unnest_wider(split) |>\n mutate(bwd = map(bwd, rev)) |>\n pivot_longer(c(fwd, bwd), names_to = \"dir\", values_to = \"treeline\") |>\n\n # Check if each is the tallest tree in each direction & count visible trees\n mutate(\n is_tallest = map2_lgl(curr_tree, treeline, ~ all(.x > .y)),\n num_visible = map2_int(curr_tree, treeline, function(curr_tree, treeline) {\n ifelse(\n every(treeline, ~.x < curr_tree),\n length(treeline),\n detect_index(treeline, ~ .x >= curr_tree)\n )\n })\n ) |>\n\n # Summarize visibility & scenic scores from all 4 directions for each tree\n group_by(tree_idx) |>\n summarize(\n is_visible = any(is_tallest),\n scenic_score = prod(num_visible)\n ) |>\n ungroup() |>\n\n # Compute total trees visible from forest edge & max scenic score in forest\n summarize(\n total_visible = sum(is_visible),\n max_scenic_score = max(scenic_score)\n )\n\n# A tibble: 1 × 2\n total_visible max_scenic_score\n <int> <dbl>\n1 1690 535680", + "objectID": "2022/R/day09.html#part-1", + "href": "2022/R/day09.html#part-1", + "title": "Day 9", + "section": "Part 1", + "text": "Part 1\n\n# Convert head movements to sequence of locations on complex plane\nhead_path <- input |>\n map(~ rep(.x[[1]], .x[[2]])) |>\n reduce(c) |>\n recode(\"R\" = 1 + 0i, \"L\" = -1 + 0i, \"U\" = 0 + 1i, \"D\" = 0 - 1i) |>\n accumulate(.init = 0 + 0i, .f = sum)\n\n# Find path of next knot given path of previous knot.\nmove_next_knot <- function(prev_knot_path) {\n accumulate(\n .x = prev_knot_path,\n .f = function(tail = .x, head = .y) {\n diff <- head - tail\n if_else(\n max(abs(Re(diff)), abs(Im(diff))) <= 1,\n tail,\n tail + bound(Re(diff), c(-1, 1)) + bound(Im(diff), c(-1, 1)) * 1i\n )\n }\n )\n}\n\n\nunique_tail_spots <- function(path, num_knots) {\n # Iteratively compute path of each knot from head & to tail\n move_tail <- reduce(map(1:(num_knots - 1), ~ move_next_knot), compose)\n\n # Find number of unique locations in the tail's path\n length(unique(move_tail(path)))\n}\n\n\nunique_tail_spots(head_path, num_knots = 2)\n\n[1] 6197", "crumbs": [ "2022", - "Day 8" + "Day 9" ] }, { - "objectID": "2022/R/day01.html", - "href": "2022/R/day01.html", - "title": "Day 1", - "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day01.txt\") |> \n as.integer()", + "objectID": "2022/R/day09.html#part-2", + "href": "2022/R/day09.html#part-2", + "title": "Day 9", + "section": "Part 2", + "text": "Part 2\n\nunique_tail_spots(head_path, num_knots = 10)\n\n[1] 2562", "crumbs": [ "2022", - "Day 1" + "Day 9" ] }, { - "objectID": "2022/R/day01.html#setup", - "href": "2022/R/day01.html#setup", - "title": "Day 1", + "objectID": "2022/R/day14.html", + "href": "2022/R/day14.html", + "title": "Day 14", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day01.txt\") |> \n as.integer()", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from text\ninput <- read_lines(\"../input/day14.txt\")", "crumbs": [ "2022", - "Day 1" + "Day 14" ] }, { - "objectID": "2022/R/day01.html#part-1", - "href": "2022/R/day01.html#part-1", - "title": "Day 1", + "objectID": "2022/R/day14.html#setup", + "href": "2022/R/day14.html#setup", + "title": "Day 14", + "section": "", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from text\ninput <- read_lines(\"../input/day14.txt\")", + "crumbs": [ + "2022", + "Day 14" + ] + }, + { + "objectID": "2022/R/day14.html#part-1", + "href": "2022/R/day14.html#part-1", + "title": "Day 14", "section": "Part 1", - "text": "Part 1\n\n# Format input as a data frame and number the elves\ndf <- tibble(\n cal = input,\n elf_id = cumsum(is.na(cal)) + 1\n) |>\n filter(!is.na(cal))\n\n# Compute calorie sum for each elf, get the top n elves, and combine totals\ncount_max <- function(df, num_top_elves) {\n df |> \n group_by(elf_id) |>\n summarize(total_cal = sum(cal)) |>\n slice_max(total_cal, n = num_top_elves) |>\n pull(total_cal) |>\n sum()\n}\n\nRun puzzle input:\n\ncount_max(df, 1)\n\n[1] 68787", + "text": "Part 1\nConvert input from rows of text lines to a list of obstacle coordinates:\n\n# Extract numeric values from the input text\ndf <- input |> \n str_split(\" -> \") |> \n imap_dfr(\\(input, idx) tibble(idx, input)) |> \n unglue_unnest(input, \"{x},{y}\", convert = TRUE)\n\n\n# Expand the endpoints into a list of coordinates of every obstacle\nobstacles <- df |> \n mutate(\n seq_x = map2(x, lead(x), ~ if (!is.na(.x) & !is.na(.y)) .x:.y),\n seq_y = map2(y, lead(y), ~ if (!is.na(.x) & !is.na(.y)) .x:.y),\n .by = idx\n ) |> \n unnest(c(seq_x, seq_y)) |> \n distinct(x = seq_x, y = seq_y) |> \n mutate(chr = '#')\n\nDefine a function to recursively drop a grain of sand until it comes to rest:\n\ndrop_grain <- function(scan, x_cur, y_cur) {\n y_new <- y_cur + 1\n \n # Check if new y-coordinate is out of bounds\n if (y_new > max(scan$y))\n return(scan)\n \n for (x_new in c(x_cur, x_cur - 1, x_cur + 1)) {\n # Check if next x-coordinate is out of bounds\n if (!between(x_new, min(scan$x), max(scan$x)))\n return(scan)\n # If grain can flow into the next spot, recurse into next spot\n else if (nrow(filter(scan, x == x_new, y == y_new)) == 0)\n return(drop_grain(scan, x_new, y_new))\n }\n\n # If grain has nowhere to go, drop the grain in its current place and exit\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n}\n\nDefine a function that fills the map with sand one grain at a time, starting from the designated point, until all sand comes to rest:\n\nfill_sand <- function(scan, grain_func) {\n repeat {\n scan_new <- grain_func(scan)\n \n # If the scan is unchanged after dropping the grain, the sand is at rest. \n if (nrow(scan_new) == nrow(scan)) break\n \n scan <- scan_new\n }\n scan\n}\n\ncount_grains <- function(scan) {\n scan |> \n filter(chr == \"o\") |> \n nrow()\n}\n\nCount the grains using the puzzle input:\n\nobstacles |> \n fill_sand(grain_func = partial(drop_grain, x_cur = 500, y_cur = 0)) |> \n count_grains()\n\n[1] 862", "crumbs": [ "2022", - "Day 1" + "Day 14" ] }, { - "objectID": "2022/R/day01.html#part-2", - "href": "2022/R/day01.html#part-2", - "title": "Day 1", + "objectID": "2022/R/day14.html#part-2", + "href": "2022/R/day14.html#part-2", + "title": "Day 14", "section": "Part 2", - "text": "Part 2\n\ncount_max(df, 3)\n\n[1] 198041", + "text": "Part 2\nRe-define the drop_grain function to allow for the boundless floor:\n\ndrop_grain <- function(scan, x_cur, y_cur, floor) {\n y_new <- y_cur + 1\n \n # Check if current location already has a grain of sand (entry blocked)\n if (nrow(filter(scan, x == x_cur, y == y_cur)) > 0)\n return(scan)\n \n # Check if the current sand grain is sitting on top of the floor\n if (y_new == floor)\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n \n # If grain can flow into the next spot, recurse into next spot\n for (x_new in c(x_cur, x_cur - 1, x_cur + 1)) {\n if (nrow(filter(scan, x == x_new, y == y_new)) == 0)\n return(drop_grain(scan, x_new, y_new, floor))\n }\n\n # If grain has nowhere to go, drop the grain in its current place and exit\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n}\n\nCount the grains using the puzzle input:\n\nobstacles |> \n fill_sand(\n grain_func = partial(\n drop_grain, \n x_cur = 500, \n y_cur = 0, \n floor = max(obstacles$y) + 2\n )\n ) |> \n count_grains()\n\n[1] 28744", "crumbs": [ "2022", - "Day 1" + "Day 14" ] }, { - "objectID": "2022/R/day03.html", - "href": "2022/R/day03.html", - "title": "Day 3", + "objectID": "2022/R/day15.html", + "href": "2022/R/day15.html", + "title": "Day 15", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_table(\"../input/day03.txt\", col_names = \"str\")", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\nlibrary(sf)\n\ntheme_set(theme_bw())\n\n# Read input from text and extract numeric values into a data frame\ninput <- read_lines(\"../input/day15.txt\") |> \n unglue_data(\n \"Sensor at x={s_x}, y={s_y}: closest beacon is at x={b_x}, y={b_y}\",\n convert = TRUE\n )", "crumbs": [ "2022", - "Day 3" + "Day 15" ] }, { - "objectID": "2022/R/day03.html#setup", - "href": "2022/R/day03.html#setup", - "title": "Day 3", + "objectID": "2022/R/day15.html#setup", + "href": "2022/R/day15.html#setup", + "title": "Day 15", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_table(\"../input/day03.txt\", col_names = \"str\")", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\nlibrary(sf)\n\ntheme_set(theme_bw())\n\n# Read input from text and extract numeric values into a data frame\ninput <- read_lines(\"../input/day15.txt\") |> \n unglue_data(\n \"Sensor at x={s_x}, y={s_y}: closest beacon is at x={b_x}, y={b_y}\",\n convert = TRUE\n )", "crumbs": [ "2022", - "Day 3" + "Day 15" ] }, { - "objectID": "2022/R/day03.html#part-1", - "href": "2022/R/day03.html#part-1", - "title": "Day 3", + "objectID": "2022/R/day15.html#part-1", + "href": "2022/R/day15.html#part-1", + "title": "Day 15", "section": "Part 1", - "text": "Part 1\n\ninput |>\n mutate(\n # Separate each line into two compartments\n str_length = str_length(str) / 2,\n str_1 = str_sub(str, start = 1L, end = str_length),\n str_2 = str_sub(str, start = str_length + 1, end = -1L),\n\n # Find the letter in common between each pair of compartments\n across(c(str_1, str_2), ~ str_split(.x, \"\")),\n dup = map2_chr(str_1, str_2, ~ intersect(.x, .y)),\n\n # Convert to priority value\n priority = match(dup, c(letters, LETTERS))\n ) |>\n\n # Compute total sum\n pull(priority) |>\n sum()\n\n[1] 8252", + "text": "Part 1\nConvert input into a list of beacons, sensors, and total detection distances:\n\nsensors <- input |> \n distinct(\n s_x, \n s_y, \n max_dist = abs(b_x - s_x) + abs(b_y - s_y)\n )\n\nbeacons <- input |> \n distinct(x = b_x, y = b_y)\n\nGenerate a set of polygons that defines the regions detectible by each sensor:\n\n# Convert each sensor's detection distance into a region defined by a polygon\npoly <- sensors |> \n mutate(\n polygon = pmap(list(s_x, s_y, max_dist), function(x, y, dist) {\n rbind(\n c(x - dist, y),\n c(x, y - dist),\n c(x + dist, y),\n c(x, y + dist),\n c(x - dist, y)\n ) |> \n list() |> \n st_polygon()\n }),\n geometry = st_sfc(polygon)\n ) |> \n transmute(idx = row_number(), geometry) |> \n st_as_sf()\n\n# Merge all polygons into a single geometric shape\npoly_union <- st_union(poly)\n\nVisualize:\n\n# Overlapping regions\nggplot() + \n geom_sf(data = poly, aes(fill = factor(idx))) + \n scale_fill_viridis_d(guide = \"none\")\n\n\n\n\n\n\n\n# Merged region\nggplot() + \n geom_sf(data = poly_union)\n\n\n\n\n\n\n\n\nDefine a set of functions to count the number of integer points that cannot have a beacon within the detection region\n\n# Convert a set of x/y boundaries to a spatial rectangle object\npoly_rect <- function(xmin, xmax, ymin, ymax) {\n rbind(\n c(xmin, ymax), \n c(xmin, ymin), \n c(xmax, ymin), \n c(xmax, ymax), \n c(xmin, ymax)\n ) |> \n list() |> \n st_polygon() |> \n st_sfc()\n}\n\n# Get the coordinates within a poly, optionally limited within x/y bounds\nsf_points_in_poly <- function(poly, xlim = NULL, ylim = NULL) {\n \n # Define a rectangular region within which to generate grid points\n points_region <- poly_rect(\n xmin = (if (is_null(xlim)) st_bbox(poly)$xmin else head(xlim, 1)) - 0.5, \n xmax = (if (is_null(xlim)) st_bbox(poly)$xmax else tail(xlim, 1)) + 0.5, \n ymin = (if (is_null(ylim)) st_bbox(poly)$ymin else head(ylim, 1)) - 0.5,\n ymax = (if (is_null(ylim)) st_bbox(poly)$ymax else tail(ylim, 1)) + 0.5\n )\n \n # Generate the grid points that sit within the polygon\n points_region |> \n st_make_grid(cellsize = 1, what = \"centers\") |> \n st_intersection(poly) |> \n \n # Convert the set of points from spatial objects to x-y coordinates\n st_coordinates() |> \n as_tibble() |> \n mutate(across(everything(), as.integer)) |> \n rename_with(tolower)\n}\n\n# Count the points in a sf region (with optional x/y lims) that can't be a beacon\ncount_nonbeacon <- function(detection_region, known_beacons, x = NULL, y = NULL) {\n \n # Get the set of integer points within the polygon and x-y region specified\n detection_region |> \n sf_points_in_poly(xlim = x, ylim = y) |>\n \n # Remove known beacons from the list of points and count\n anti_join(known_beacons, join_by(x, y)) |>\n nrow()\n}\n\nRun puzzle input:\n\ncount_nonbeacon(poly_union, beacons, y = 2000000)\n\n[1] 5367037", "crumbs": [ "2022", - "Day 3" + "Day 15" ] }, { - "objectID": "2022/R/day03.html#part-2", - "href": "2022/R/day03.html#part-2", - "title": "Day 3", + "objectID": "2022/R/day15.html#part-2", + "href": "2022/R/day15.html#part-2", + "title": "Day 15", "section": "Part 2", - "text": "Part 2\n\ninput |>\n\n # Reshape to one row per group, one column per elf\n mutate(\n str = str_split(str, \"\"),\n group_num = floor((row_number() - 1) / 3),\n elf_num = as.character(row_number() %% 3)\n ) |>\n pivot_wider(names_from = elf_num, values_from = str, names_prefix = \"elf_\") |>\n\n # Find the character in common between all 3 elves & convert to priority val\n mutate(\n dup = pmap_chr(\n list(elf_0, elf_1, elf_2),\n ~ reduce(list(..1, ..2, ..3), intersect)\n ),\n priority = match(dup, c(letters, LETTERS))\n ) |>\n\n # Compute total sum\n pull(priority) |>\n sum()\n\n[1] 2828", + "text": "Part 2\nDefine a function to get the location of an undetected beacon within a viewport:\n\nfind_undetected_beacon <- function(detection_region, xlim, ylim) {\n boundary <- poly_rect(xlim[1], xlim[2], ylim[1], ylim[2])\n \n # Find the polygon region where an undetected beacon could occur\n undetected_region <- st_difference(boundary, detection_region)\n \n # Get all integer points in the region\n points <- sf_points_in_poly(undetected_region)\n \n # Compute the region's boundary points to exclude\n undetected_region_boundary <- undetected_region |>\n st_bbox() |>\n as.list() |>\n pmap(\\(xmin, xmax, ymin, ymax) poly_rect(xmin, xmax, ymin, ymax)) |>\n pluck(1) |> \n st_difference(undetected_region) |> \n sf_points_in_poly()\n \n # Exclude all boundary points from the region\n anti_join(points, undetected_region_boundary, join_by(x, y))\n \n}\n\ntuning_freq <- function(x, y) format(4000000 * x + y, scientific = FALSE)\n\nRun on puzzle input:\n\npoint <- find_undetected_beacon(poly_union, c(0, 4000000), c(0, 4000000))\n\ntuning_freq(point$x, point$y)\n\n[1] \"11914583249288\"", "crumbs": [ "2022", - "Day 3" + "Day 15" ] }, { - "objectID": "2022/R/day06.html", - "href": "2022/R/day06.html", - "title": "Day 6", + "objectID": "2022/R/day02.html", + "href": "2022/R/day02.html", + "title": "Day 2", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\") |>\n str_split_1(\"\") |>\n enframe(name = \"idx\", value = \"char\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_fwf(\"../input/day02.txt\", col_types = \"c\")", "crumbs": [ "2022", - "Day 6" + "Day 2" ] }, { - "objectID": "2022/R/day06.html#setup", - "href": "2022/R/day06.html#setup", - "title": "Day 6", + "objectID": "2022/R/day02.html#setup", + "href": "2022/R/day02.html#setup", + "title": "Day 2", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\") |>\n str_split_1(\"\") |>\n enframe(name = \"idx\", value = \"char\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_fwf(\"../input/day02.txt\", col_types = \"c\")", "crumbs": [ "2022", - "Day 6" + "Day 2" ] }, { - "objectID": "2022/R/day06.html#part-1", - "href": "2022/R/day06.html#part-1", - "title": "Day 6", + "objectID": "2022/R/day02.html#part-1", + "href": "2022/R/day02.html#part-1", + "title": "Day 2", "section": "Part 1", - "text": "Part 1\n\nfind_marker <- function(df, marker_length) {\n df |> \n # Construct sequences of next n chars and count # of unique chars in each\n transmute(\n marker_idx = idx + marker_length - 1,\n char_seq = reduce(\n .x = map(0:(marker_length - 1), ~ lead(char, n = .x)),\n .f = str_c\n ),\n n_unique = map_int(\n char_seq,\n ~ .x |>\n str_split(\"\") |>\n unlist() |>\n unique() |>\n length()\n )\n ) |>\n\n # Extract first instance where all n chars are unique\n filter(n_unique == marker_length) |>\n pull(marker_idx) |>\n min()\n}\n\n\nfind_marker(input, marker_length = 4)\n\n[1] 1802", + "text": "Part 1\n\n# Format shapes/strategies as numbers 1-3 for modular arithmetic\ndf <- tibble(\n opponent = as.numeric(factor(input$X1, levels = c(\"A\", \"B\", \"C\"))),\n strategy = as.numeric(factor(input$X2, levels = c(\"X\", \"Y\", \"Z\")))\n)\n\nscore_shape <- function(df) {\n df |> \n mutate(\n self = strategy,\n outcome = (self - opponent + 1) %% 3 * 3,\n score = self + outcome\n ) |>\n pull(score) |>\n sum()\n}\n\nRun puzzle input:\n\nscore_shape(df)\n\n[1] 15422", "crumbs": [ "2022", - "Day 6" + "Day 2" ] }, { - "objectID": "2022/R/day06.html#part-2", - "href": "2022/R/day06.html#part-2", - "title": "Day 6", + "objectID": "2022/R/day02.html#part-2", + "href": "2022/R/day02.html#part-2", + "title": "Day 2", "section": "Part 2", - "text": "Part 2\n\nfind_marker(input, marker_length = 14)\n\n[1] 3551", + "text": "Part 2\n\nscore_outcome <- function(df) {\n df |> \n mutate(\n self = (opponent + strategy) %% 3 + 1,\n outcome = (strategy - 1) * 3,\n score = self + outcome\n ) |>\n pull(score) |>\n sum()\n}\n\n\nscore_outcome(df)\n\n[1] 15442", "crumbs": [ "2022", - "Day 6" + "Day 2" ] }, { - "objectID": "2022/R/day13.html", - "href": "2022/R/day13.html", - "title": "Day 13", + "objectID": "2022/R/day12.html", + "href": "2022/R/day12.html", + "title": "Day 12", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input into a data frame and convert to indexed nested lists\ndf <- read_table(\"../input/day13.txt\", col_names = \"txt\") |> \n mutate(\n row_id = row_number(),\n group_id = floor((row_id - 1) / 2) + 1,\n item_id = (row_id - 1) %% 2 + 1,\n lst = map(txt, jsonlite::fromJSON, simplifyVector = FALSE)\n )", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day12.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2022", - "Day 13" + "Day 12" ] }, { - "objectID": "2022/R/day13.html#setup", - "href": "2022/R/day13.html#setup", - "title": "Day 13", + "objectID": "2022/R/day12.html#setup", + "href": "2022/R/day12.html#setup", + "title": "Day 12", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input into a data frame and convert to indexed nested lists\ndf <- read_table(\"../input/day13.txt\", col_names = \"txt\") |> \n mutate(\n row_id = row_number(),\n group_id = floor((row_id - 1) / 2) + 1,\n item_id = (row_id - 1) %% 2 + 1,\n lst = map(txt, jsonlite::fromJSON, simplifyVector = FALSE)\n )", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day12.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2022", - "Day 13" + "Day 12" ] }, { - "objectID": "2022/R/day13.html#part-1", - "href": "2022/R/day13.html#part-1", - "title": "Day 13", + "objectID": "2022/R/day12.html#part-1", + "href": "2022/R/day12.html#part-1", + "title": "Day 12", "section": "Part 1", - "text": "Part 1\nDefine a function to compare nested lists:\n\ncompare_nested <- function(a, b) {\n\n # Compare if both inputs are numeric\n if (is.numeric(a) & is.numeric(b)) {\n if (a < b) return(-1)\n if (a > b) return(1)\n if (a == b) return(0)\n }\n\n # Compare if only one input is numeric\n if (is.numeric(a) != is.numeric(b)) {\n if (is.numeric(a))\n return(compare_nested(list(a), b))\n if (is.numeric(b))\n return(compare_nested(a, list(b)))\n }\n\n # Compare if both inputs are lists\n i <- 1\n while (i <= min(length(a), length(b))) {\n result <- compare_nested(a[[i]], b[[i]])\n if (result %in% c(1, -1))\n return(result)\n i <- i + 1\n }\n # When all comparable values are equal, compare lengths\n return(compare_nested(length(a), length(b)))\n}\n\nSum the indices of packet pairs that are in order:\n\ndf |>\n select(group_id, item_id, lst) |>\n pivot_wider(\n names_from = item_id,\n names_prefix = \"item_\",\n values_from = lst\n ) |>\n mutate(comparison = map2_int(item_1, item_2, compare_nested)) |>\n filter(comparison == -1) |>\n pull(group_id) |> \n sum()\n\n[1] 5684", + "text": "Part 1\nReformat input as a data frame of coordinates and elevations:\n\ndf <- input |>\n str_split(\"\") |>\n unlist() |>\n as_tibble() |>\n transmute(\n id = row_number(),\n letter = value,\n elevation = case_when(\n letter == \"S\" ~ Inf,\n letter == \"E\" ~ -Inf,\n .default = match(letter, letters)\n ),\n row = floor((id - 1) / str_length(input[1]) + 1),\n col = (id - 1) %% str_length(input[1]) + 1\n )\n\n\ndf_to_graph <- function(df) {\n\n # Flag whether each neighbor of each vertex is walkable\n neighbors <- df |>\n mutate(up = lag(id), down = lead(id), .by = col) |>\n mutate(left = lag(id), right = lead(id), .by = row) |>\n mutate(\n across(\n c(up, down, left, right),\n ~ elevation[.x],\n .names = \"{.col}_elev\"\n ),\n across(\n ends_with(\"_elev\"),\n ~ (.x - elevation) <= 1,\n .names = \"{str_remove(.col, '_elev')}_walkable\"\n )\n ) |>\n rename_with(.cols = c(up, down, left, right), ~ str_c(.x, \"_idx\")) |>\n select(source_idx = id, ends_with(c(\"idx\", \"walkable\")))\n\n # Construct a list of edges\n edge_list <- neighbors |>\n pivot_longer(\n !source_idx,\n names_to = c(\"target_dir\", \".value\"),\n names_sep = \"_\"\n ) |>\n rename(\n target_idx = idx,\n target_walkable = walkable\n ) |>\n filter(target_walkable == TRUE) |>\n pmap(function(source_idx, target_idx, ...) { c(source_idx, target_idx) }) |>\n unlist()\n\n # Convert to a directed graph\n g <- make_empty_graph() |>\n add_vertices(length(df$id)) |>\n add_edges(edge_list)\n\n}\n\nshortest_path_length <- function(g, source_idx, target_idx) {\n shortest_paths(g, from = source_idx, to = target_idx)$vpath[[1]] |>\n length() - 1\n}\n\n\ng <- df_to_graph(df)\n\n\n# Get the indices of the start and end vertices\nidx_start <- match(\"S\", df$letter)\nidx_end <- match(\"E\", df$letter)\n\n# Compute shortest path from start to end\nshortest_path_length(g, idx_start, idx_end)\n\n[1] 462", "crumbs": [ "2022", - "Day 13" + "Day 12" ] }, { - "objectID": "2022/R/day13.html#part-2", - "href": "2022/R/day13.html#part-2", - "title": "Day 13", + "objectID": "2022/R/day12.html#part-2", + "href": "2022/R/day12.html#part-2", + "title": "Day 12", "section": "Part 2", - "text": "Part 2\nDefine a function to determine the pairwise order of nested lists:\n\nsort_nested <- function(lst) {\n n <- length(lst)\n indices <- 1:n\n\n if (n == 0) return()\n if (n == 1) return(indices)\n\n # Bubble sort: loop through list and swap elements until sorted\n repeat {\n swap_occurred <- FALSE\n for (i in 1:(n - 1)) {\n j1 <- which(indices == i)\n j2 <- which(indices == i + 1)\n if (compare_nested(lst[[j1]], lst[[j2]]) == 1) {\n indices[j1] <- i + 1\n indices[j2] <- i\n swap_occurred <- TRUE\n }\n }\n if (!swap_occurred) break\n }\n return(indices)\n}\n\nAdd new flagged packets, sort all, and multiply flagged indices:\n\nnew_packets <- list(\"[[2]]\", \"[[6]]\") |>\n map(jsonlite::fromJSON, simplifyVector = FALSE) |>\n as_tibble_col(column_name = \"lst\")\n\ndf |>\n transmute(lst, flag = FALSE) |>\n add_row(new_packets, flag = TRUE) |>\n mutate(ord = sort_nested(lst)) |>\n filter(flag) |>\n pull(ord) |> \n prod()\n\n[1] 22932", + "text": "Part 2\n\n# Loop over all starting locations and find the shortest path to the end\nmin_dist <- Inf\nfor (i in c(idx_start, which(df$letter == \"a\"))) {\n cur <- shortest_path_length(g, i, idx_end)\n if (cur >= 0 & cur < min_dist) {\n min_dist <- cur\n }\n}\nmin_dist\n\n[1] 451", "crumbs": [ "2022", - "Day 13" + "Day 12" ] }, { - "objectID": "2022/R/day11.html", - "href": "2022/R/day11.html", - "title": "Day 11", + "objectID": "2022/R/day07.html", + "href": "2022/R/day07.html", + "title": "Day 7", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\") |>\n trimws() |>\n discard(~ .x == \"\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\") |> \n enframe(name = NULL)", "crumbs": [ "2022", - "Day 11" + "Day 7" ] }, { - "objectID": "2022/R/day11.html#setup", - "href": "2022/R/day11.html#setup", - "title": "Day 11", + "objectID": "2022/R/day07.html#setup", + "href": "2022/R/day07.html#setup", + "title": "Day 7", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\") |>\n trimws() |>\n discard(~ .x == \"\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\") |> \n enframe(name = NULL)", "crumbs": [ "2022", - "Day 11" + "Day 7" ] }, { - "objectID": "2022/R/day11.html#part-1", - "href": "2022/R/day11.html#part-1", - "title": "Day 11", + "objectID": "2022/R/day07.html#part-1", + "href": "2022/R/day07.html#part-1", + "title": "Day 7", "section": "Part 1", - "text": "Part 1\n\n# Reformat input\nitems <- str_match(input, \"Starting items:(.*)\")[,2] |>\n discard(is.na) |>\n str_split(\",\") |>\n map(parse_number)\n\noperations <- input |>\n keep(~ str_detect(.x, \"Operation:\")) |>\n str_replace(\"Operation: new = \", \"~ \") |>\n str_replace_all(\"old\", \".x\") |>\n map(~ rlang::as_function(as.formula(.x)))\n\ndiv <- parse_number(keep(input, ~ str_detect(.x, \"Test:\")))\ndivt <- parse_number(keep(input, ~ str_detect(.x, \"If true:\")))\ndivf <- parse_number(keep(input, ~ str_detect(.x, \"If false:\")))\n\ntest <- pmap(\n list(div, divt, divf),\n ~ function(x) if_else(x %% ..1 == 0, ..2 + 1, ..3 + 1)\n)\n\nnum_monkeys <- length(input) / 6\n\n\ncompute_monkey_business <- function(num_rounds, worry_func) {\n # Initialize\n activity <- rep(0, num_monkeys)\n\n # Perform the tosses\n for (round in 1:num_rounds) {\n for (monkey in 1:num_monkeys) {\n for (item in items[[monkey]]) {\n worry <- worry_func(operations[[monkey]](item))\n toss <- test[[monkey]](worry)\n items[[toss]] <- c(items[[toss]], worry)\n }\n activity[[monkey]] <- activity[[monkey]] + length(items[[monkey]])\n items[[monkey]] <- numeric(0)\n }\n }\n\n # Compute monkey business score\n activity |>\n sort() |>\n tail(2) |>\n reduce(`*`)\n}\n\n\ncompute_monkey_business(num_rounds = 20, worry_func = \\(x) floor(x / 3))\n\n[1] 78678", + "text": "Part 1\n\ndir_sizes <- input |> \n mutate(\n\n # Determine path of each file by accumulating preceding cd terms\n path = value |>\n str_extract(\"(?<=^\\\\$ cd ).*\") |>\n str_c(\"/\") |>\n replace_na(\"\") |>\n accumulate(\n ~ if (.y == \"../\") {\n str_remove(.x, \"(?<=/)[a-z]+/$\")\n } else {\n str_c(.x, .y)\n }\n ) |>\n str_remove_all(\"^/|/$\"),\n\n # Convert paths to lists of all containing directories\n dirs = path |>\n str_split(\"/\") |>\n map(~accumulate(.x, str_c, sep = \"/\"))\n ) |>\n\n # Remove commands & directories from output and format file info as cols\n filter(!str_detect(value, \"^\\\\$|dir \")) |>\n separate(value, into = c(\"size\", \"file\"), sep = \" \") |>\n mutate(size = as.integer(size)) |>\n\n # Convert nested lists of directories to long-format\n unnest_wider(dirs, names_sep = \"_\") |>\n mutate(dirs_1 = \"/\") |>\n pivot_longer(\n cols = matches(\"dirs_\\\\d+\"),\n names_to = NULL,\n values_to = \"dir\",\n values_drop_na = TRUE\n ) |>\n\n # Compute size of each directory\n group_by(dir) |>\n summarize(size = sum(size)) |>\n ungroup()\n\nSum sizes of all directories with maximum size 100000:\n\ndir_sizes |>\n filter(size <= 100000) |>\n pull(size) |>\n sum()\n\n[1] 1243729", "crumbs": [ "2022", - "Day 11" + "Day 7" ] }, { - "objectID": "2022/R/day11.html#part-2", - "href": "2022/R/day11.html#part-2", - "title": "Day 11", + "objectID": "2022/R/day07.html#part-2", + "href": "2022/R/day07.html#part-2", + "title": "Day 7", "section": "Part 2", - "text": "Part 2\n\nlcm <- DescTools::LCM(div)\ncompute_monkey_business(num_rounds = 10000, worry_func = \\(x) x %% lcm)\n\n[1] 15333249714", + "text": "Part 2\nFind size of the smallest necessary directory to delete:\n\ncurr_system_size <- dir_sizes |>\n filter(dir == \"/\") |>\n pull(size)\n\ndir_sizes |>\n filter(size >= (30000000 - (70000000 - curr_system_size))) |>\n slice_min(size) |>\n pull(size)\n\n[1] 4443914", "crumbs": [ "2022", - "Day 11" + "Day 7" ] }, { - "objectID": "2022/R/day04.html", - "href": "2022/R/day04.html", - "title": "Day 4", + "objectID": "2022/R/day05.html", + "href": "2022/R/day05.html", + "title": "Day 5", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_csv(\"../input/day04.txt\", col_names = FALSE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\npath <- \"../input/day05.txt\"\ninput <- read_lines(path)", "crumbs": [ "2022", - "Day 4" + "Day 5" ] }, { - "objectID": "2022/R/day04.html#setup", - "href": "2022/R/day04.html#setup", - "title": "Day 4", + "objectID": "2022/R/day05.html#setup", + "href": "2022/R/day05.html#setup", + "title": "Day 5", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_csv(\"../input/day04.txt\", col_names = FALSE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\npath <- \"../input/day05.txt\"\ninput <- read_lines(path)", "crumbs": [ "2022", - "Day 4" + "Day 5" ] }, { - "objectID": "2022/R/day04.html#parts-1-2", - "href": "2022/R/day04.html#parts-1-2", - "title": "Day 4", - "section": "Parts 1 & 2", - "text": "Parts 1 & 2\n\ninput |> \n separate(X1, into = c(\"start1\", \"end1\"), sep = \"-\", convert = TRUE) |>\n separate(X2, into = c(\"start2\", \"end2\"), sep = \"-\", convert = TRUE) |>\n mutate(\n range1 = map2(start1, end1, ~ .x:.y),\n range2 = map2(start2, end2, ~ .x:.y),\n contained = map2_lgl(range1, range2, ~ all(.x %in% .y) | all(.y %in% .x)),\n overlap = map2_lgl(range1, range2, ~ length(intersect(.x, .y)) > 0)\n ) |>\n summarize(num_contained = sum(contained), num_overlap = sum(overlap))\n\n# A tibble: 1 × 2\n num_contained num_overlap\n <int> <int>\n1 550 931", + "objectID": "2022/R/day05.html#part-1", + "href": "2022/R/day05.html#part-1", + "title": "Day 5", + "section": "Part 1", + "text": "Part 1\n\n# Format raw input\nmoves <- input |>\n tail_while(~ .x != \"\") |>\n str_extract_all(\"\\\\d+\") |>\n map(as.integer)\n\nstacks <- read_fwf(\n path,\n n_max = length(input) - length(moves) - 2,\n col_types = \"c\"\n) |>\n mutate(across(everything(), ~ str_extract(.x, \"[A-Z]\"))) |>\n as.list() |>\n map(discard, is.na) |>\n map(rev)\n\nmove_crates <- function(moves, stacks, func) {\n \n # Execute moves\n for (curr_move in moves) {\n count <- curr_move[1]\n from <- curr_move[2]\n to <- curr_move[3]\n \n crates <- func(tail(stacks[[from]], count))\n\n stacks[[to]] <- append(stacks[[to]], crates)\n stacks[[from]] <- head(stacks[[from]], -1 * count)\n }\n\n # Examine final top row of crates\n stacks |>\n map(~ tail(.x, 1)) |>\n str_c(collapse = \"\")\n}\n\n\nmove_crates(moves, stacks, rev)\n\n[1] \"RLFNRTNFB\"", "crumbs": [ "2022", - "Day 4" + "Day 5" + ] + }, + { + "objectID": "2022/R/day05.html#part-2", + "href": "2022/R/day05.html#part-2", + "title": "Day 5", + "section": "Part 2", + "text": "Part 2\n\nmove_crates(moves, stacks, identity)\n\n[1] \"MHQTLJRLB\"", + "crumbs": [ + "2022", + "Day 5" ] }, { @@ -689,531 +711,542 @@ ] }, { - "objectID": "2022/R/day05.html", - "href": "2022/R/day05.html", - "title": "Day 5", + "objectID": "2022/R/day04.html", + "href": "2022/R/day04.html", + "title": "Day 4", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\npath <- \"../input/day05.txt\"\ninput <- read_lines(path)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_csv(\"../input/day04.txt\", col_names = FALSE)", "crumbs": [ "2022", - "Day 5" + "Day 4" ] }, { - "objectID": "2022/R/day05.html#setup", - "href": "2022/R/day05.html#setup", - "title": "Day 5", + "objectID": "2022/R/day04.html#setup", + "href": "2022/R/day04.html#setup", + "title": "Day 4", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\npath <- \"../input/day05.txt\"\ninput <- read_lines(path)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_csv(\"../input/day04.txt\", col_names = FALSE)", "crumbs": [ "2022", - "Day 5" + "Day 4" ] }, { - "objectID": "2022/R/day05.html#part-1", - "href": "2022/R/day05.html#part-1", - "title": "Day 5", - "section": "Part 1", - "text": "Part 1\n\n# Format raw input\nmoves <- input |>\n tail_while(~ .x != \"\") |>\n str_extract_all(\"\\\\d+\") |>\n map(as.integer)\n\nstacks <- read_fwf(\n path,\n n_max = length(input) - length(moves) - 2,\n col_types = \"c\"\n) |>\n mutate(across(everything(), ~ str_extract(.x, \"[A-Z]\"))) |>\n as.list() |>\n map(discard, is.na) |>\n map(rev)\n\nmove_crates <- function(moves, stacks, func) {\n \n # Execute moves\n for (curr_move in moves) {\n count <- curr_move[1]\n from <- curr_move[2]\n to <- curr_move[3]\n \n crates <- func(tail(stacks[[from]], count))\n\n stacks[[to]] <- append(stacks[[to]], crates)\n stacks[[from]] <- head(stacks[[from]], -1 * count)\n }\n\n # Examine final top row of crates\n stacks |>\n map(~ tail(.x, 1)) |>\n str_c(collapse = \"\")\n}\n\n\nmove_crates(moves, stacks, rev)\n\n[1] \"RLFNRTNFB\"", - "crumbs": [ - "2022", - "Day 5" - ] - }, - { - "objectID": "2022/R/day05.html#part-2", - "href": "2022/R/day05.html#part-2", - "title": "Day 5", - "section": "Part 2", - "text": "Part 2\n\nmove_crates(moves, stacks, identity)\n\n[1] \"MHQTLJRLB\"", + "objectID": "2022/R/day04.html#parts-1-2", + "href": "2022/R/day04.html#parts-1-2", + "title": "Day 4", + "section": "Parts 1 & 2", + "text": "Parts 1 & 2\n\ninput |> \n separate(X1, into = c(\"start1\", \"end1\"), sep = \"-\", convert = TRUE) |>\n separate(X2, into = c(\"start2\", \"end2\"), sep = \"-\", convert = TRUE) |>\n mutate(\n range1 = map2(start1, end1, ~ .x:.y),\n range2 = map2(start2, end2, ~ .x:.y),\n contained = map2_lgl(range1, range2, ~ all(.x %in% .y) | all(.y %in% .x)),\n overlap = map2_lgl(range1, range2, ~ length(intersect(.x, .y)) > 0)\n ) |>\n summarize(num_contained = sum(contained), num_overlap = sum(overlap))\n\n# A tibble: 1 × 2\n num_contained num_overlap\n <int> <int>\n1 550 931", "crumbs": [ "2022", - "Day 5" + "Day 4" ] }, { - "objectID": "2022/R/day07.html", - "href": "2022/R/day07.html", - "title": "Day 7", + "objectID": "2022/R/day11.html", + "href": "2022/R/day11.html", + "title": "Day 11", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\") |> \n enframe(name = NULL)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\") |>\n trimws() |>\n discard(~ .x == \"\")", "crumbs": [ "2022", - "Day 7" + "Day 11" ] }, { - "objectID": "2022/R/day07.html#setup", - "href": "2022/R/day07.html#setup", - "title": "Day 7", + "objectID": "2022/R/day11.html#setup", + "href": "2022/R/day11.html#setup", + "title": "Day 11", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\") |> \n enframe(name = NULL)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\") |>\n trimws() |>\n discard(~ .x == \"\")", "crumbs": [ "2022", - "Day 7" + "Day 11" ] }, { - "objectID": "2022/R/day07.html#part-1", - "href": "2022/R/day07.html#part-1", - "title": "Day 7", + "objectID": "2022/R/day11.html#part-1", + "href": "2022/R/day11.html#part-1", + "title": "Day 11", "section": "Part 1", - "text": "Part 1\n\ndir_sizes <- input |> \n mutate(\n\n # Determine path of each file by accumulating preceding cd terms\n path = value |>\n str_extract(\"(?<=^\\\\$ cd ).*\") |>\n str_c(\"/\") |>\n replace_na(\"\") |>\n accumulate(\n ~ if (.y == \"../\") {\n str_remove(.x, \"(?<=/)[a-z]+/$\")\n } else {\n str_c(.x, .y)\n }\n ) |>\n str_remove_all(\"^/|/$\"),\n\n # Convert paths to lists of all containing directories\n dirs = path |>\n str_split(\"/\") |>\n map(~accumulate(.x, str_c, sep = \"/\"))\n ) |>\n\n # Remove commands & directories from output and format file info as cols\n filter(!str_detect(value, \"^\\\\$|dir \")) |>\n separate(value, into = c(\"size\", \"file\"), sep = \" \") |>\n mutate(size = as.integer(size)) |>\n\n # Convert nested lists of directories to long-format\n unnest_wider(dirs, names_sep = \"_\") |>\n mutate(dirs_1 = \"/\") |>\n pivot_longer(\n cols = matches(\"dirs_\\\\d+\"),\n names_to = NULL,\n values_to = \"dir\",\n values_drop_na = TRUE\n ) |>\n\n # Compute size of each directory\n group_by(dir) |>\n summarize(size = sum(size)) |>\n ungroup()\n\nSum sizes of all directories with maximum size 100000:\n\ndir_sizes |>\n filter(size <= 100000) |>\n pull(size) |>\n sum()\n\n[1] 1243729", + "text": "Part 1\n\n# Reformat input\nitems <- str_match(input, \"Starting items:(.*)\")[,2] |>\n discard(is.na) |>\n str_split(\",\") |>\n map(parse_number)\n\noperations <- input |>\n keep(~ str_detect(.x, \"Operation:\")) |>\n str_replace(\"Operation: new = \", \"~ \") |>\n str_replace_all(\"old\", \".x\") |>\n map(~ rlang::as_function(as.formula(.x)))\n\ndiv <- parse_number(keep(input, ~ str_detect(.x, \"Test:\")))\ndivt <- parse_number(keep(input, ~ str_detect(.x, \"If true:\")))\ndivf <- parse_number(keep(input, ~ str_detect(.x, \"If false:\")))\n\ntest <- pmap(\n list(div, divt, divf),\n ~ function(x) if_else(x %% ..1 == 0, ..2 + 1, ..3 + 1)\n)\n\nnum_monkeys <- length(input) / 6\n\n\ncompute_monkey_business <- function(num_rounds, worry_func) {\n # Initialize\n activity <- rep(0, num_monkeys)\n\n # Perform the tosses\n for (round in 1:num_rounds) {\n for (monkey in 1:num_monkeys) {\n for (item in items[[monkey]]) {\n worry <- worry_func(operations[[monkey]](item))\n toss <- test[[monkey]](worry)\n items[[toss]] <- c(items[[toss]], worry)\n }\n activity[[monkey]] <- activity[[monkey]] + length(items[[monkey]])\n items[[monkey]] <- numeric(0)\n }\n }\n\n # Compute monkey business score\n activity |>\n sort() |>\n tail(2) |>\n reduce(`*`)\n}\n\n\ncompute_monkey_business(num_rounds = 20, worry_func = \\(x) floor(x / 3))\n\n[1] 78678", "crumbs": [ "2022", - "Day 7" + "Day 11" ] }, { - "objectID": "2022/R/day07.html#part-2", - "href": "2022/R/day07.html#part-2", - "title": "Day 7", + "objectID": "2022/R/day11.html#part-2", + "href": "2022/R/day11.html#part-2", + "title": "Day 11", "section": "Part 2", - "text": "Part 2\nFind size of the smallest necessary directory to delete:\n\ncurr_system_size <- dir_sizes |>\n filter(dir == \"/\") |>\n pull(size)\n\ndir_sizes |>\n filter(size >= (30000000 - (70000000 - curr_system_size))) |>\n slice_min(size) |>\n pull(size)\n\n[1] 4443914", + "text": "Part 2\n\nlcm <- DescTools::LCM(div)\ncompute_monkey_business(num_rounds = 10000, worry_func = \\(x) x %% lcm)\n\n[1] 15333249714", "crumbs": [ "2022", - "Day 7" + "Day 11" ] }, { - "objectID": "2022/R/day12.html", - "href": "2022/R/day12.html", - "title": "Day 12", + "objectID": "2022/R/day13.html", + "href": "2022/R/day13.html", + "title": "Day 13", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day12.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input into a data frame and convert to indexed nested lists\ndf <- read_table(\"../input/day13.txt\", col_names = \"txt\") |> \n mutate(\n row_id = row_number(),\n group_id = floor((row_id - 1) / 2) + 1,\n item_id = (row_id - 1) %% 2 + 1,\n lst = map(txt, jsonlite::fromJSON, simplifyVector = FALSE)\n )", "crumbs": [ "2022", - "Day 12" + "Day 13" ] }, { - "objectID": "2022/R/day12.html#setup", - "href": "2022/R/day12.html#setup", - "title": "Day 12", + "objectID": "2022/R/day13.html#setup", + "href": "2022/R/day13.html#setup", + "title": "Day 13", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day12.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input into a data frame and convert to indexed nested lists\ndf <- read_table(\"../input/day13.txt\", col_names = \"txt\") |> \n mutate(\n row_id = row_number(),\n group_id = floor((row_id - 1) / 2) + 1,\n item_id = (row_id - 1) %% 2 + 1,\n lst = map(txt, jsonlite::fromJSON, simplifyVector = FALSE)\n )", "crumbs": [ "2022", - "Day 12" + "Day 13" ] }, { - "objectID": "2022/R/day12.html#part-1", - "href": "2022/R/day12.html#part-1", - "title": "Day 12", + "objectID": "2022/R/day13.html#part-1", + "href": "2022/R/day13.html#part-1", + "title": "Day 13", "section": "Part 1", - "text": "Part 1\nReformat input as a data frame of coordinates and elevations:\n\ndf <- input |>\n str_split(\"\") |>\n unlist() |>\n as_tibble() |>\n transmute(\n id = row_number(),\n letter = value,\n elevation = case_when(\n letter == \"S\" ~ Inf,\n letter == \"E\" ~ -Inf,\n .default = match(letter, letters)\n ),\n row = floor((id - 1) / str_length(input[1]) + 1),\n col = (id - 1) %% str_length(input[1]) + 1\n )\n\n\ndf_to_graph <- function(df) {\n\n # Flag whether each neighbor of each vertex is walkable\n neighbors <- df |>\n mutate(up = lag(id), down = lead(id), .by = col) |>\n mutate(left = lag(id), right = lead(id), .by = row) |>\n mutate(\n across(\n c(up, down, left, right),\n ~ elevation[.x],\n .names = \"{.col}_elev\"\n ),\n across(\n ends_with(\"_elev\"),\n ~ (.x - elevation) <= 1,\n .names = \"{str_remove(.col, '_elev')}_walkable\"\n )\n ) |>\n rename_with(.cols = c(up, down, left, right), ~ str_c(.x, \"_idx\")) |>\n select(source_idx = id, ends_with(c(\"idx\", \"walkable\")))\n\n # Construct a list of edges\n edge_list <- neighbors |>\n pivot_longer(\n !source_idx,\n names_to = c(\"target_dir\", \".value\"),\n names_sep = \"_\"\n ) |>\n rename(\n target_idx = idx,\n target_walkable = walkable\n ) |>\n filter(target_walkable == TRUE) |>\n pmap(function(source_idx, target_idx, ...) { c(source_idx, target_idx) }) |>\n unlist()\n\n # Convert to a directed graph\n g <- make_empty_graph() |>\n add_vertices(length(df$id)) |>\n add_edges(edge_list)\n\n}\n\nshortest_path_length <- function(g, source_idx, target_idx) {\n shortest_paths(g, from = source_idx, to = target_idx)$vpath[[1]] |>\n length() - 1\n}\n\n\ng <- df_to_graph(df)\n\n\n# Get the indices of the start and end vertices\nidx_start <- match(\"S\", df$letter)\nidx_end <- match(\"E\", df$letter)\n\n# Compute shortest path from start to end\nshortest_path_length(g, idx_start, idx_end)\n\n[1] 462", + "text": "Part 1\nDefine a function to compare nested lists:\n\ncompare_nested <- function(a, b) {\n\n # Compare if both inputs are numeric\n if (is.numeric(a) & is.numeric(b)) {\n if (a < b) return(-1)\n if (a > b) return(1)\n if (a == b) return(0)\n }\n\n # Compare if only one input is numeric\n if (is.numeric(a) != is.numeric(b)) {\n if (is.numeric(a))\n return(compare_nested(list(a), b))\n if (is.numeric(b))\n return(compare_nested(a, list(b)))\n }\n\n # Compare if both inputs are lists\n i <- 1\n while (i <= min(length(a), length(b))) {\n result <- compare_nested(a[[i]], b[[i]])\n if (result %in% c(1, -1))\n return(result)\n i <- i + 1\n }\n # When all comparable values are equal, compare lengths\n return(compare_nested(length(a), length(b)))\n}\n\nSum the indices of packet pairs that are in order:\n\ndf |>\n select(group_id, item_id, lst) |>\n pivot_wider(\n names_from = item_id,\n names_prefix = \"item_\",\n values_from = lst\n ) |>\n mutate(comparison = map2_int(item_1, item_2, compare_nested)) |>\n filter(comparison == -1) |>\n pull(group_id) |> \n sum()\n\n[1] 5684", "crumbs": [ "2022", - "Day 12" + "Day 13" ] }, { - "objectID": "2022/R/day12.html#part-2", - "href": "2022/R/day12.html#part-2", - "title": "Day 12", + "objectID": "2022/R/day13.html#part-2", + "href": "2022/R/day13.html#part-2", + "title": "Day 13", "section": "Part 2", - "text": "Part 2\n\n# Loop over all starting locations and find the shortest path to the end\nmin_dist <- Inf\nfor (i in c(idx_start, which(df$letter == \"a\"))) {\n cur <- shortest_path_length(g, i, idx_end)\n if (cur >= 0 & cur < min_dist) {\n min_dist <- cur\n }\n}\nmin_dist\n\n[1] 451", + "text": "Part 2\nDefine a function to determine the pairwise order of nested lists:\n\nsort_nested <- function(lst) {\n n <- length(lst)\n indices <- 1:n\n\n if (n == 0) return()\n if (n == 1) return(indices)\n\n # Bubble sort: loop through list and swap elements until sorted\n repeat {\n swap_occurred <- FALSE\n for (i in 1:(n - 1)) {\n j1 <- which(indices == i)\n j2 <- which(indices == i + 1)\n if (compare_nested(lst[[j1]], lst[[j2]]) == 1) {\n indices[j1] <- i + 1\n indices[j2] <- i\n swap_occurred <- TRUE\n }\n }\n if (!swap_occurred) break\n }\n return(indices)\n}\n\nAdd new flagged packets, sort all, and multiply flagged indices:\n\nnew_packets <- list(\"[[2]]\", \"[[6]]\") |>\n map(jsonlite::fromJSON, simplifyVector = FALSE) |>\n as_tibble_col(column_name = \"lst\")\n\ndf |>\n transmute(lst, flag = FALSE) |>\n add_row(new_packets, flag = TRUE) |>\n mutate(ord = sort_nested(lst)) |>\n filter(flag) |>\n pull(ord) |> \n prod()\n\n[1] 22932", "crumbs": [ "2022", - "Day 12" + "Day 13" ] }, { - "objectID": "2022/R/day02.html", - "href": "2022/R/day02.html", - "title": "Day 2", + "objectID": "2022/R/day06.html", + "href": "2022/R/day06.html", + "title": "Day 6", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_fwf(\"../input/day02.txt\", col_types = \"c\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\") |>\n str_split_1(\"\") |>\n enframe(name = \"idx\", value = \"char\")", "crumbs": [ "2022", - "Day 2" + "Day 6" ] }, { - "objectID": "2022/R/day02.html#setup", - "href": "2022/R/day02.html#setup", - "title": "Day 2", + "objectID": "2022/R/day06.html#setup", + "href": "2022/R/day06.html#setup", + "title": "Day 6", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_fwf(\"../input/day02.txt\", col_types = \"c\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\") |>\n str_split_1(\"\") |>\n enframe(name = \"idx\", value = \"char\")", "crumbs": [ "2022", - "Day 2" + "Day 6" ] }, { - "objectID": "2022/R/day02.html#part-1", - "href": "2022/R/day02.html#part-1", - "title": "Day 2", + "objectID": "2022/R/day06.html#part-1", + "href": "2022/R/day06.html#part-1", + "title": "Day 6", "section": "Part 1", - "text": "Part 1\n\n# Format shapes/strategies as numbers 1-3 for modular arithmetic\ndf <- tibble(\n opponent = as.numeric(factor(input$X1, levels = c(\"A\", \"B\", \"C\"))),\n strategy = as.numeric(factor(input$X2, levels = c(\"X\", \"Y\", \"Z\")))\n)\n\nscore_shape <- function(df) {\n df |> \n mutate(\n self = strategy,\n outcome = (self - opponent + 1) %% 3 * 3,\n score = self + outcome\n ) |>\n pull(score) |>\n sum()\n}\n\nRun puzzle input:\n\nscore_shape(df)\n\n[1] 15422", + "text": "Part 1\n\nfind_marker <- function(df, marker_length) {\n df |> \n # Construct sequences of next n chars and count # of unique chars in each\n transmute(\n marker_idx = idx + marker_length - 1,\n char_seq = reduce(\n .x = map(0:(marker_length - 1), ~ lead(char, n = .x)),\n .f = str_c\n ),\n n_unique = map_int(\n char_seq,\n ~ .x |>\n str_split(\"\") |>\n unlist() |>\n unique() |>\n length()\n )\n ) |>\n\n # Extract first instance where all n chars are unique\n filter(n_unique == marker_length) |>\n pull(marker_idx) |>\n min()\n}\n\n\nfind_marker(input, marker_length = 4)\n\n[1] 1802", "crumbs": [ "2022", - "Day 2" + "Day 6" ] }, { - "objectID": "2022/R/day02.html#part-2", - "href": "2022/R/day02.html#part-2", - "title": "Day 2", + "objectID": "2022/R/day06.html#part-2", + "href": "2022/R/day06.html#part-2", + "title": "Day 6", "section": "Part 2", - "text": "Part 2\n\nscore_outcome <- function(df) {\n df |> \n mutate(\n self = (opponent + strategy) %% 3 + 1,\n outcome = (strategy - 1) * 3,\n score = self + outcome\n ) |>\n pull(score) |>\n sum()\n}\n\n\nscore_outcome(df)\n\n[1] 15442", + "text": "Part 2\n\nfind_marker(input, marker_length = 14)\n\n[1] 3551", "crumbs": [ "2022", - "Day 2" + "Day 6" ] }, { - "objectID": "2022/R/day15.html", - "href": "2022/R/day15.html", - "title": "Day 15", + "objectID": "2022/R/day03.html", + "href": "2022/R/day03.html", + "title": "Day 3", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\nlibrary(sf)\n\ntheme_set(theme_bw())\n\n# Read input from text and extract numeric values into a data frame\ninput <- read_lines(\"../input/day15.txt\") |> \n unglue_data(\n \"Sensor at x={s_x}, y={s_y}: closest beacon is at x={b_x}, y={b_y}\",\n convert = TRUE\n )", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_table(\"../input/day03.txt\", col_names = \"str\")", "crumbs": [ "2022", - "Day 15" + "Day 3" ] }, { - "objectID": "2022/R/day15.html#setup", - "href": "2022/R/day15.html#setup", - "title": "Day 15", + "objectID": "2022/R/day03.html#setup", + "href": "2022/R/day03.html#setup", + "title": "Day 3", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\nlibrary(sf)\n\ntheme_set(theme_bw())\n\n# Read input from text and extract numeric values into a data frame\ninput <- read_lines(\"../input/day15.txt\") |> \n unglue_data(\n \"Sensor at x={s_x}, y={s_y}: closest beacon is at x={b_x}, y={b_y}\",\n convert = TRUE\n )", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_table(\"../input/day03.txt\", col_names = \"str\")", "crumbs": [ "2022", - "Day 15" + "Day 3" ] }, { - "objectID": "2022/R/day15.html#part-1", - "href": "2022/R/day15.html#part-1", - "title": "Day 15", + "objectID": "2022/R/day03.html#part-1", + "href": "2022/R/day03.html#part-1", + "title": "Day 3", "section": "Part 1", - "text": "Part 1\nConvert input into a list of beacons, sensors, and total detection distances:\n\nsensors <- input |> \n distinct(\n s_x, \n s_y, \n max_dist = abs(b_x - s_x) + abs(b_y - s_y)\n )\n\nbeacons <- input |> \n distinct(x = b_x, y = b_y)\n\nGenerate a set of polygons that defines the regions detectible by each sensor:\n\n# Convert each sensor's detection distance into a region defined by a polygon\npoly <- sensors |> \n mutate(\n polygon = pmap(list(s_x, s_y, max_dist), function(x, y, dist) {\n rbind(\n c(x - dist, y),\n c(x, y - dist),\n c(x + dist, y),\n c(x, y + dist),\n c(x - dist, y)\n ) |> \n list() |> \n st_polygon()\n }),\n geometry = st_sfc(polygon)\n ) |> \n transmute(idx = row_number(), geometry) |> \n st_as_sf()\n\n# Merge all polygons into a single geometric shape\npoly_union <- st_union(poly)\n\nVisualize:\n\n# Overlapping regions\nggplot() + \n geom_sf(data = poly, aes(fill = factor(idx))) + \n scale_fill_viridis_d(guide = \"none\")\n\n\n\n\n\n\n\n# Merged region\nggplot() + \n geom_sf(data = poly_union)\n\n\n\n\n\n\n\n\nDefine a set of functions to count the number of integer points that cannot have a beacon within the detection region\n\n# Convert a set of x/y boundaries to a spatial rectangle object\npoly_rect <- function(xmin, xmax, ymin, ymax) {\n rbind(\n c(xmin, ymax), \n c(xmin, ymin), \n c(xmax, ymin), \n c(xmax, ymax), \n c(xmin, ymax)\n ) |> \n list() |> \n st_polygon() |> \n st_sfc()\n}\n\n# Get the coordinates within a poly, optionally limited within x/y bounds\nsf_points_in_poly <- function(poly, xlim = NULL, ylim = NULL) {\n \n # Define a rectangular region within which to generate grid points\n points_region <- poly_rect(\n xmin = (if (is_null(xlim)) st_bbox(poly)$xmin else head(xlim, 1)) - 0.5, \n xmax = (if (is_null(xlim)) st_bbox(poly)$xmax else tail(xlim, 1)) + 0.5, \n ymin = (if (is_null(ylim)) st_bbox(poly)$ymin else head(ylim, 1)) - 0.5,\n ymax = (if (is_null(ylim)) st_bbox(poly)$ymax else tail(ylim, 1)) + 0.5\n )\n \n # Generate the grid points that sit within the polygon\n points_region |> \n st_make_grid(cellsize = 1, what = \"centers\") |> \n st_intersection(poly) |> \n \n # Convert the set of points from spatial objects to x-y coordinates\n st_coordinates() |> \n as_tibble() |> \n mutate(across(everything(), as.integer)) |> \n rename_with(tolower)\n}\n\n# Count the points in a sf region (with optional x/y lims) that can't be a beacon\ncount_nonbeacon <- function(detection_region, known_beacons, x = NULL, y = NULL) {\n \n # Get the set of integer points within the polygon and x-y region specified\n detection_region |> \n sf_points_in_poly(xlim = x, ylim = y) |>\n \n # Remove known beacons from the list of points and count\n anti_join(known_beacons, join_by(x, y)) |>\n nrow()\n}\n\nRun puzzle input:\n\ncount_nonbeacon(poly_union, beacons, y = 2000000)\n\n[1] 5367037", + "text": "Part 1\n\ninput |>\n mutate(\n # Separate each line into two compartments\n str_length = str_length(str) / 2,\n str_1 = str_sub(str, start = 1L, end = str_length),\n str_2 = str_sub(str, start = str_length + 1, end = -1L),\n\n # Find the letter in common between each pair of compartments\n across(c(str_1, str_2), ~ str_split(.x, \"\")),\n dup = map2_chr(str_1, str_2, ~ intersect(.x, .y)),\n\n # Convert to priority value\n priority = match(dup, c(letters, LETTERS))\n ) |>\n\n # Compute total sum\n pull(priority) |>\n sum()\n\n[1] 8252", "crumbs": [ "2022", - "Day 15" + "Day 3" ] }, { - "objectID": "2022/R/day15.html#part-2", - "href": "2022/R/day15.html#part-2", - "title": "Day 15", + "objectID": "2022/R/day03.html#part-2", + "href": "2022/R/day03.html#part-2", + "title": "Day 3", "section": "Part 2", - "text": "Part 2\nDefine a function to get the location of an undetected beacon within a viewport:\n\nfind_undetected_beacon <- function(detection_region, xlim, ylim) {\n boundary <- poly_rect(xlim[1], xlim[2], ylim[1], ylim[2])\n \n # Find the polygon region where an undetected beacon could occur\n undetected_region <- st_difference(boundary, detection_region)\n \n # Get all integer points in the region\n points <- sf_points_in_poly(undetected_region)\n \n # Compute the region's boundary points to exclude\n undetected_region_boundary <- undetected_region |>\n st_bbox() |>\n as.list() |>\n pmap(\\(xmin, xmax, ymin, ymax) poly_rect(xmin, xmax, ymin, ymax)) |>\n pluck(1) |> \n st_difference(undetected_region) |> \n sf_points_in_poly()\n \n # Exclude all boundary points from the region\n anti_join(points, undetected_region_boundary, join_by(x, y))\n \n}\n\ntuning_freq <- function(x, y) format(4000000 * x + y, scientific = FALSE)\n\nRun on puzzle input:\n\npoint <- find_undetected_beacon(poly_union, c(0, 4000000), c(0, 4000000))\n\ntuning_freq(point$x, point$y)\n\n[1] \"11914583249288\"", + "text": "Part 2\n\ninput |>\n\n # Reshape to one row per group, one column per elf\n mutate(\n str = str_split(str, \"\"),\n group_num = floor((row_number() - 1) / 3),\n elf_num = as.character(row_number() %% 3)\n ) |>\n pivot_wider(names_from = elf_num, values_from = str, names_prefix = \"elf_\") |>\n\n # Find the character in common between all 3 elves & convert to priority val\n mutate(\n dup = pmap_chr(\n list(elf_0, elf_1, elf_2),\n ~ reduce(list(..1, ..2, ..3), intersect)\n ),\n priority = match(dup, c(letters, LETTERS))\n ) |>\n\n # Compute total sum\n pull(priority) |>\n sum()\n\n[1] 2828", "crumbs": [ "2022", - "Day 15" + "Day 3" ] }, { - "objectID": "2022/R/day14.html", - "href": "2022/R/day14.html", - "title": "Day 14", + "objectID": "2022/R/day01.html", + "href": "2022/R/day01.html", + "title": "Day 1", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from text\ninput <- read_lines(\"../input/day14.txt\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day01.txt\") |> \n as.integer()", "crumbs": [ "2022", - "Day 14" + "Day 1" ] }, { - "objectID": "2022/R/day14.html#setup", - "href": "2022/R/day14.html#setup", - "title": "Day 14", + "objectID": "2022/R/day01.html#setup", + "href": "2022/R/day01.html#setup", + "title": "Day 1", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from text\ninput <- read_lines(\"../input/day14.txt\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day01.txt\") |> \n as.integer()", "crumbs": [ "2022", - "Day 14" + "Day 1" ] }, { - "objectID": "2022/R/day14.html#part-1", - "href": "2022/R/day14.html#part-1", - "title": "Day 14", + "objectID": "2022/R/day01.html#part-1", + "href": "2022/R/day01.html#part-1", + "title": "Day 1", "section": "Part 1", - "text": "Part 1\nConvert input from rows of text lines to a list of obstacle coordinates:\n\n# Extract numeric values from the input text\ndf <- input |> \n str_split(\" -> \") |> \n imap_dfr(\\(input, idx) tibble(idx, input)) |> \n unglue_unnest(input, \"{x},{y}\", convert = TRUE)\n\n\n# Expand the endpoints into a list of coordinates of every obstacle\nobstacles <- df |> \n mutate(\n seq_x = map2(x, lead(x), ~ if (!is.na(.x) & !is.na(.y)) .x:.y),\n seq_y = map2(y, lead(y), ~ if (!is.na(.x) & !is.na(.y)) .x:.y),\n .by = idx\n ) |> \n unnest(c(seq_x, seq_y)) |> \n distinct(x = seq_x, y = seq_y) |> \n mutate(chr = '#')\n\nDefine a function to recursively drop a grain of sand until it comes to rest:\n\ndrop_grain <- function(scan, x_cur, y_cur) {\n y_new <- y_cur + 1\n \n # Check if new y-coordinate is out of bounds\n if (y_new > max(scan$y))\n return(scan)\n \n for (x_new in c(x_cur, x_cur - 1, x_cur + 1)) {\n # Check if next x-coordinate is out of bounds\n if (!between(x_new, min(scan$x), max(scan$x)))\n return(scan)\n # If grain can flow into the next spot, recurse into next spot\n else if (nrow(filter(scan, x == x_new, y == y_new)) == 0)\n return(drop_grain(scan, x_new, y_new))\n }\n\n # If grain has nowhere to go, drop the grain in its current place and exit\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n}\n\nDefine a function that fills the map with sand one grain at a time, starting from the designated point, until all sand comes to rest:\n\nfill_sand <- function(scan, grain_func) {\n repeat {\n scan_new <- grain_func(scan)\n \n # If the scan is unchanged after dropping the grain, the sand is at rest. \n if (nrow(scan_new) == nrow(scan)) break\n \n scan <- scan_new\n }\n scan\n}\n\ncount_grains <- function(scan) {\n scan |> \n filter(chr == \"o\") |> \n nrow()\n}\n\nCount the grains using the puzzle input:\n\nobstacles |> \n fill_sand(grain_func = partial(drop_grain, x_cur = 500, y_cur = 0)) |> \n count_grains()\n\n[1] 862", + "text": "Part 1\n\n# Format input as a data frame and number the elves\ndf <- tibble(\n cal = input,\n elf_id = cumsum(is.na(cal)) + 1\n) |>\n filter(!is.na(cal))\n\n# Compute calorie sum for each elf, get the top n elves, and combine totals\ncount_max <- function(df, num_top_elves) {\n df |> \n group_by(elf_id) |>\n summarize(total_cal = sum(cal)) |>\n slice_max(total_cal, n = num_top_elves) |>\n pull(total_cal) |>\n sum()\n}\n\nRun puzzle input:\n\ncount_max(df, 1)\n\n[1] 68787", "crumbs": [ "2022", - "Day 14" + "Day 1" ] }, { - "objectID": "2022/R/day14.html#part-2", - "href": "2022/R/day14.html#part-2", - "title": "Day 14", + "objectID": "2022/R/day01.html#part-2", + "href": "2022/R/day01.html#part-2", + "title": "Day 1", "section": "Part 2", - "text": "Part 2\nRe-define the drop_grain function to allow for the boundless floor:\n\ndrop_grain <- function(scan, x_cur, y_cur, floor) {\n y_new <- y_cur + 1\n \n # Check if current location already has a grain of sand (entry blocked)\n if (nrow(filter(scan, x == x_cur, y == y_cur)) > 0)\n return(scan)\n \n # Check if the current sand grain is sitting on top of the floor\n if (y_new == floor)\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n \n # If grain can flow into the next spot, recurse into next spot\n for (x_new in c(x_cur, x_cur - 1, x_cur + 1)) {\n if (nrow(filter(scan, x == x_new, y == y_new)) == 0)\n return(drop_grain(scan, x_new, y_new, floor))\n }\n\n # If grain has nowhere to go, drop the grain in its current place and exit\n return(add_row(scan, x = x_cur, y = y_cur, chr = \"o\"))\n}\n\nCount the grains using the puzzle input:\n\nobstacles |> \n fill_sand(\n grain_func = partial(\n drop_grain, \n x_cur = 500, \n y_cur = 0, \n floor = max(obstacles$y) + 2\n )\n ) |> \n count_grains()\n\n[1] 28744", + "text": "Part 2\n\ncount_max(df, 3)\n\n[1] 198041", "crumbs": [ "2022", - "Day 14" + "Day 1" ] }, { - "objectID": "2022/R/day09.html", - "href": "2022/R/day09.html", - "title": "Day 9", + "objectID": "2022/R/day08.html", + "href": "2022/R/day08.html", + "title": "Day 8", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(ctmle, include.only = \"bound\")\n\n# Read input from file\ninput <- read_lines(\"../input/day09.txt\") |>\n str_split(\" \")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file and format as a matrix\ninput <- read_fwf(\"../input/day08.txt\") |> \n transmute(x = str_split(X1, \"\")) |>\n unnest_wider(x, names_sep = \"\") |>\n mutate(across(everything(), as.integer)) |>\n as.matrix()", "crumbs": [ "2022", - "Day 9" + "Day 8" ] }, { - "objectID": "2022/R/day09.html#setup", - "href": "2022/R/day09.html#setup", - "title": "Day 9", + "objectID": "2022/R/day08.html#setup", + "href": "2022/R/day08.html#setup", + "title": "Day 8", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(ctmle, include.only = \"bound\")\n\n# Read input from file\ninput <- read_lines(\"../input/day09.txt\") |>\n str_split(\" \")", - "crumbs": [ - "2022", - "Day 9" - ] - }, - { - "objectID": "2022/R/day09.html#part-1", - "href": "2022/R/day09.html#part-1", - "title": "Day 9", - "section": "Part 1", - "text": "Part 1\n\n# Convert head movements to sequence of locations on complex plane\nhead_path <- input |>\n map(~ rep(.x[[1]], .x[[2]])) |>\n reduce(c) |>\n recode(\"R\" = 1 + 0i, \"L\" = -1 + 0i, \"U\" = 0 + 1i, \"D\" = 0 - 1i) |>\n accumulate(.init = 0 + 0i, .f = sum)\n\n# Find path of next knot given path of previous knot.\nmove_next_knot <- function(prev_knot_path) {\n accumulate(\n .x = prev_knot_path,\n .f = function(tail = .x, head = .y) {\n diff <- head - tail\n if_else(\n max(abs(Re(diff)), abs(Im(diff))) <= 1,\n tail,\n tail + bound(Re(diff), c(-1, 1)) + bound(Im(diff), c(-1, 1)) * 1i\n )\n }\n )\n}\n\n\nunique_tail_spots <- function(path, num_knots) {\n # Iteratively compute path of each knot from head & to tail\n move_tail <- reduce(map(1:(num_knots - 1), ~ move_next_knot), compose)\n\n # Find number of unique locations in the tail's path\n length(unique(move_tail(path)))\n}\n\n\nunique_tail_spots(head_path, num_knots = 2)\n\n[1] 6197", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file and format as a matrix\ninput <- read_fwf(\"../input/day08.txt\") |> \n transmute(x = str_split(X1, \"\")) |>\n unnest_wider(x, names_sep = \"\") |>\n mutate(across(everything(), as.integer)) |>\n as.matrix()", "crumbs": [ "2022", - "Day 9" + "Day 8" ] }, { - "objectID": "2022/R/day09.html#part-2", - "href": "2022/R/day09.html#part-2", - "title": "Day 9", - "section": "Part 2", - "text": "Part 2\n\nunique_tail_spots(head_path, num_knots = 10)\n\n[1] 2562", + "objectID": "2022/R/day08.html#parts-1-2", + "href": "2022/R/day08.html#parts-1-2", + "title": "Day 8", + "section": "Parts 1 & 2", + "text": "Parts 1 & 2\n\n# Create df with one row per tree and variables for its containing row & col\nexpand_grid(\n col_pos = 1:nrow(input),\n row_pos = 1:ncol(input)\n) |>\n mutate(\n tree_idx = row_number(),\n row_list = map(col_pos, ~ unname(as.matrix(input)[.x, ])),\n col_list = map(row_pos, ~ unname(as.matrix(input)[, .x])),\n ) |>\n\n # For each tree, construct its treeline looking outward in each direction\n pivot_longer(\n c(col_pos, row_pos, col_list, row_list),\n names_to = c(\"axis\", \".value\"),\n names_sep = \"_\"\n ) |>\n mutate(\n split = map2(\n list,\n pos,\n ~ split(.x, c(rep(\"bwd\", .y - 1), \"curr_tree\", rep(\"fwd\", length(.x) - .y)))\n )\n ) |>\n unnest_wider(split) |>\n mutate(bwd = map(bwd, rev)) |>\n pivot_longer(c(fwd, bwd), names_to = \"dir\", values_to = \"treeline\") |>\n\n # Check if each is the tallest tree in each direction & count visible trees\n mutate(\n is_tallest = map2_lgl(curr_tree, treeline, ~ all(.x > .y)),\n num_visible = map2_int(curr_tree, treeline, function(curr_tree, treeline) {\n ifelse(\n every(treeline, ~.x < curr_tree),\n length(treeline),\n detect_index(treeline, ~ .x >= curr_tree)\n )\n })\n ) |>\n\n # Summarize visibility & scenic scores from all 4 directions for each tree\n group_by(tree_idx) |>\n summarize(\n is_visible = any(is_tallest),\n scenic_score = prod(num_visible)\n ) |>\n ungroup() |>\n\n # Compute total trees visible from forest edge & max scenic score in forest\n summarize(\n total_visible = sum(is_visible),\n max_scenic_score = max(scenic_score)\n )\n\n# A tibble: 1 × 2\n total_visible max_scenic_score\n <int> <dbl>\n1 1690 535680", "crumbs": [ "2022", - "Day 9" + "Day 8" ] }, { - "objectID": "2024/R/day04.html", - "href": "2024/R/day04.html", - "title": "Day 4", + "objectID": "2024/R/day10.html", + "href": "2024/R/day10.html", + "title": "Day 10", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day04.txt\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day10.txt\", skip_empty_rows = TRUE)\n\n# Convert input to matrix format\nmtx <- input |> \n str_split(\"\") |>\n unlist() |> \n as.integer() |> \n matrix(nrow = length(input), byrow = TRUE)", "crumbs": [ "2024", - "Day 4" + "Day 10" ] }, { - "objectID": "2024/R/day04.html#setup", - "href": "2024/R/day04.html#setup", - "title": "Day 4", + "objectID": "2024/R/day10.html#setup", + "href": "2024/R/day10.html#setup", + "title": "Day 10", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day04.txt\")", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day10.txt\", skip_empty_rows = TRUE)\n\n# Convert input to matrix format\nmtx <- input |> \n str_split(\"\") |>\n unlist() |> \n as.integer() |> \n matrix(nrow = length(input), byrow = TRUE)", "crumbs": [ "2024", - "Day 4" + "Day 10" ] }, { - "objectID": "2024/R/day04.html#part-1", - "href": "2024/R/day04.html#part-1", - "title": "Day 4", + "objectID": "2024/R/day10.html#part-1", + "href": "2024/R/day10.html#part-1", + "title": "Day 10", "section": "Part 1", - "text": "Part 1\n\n# Convert vector of text to matrix\ntxt_to_mtx <- function(txt) {\n txt |> \n str_split(\"\") |> \n unlist() |> \n matrix(nrow = length(txt), byrow = TRUE)\n}\n\n# Convert matrix to vector of text\nmtx_to_txt <- function(mtx) {\n mtx |> \n t() |> \n as_tibble() |> \n as.list() |> \n map(str_flatten) |> \n unlist() |> \n unname()\n}\n\n# Transpose a vector of text\ntranspose_txt <- function(txt) {\n txt |> \n txt_to_mtx() |> \n t() |> \n mtx_to_txt()\n}\n\n# Get rows and columns of input as individual text vectors\nrows <- input\ncols <- transpose_txt(rows)\n\n# Convert diagonals of input as individual text vectors\npad <- map_chr(\n 1:length(rows), \n ~ str_c(rep_len(\" \", .x - 1), collapse = \"\")\n)\n\ndiag1 <- str_c(pad, rows, rev(pad)) |> \n transpose_txt()\ndiag2 <- str_c(rev(pad), rows, pad) |> \n transpose_txt()\n\n# Loop over rows, columns, and diagnoals and count occurrences of \"XMAS\"\nmap_int(\n list(rows, cols, diag1, diag2),\n ~ sum(str_count(.x, \"XMAS\") + str_count(.x, \"SAMX\"))\n) |> \n sum()\n\n[1] 2599", + "text": "Part 1\nDefine custom functions:\n\n# North / South / East / West\ndirs <- list(c(0, 1), c(0, -1), c(1, 0), c(-1, 0))\n\n# Check if a coordinate is in the bounds of the map\nin_bounds <- function(x, map) {\n between(x[1], 1, nrow(map)) & between(x[2], 1, ncol(map))\n}\n\nfind_trail_ends <- function(cur_coord, map) {\n \n # If the trailhead has been reached, return its coordinate.\n if (map[cur_coord] == 9)\n return(list(cur_coord))\n \n # Define the possible next steps within the map bounds\n next_steps <- dirs |> \n map(~ cur_coord + .x) |> \n keep(~ in_bounds(.x, map))\n \n trail_ends <- list()\n \n # If the next step leads to a valid path, add its terminal to the list\n for (next_coord in next_steps) {\n if (map[next_coord] == map[cur_coord] + 1) \n trail_ends <- c(trail_ends, find_trail_ends(next_coord, map))\n }\n \n return(unique(trail_ends))\n}\n\nscore_trail <- function(trailhead, map) length(find_trail_ends(trailhead, map))\n\n\n# Get a list of coordinates of all of the trailheads\ntrailheads <- which(mtx == 0, arr.ind = TRUE)\ntrailheads_list <- map(\n 1:nrow(trailheads), \n ~ array(trailheads[.x,], dim = c(1, 2))\n)\n\n# Score each trailhead and sum the total\ntrailheads_list |> \n map_int(~ score_trail(.x, mtx)) |> \n sum()\n\n[1] 531", "crumbs": [ "2024", - "Day 4" + "Day 10" ] }, { - "objectID": "2024/R/day04.html#part-2", - "href": "2024/R/day04.html#part-2", - "title": "Day 4", + "objectID": "2024/R/day10.html#part-2", + "href": "2024/R/day10.html#part-2", + "title": "Day 10", "section": "Part 2", - "text": "Part 2\n\n# Define the four possible XMAS patterns in a 3x3 grid as a regex string\nxmas1 <- \"M.S.A.M.S\"\nxmas4 <- \"S.M.A.S.M\"\nxmas2 <- \"S.S.A.M.M\"\nxmas3 <- \"M.M.A.S.S\"\nxmas_regex <- glue::glue(\"{xmas1}|{xmas2}|{xmas3}|{xmas4}\")\n\n# Convert input into a matrix\nmtx <- txt_to_mtx(input)\n\n# Extract every 3x3 submatrix in the input text block as a text string\nmap(\n 1:(nrow(mtx) - 2),\n function(row_start) {\n map_chr(\n 1:(ncol(mtx) - 2),\n function(col_start) {\n mtx[row_start:(row_start + 2), col_start:(col_start + 2)] |> \n mtx_to_txt() |> \n str_flatten()\n }\n )\n }\n) |> \n unlist() |> \n \n # Count the text strings with a valid XMAS pattern\n str_detect(xmas_regex) |> \n sum()\n\n[1] 1948", + "text": "Part 2\nModify the trail rating function:\n\nrate_trail <- function(cur_coord, map) {\n # If the trailhead has been reached, increment the score and exit.\n if (map[cur_coord] == 9)\n return(1)\n \n # Define the possible next steps within the map bounds\n next_steps <- dirs |> \n map(~ cur_coord + .x) |> \n keep(~ in_bounds(.x, map))\n \n # Sum the trail ratings of all valid next steps\n map_int(\n next_steps,\n ~ if (map[.x] == map[cur_coord] + 1) rate_trail(.x, map) else 0\n ) |> \n sum()\n}\n\nRe-run the puzzle input:\n\ntrailheads_list |> \n map_int(~ rate_trail(.x, mtx)) |> \n sum()\n\n[1] 1210", "crumbs": [ "2024", - "Day 4" + "Day 10" ] }, { - "objectID": "2024/R/day11.html", - "href": "2024/R/day11.html", - "title": "Day 11", + "objectID": "2024/R/day05.html", + "href": "2024/R/day05.html", + "title": "Day 5", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\", skip_empty_rows = TRUE) |> \n str_split_1(\" \") |> \n as.integer()", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day05.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 11" + "Day 5" ] }, { - "objectID": "2024/R/day11.html#setup", - "href": "2024/R/day11.html#setup", - "title": "Day 11", + "objectID": "2024/R/day05.html#setup", + "href": "2024/R/day05.html#setup", + "title": "Day 5", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day11.txt\", skip_empty_rows = TRUE) |> \n str_split_1(\" \") |> \n as.integer()", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day05.txt\", skip_empty_rows = TRUE)", "crumbs": [ "2024", - "Day 11" + "Day 5" ] }, { - "objectID": "2024/R/day11.html#part-1", - "href": "2024/R/day11.html#part-1", - "title": "Day 11", + "objectID": "2024/R/day05.html#part-1", + "href": "2024/R/day05.html#part-1", + "title": "Day 5", "section": "Part 1", - "text": "Part 1\nCreate blink functions:\n\nblink <- function(x) {\n char <- format(x, scientific = FALSE)\n n <- str_length(char)\n \n if (x == 0) \n 1\n else if (n %% 2 == 0)\n parse_number(c(\n str_sub(char, 1, n / 2),\n str_sub(char, n / 2 + 1, n)\n ))\n else \n x * 2024\n}\n\nblink_n <- function(df, n) {\n if (n == 0)\n return(sum(df$n))\n \n df |> \n mutate(stones = map(stones, blink)) |> \n unnest(stones) |> \n summarize(n = sum(n), .by = stones) |> \n blink_n(n - 1)\n}\n\nRun blink function 25 times on puzzle input:\n\ndf <- tibble(stones = input, n = 1)\n\nblink_n(df, 25)\n\n[1] 193899", + "text": "Part 1\n\n# Extract page ordering rules from input\nrule_list <- input |> \n keep(~ str_detect(.x, \"\\\\|\")) |> \n as_tibble_col(column_name = \"rule\") |> \n separate(rule, into = c(\"p1\", \"p2\")) |> \n mutate(rule_num = row_number(), .before = everything()) |> \n mutate(across(c(p1, p2), parse_number))\n\n# Extract page sequences from input\npages <- input |> \n discard(~ str_detect(.x, \"\\\\|\")) |> \n str_split(\",\") |> \n map(parse_number) |> \n as_tibble_col(column_name = \"update\")\n\n# Sort a given vector by its applicable rules\nsort_by_rules <- function(seq) {\n active_rules <- rule_list |> \n filter(p1 %in% seq & p2 %in% seq)\n \n repeat {\n swap_occurred <- FALSE\n for (i in 1:nrow(active_rules)) {\n rule <- filter(active_rules, row_number() == i)\n idx1 <- which(seq == rule$p1)\n idx2 <- which(seq == rule$p2)\n \n if (idx1 > idx2) {\n seq[[idx1]] <- rule$p2\n seq[[idx2]] <- rule$p1\n swap_occurred <- TRUE\n }\n }\n if (!swap_occurred) return(seq)\n }\n}\n\n# Sort all page sequences and extract the center page of each result\noutput <- pages |> \n mutate(\n resorted = map(update, sort_by_rules),\n is_sorted = map2_lgl(update, resorted, identical),\n center_page = map_int(resorted, ~ .x[(length(.x) + 1) / 2])\n )\n\n\n# For the properly-ordered updates, sum the center page numbers\noutput |>\n filter(is_sorted) |> \n pull(center_page) |> \n sum()\n\n[1] 6505", "crumbs": [ "2024", - "Day 11" + "Day 5" ] }, { - "objectID": "2024/R/day11.html#part-2", - "href": "2024/R/day11.html#part-2", - "title": "Day 11", + "objectID": "2024/R/day05.html#part-2", + "href": "2024/R/day05.html#part-2", + "title": "Day 5", "section": "Part 2", - "text": "Part 2\nRun blink function 75 times on puzzle input:\n\nblink_n(df, 75) |> \n format(scientific = FALSE)\n\n[1] \"229682160383225\"", + "text": "Part 2\n\n# For the improperly-ordered updates, sum their sorted center pages\noutput |>\n filter(!is_sorted) |> \n pull(center_page) |> \n sum()\n\n[1] 6897", "crumbs": [ "2024", - "Day 11" + "Day 5" ] }, { - "objectID": "2024/R/day13.html", - "href": "2024/R/day13.html", - "title": "Day 13", + "objectID": "2024/R/day07.html", + "href": "2024/R/day07.html", + "title": "Day 7", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day13.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\", skip_empty_rows = TRUE) |> \n str_split(\" \") |> \n map(parse_number)", "crumbs": [ "2024", - "Day 13" + "Day 7" ] }, { - "objectID": "2024/R/day13.html#setup", - "href": "2024/R/day13.html#setup", - "title": "Day 13", + "objectID": "2024/R/day07.html#setup", + "href": "2024/R/day07.html#setup", + "title": "Day 7", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day13.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day07.txt\", skip_empty_rows = TRUE) |> \n str_split(\" \") |> \n map(parse_number)", "crumbs": [ "2024", - "Day 13" + "Day 7" ] }, { - "objectID": "2024/R/day13.html#part-1", - "href": "2024/R/day13.html#part-1", - "title": "Day 13", + "objectID": "2024/R/day07.html#part-1", + "href": "2024/R/day07.html#part-1", + "title": "Day 7", "section": "Part 1", - "text": "Part 1\nExtract numerical values from input text:\n\ndf <- input |> \n unglue_data(c(\n \"Button {button}: X+{x=\\\\d+}, Y+{y=\\\\d+}\",\n \"{button}: X={x=\\\\d+}, Y={y=\\\\d+}\"\n )) |> \n mutate(\n machine_id = floor((row_number() - 1) / 3),\n across(c(x, y), parse_number),\n .before = everything()\n ) |> \n pivot_wider(names_from = button, values_from = c(x, y))\n\nDefine a function to convert numeric equation input and output token counts:\n\ncompute_tokens <- function(df) {\n \n # Convert each machine's properties into a system of equations and solve.\n soln <- df |> \n nest(coeff = c(x_A, x_B, y_A, y_B)) |> \n nest(intercept = c(x_Prize, y_Prize)) |> \n mutate(\n coeff = map(coeff, ~ matrix(as.numeric(.x), nrow = 2, byrow = TRUE)),\n intercept = map(intercept, as.numeric),\n soln = map2(\n coeff, \n intercept, \n ~ solve(.x, .y) |> \n set_names(\"A\", \"B\") |> \n as_tibble_row()\n )\n ) |> \n unnest(soln) |> \n select(machine_id, A, B)\n \n \n # Check that the solution is two whole numbers, then sum the token cost\n soln |> \n mutate(\n across(\n c(A, B), \n ~ near(.x, abs(round(.x)), tol = 0.001), \n .names = \"{.col}_valid\"\n ),\n win = A_valid & B_valid,\n tokens = if_else(win, 3 * A + B, 0)\n ) |> \n pull(tokens) |> \n sum()\n}\n\nRun function on puzzle input:\n\ncompute_tokens(df)\n\n[1] 31623", + "text": "Part 1\nDefine calibration functions:\n\ncalibrate_operators <- function(seq, target, operators) {\n \n # If the end of the list has been reached or the target is already overshot, exit\n if (length(seq) == 1) \n return(seq == target)\n else if (seq[1] > target)\n return(FALSE)\n \n # Recursively compare the first two items of the seq using each operator\n map_lgl(\n operators,\n \\(f) {\n new_start <- get(f)(seq[1], seq[2])\n new_seq <- c(new_start, tail(seq, -2))\n calibrate_operators(new_seq, target, operators)\n }\n ) |> \n # If any output is true, the output has been calibrated.\n any()\n}\n\ncalibration_value <- function(input, output, operators) {\n # Compute calibration for each input-output pair\n is_calibrated <- map2_lgl(\n input, \n output, \n ~ calibrate_operators(.x, .y, operators = operators)\n )\n \n # Sum the calibrated outputs\n output |> \n keep(is_calibrated) |> \n sum() |> \n format(scientific = FALSE)\n}\n\nCompute calibration of the puzzle input:\n\ninput_values <- map(input, tail, -1)\noutput_values <- map_dbl(input, head, 1)\n \ncalibration_value(input_values, output_values, c(\"+\", \"*\"))\n\n[1] \"12940396350192\"", "crumbs": [ "2024", - "Day 13" + "Day 7" ] }, { - "objectID": "2024/R/day13.html#part-2", - "href": "2024/R/day13.html#part-2", - "title": "Day 13", + "objectID": "2024/R/day07.html#part-2", + "href": "2024/R/day07.html#part-2", + "title": "Day 7", "section": "Part 2", - "text": "Part 2\nAdd 10000000000000 to each prize intercept and re-compute:\n\ndf |> \n mutate(across(c(x_Prize, y_Prize), ~ .x + 10000000000000)) |> \n compute_tokens() |> \n format(scientific = FALSE)\n\n[1] \"93209116744825\"", + "text": "Part 2\nAdd a new concatenation operator and re-run the calibration on the puzzle input\n\nconcat <- function(x, y) as.numeric(str_c(x, y))\n\ncalibration_value(input_values, output_values, c(\"+\", \"*\", \"concat\"))\n\n[1] \"106016735664498\"", "crumbs": [ "2024", - "Day 13" + "Day 7" ] }, { - "objectID": "2024/R/day06.html", - "href": "2024/R/day06.html", - "title": "Day 6", + "objectID": "2024/R/day12.html", + "href": "2024/R/day12.html", + "title": "Day 12", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file into a data frame\ninput <- read_table(\"../input/day12.txt\", col_names = \"chr\") |> \n mutate(\n row = row_number(),\n chr = str_split(chr, \"\")\n ) |> \n unnest(chr) |> \n mutate(col = row_number(), .by = row) |> \n mutate(idx = row_number(), .before = everything())", "crumbs": [ "2024", - "Day 6" + "Day 12" ] }, { - "objectID": "2024/R/day06.html#setup", - "href": "2024/R/day06.html#setup", - "title": "Day 6", + "objectID": "2024/R/day12.html#setup", + "href": "2024/R/day12.html#setup", + "title": "Day 12", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day06.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file into a data frame\ninput <- read_table(\"../input/day12.txt\", col_names = \"chr\") |> \n mutate(\n row = row_number(),\n chr = str_split(chr, \"\")\n ) |> \n unnest(chr) |> \n mutate(col = row_number(), .by = row) |> \n mutate(idx = row_number(), .before = everything())", "crumbs": [ "2024", - "Day 6" + "Day 12" ] }, { - "objectID": "2024/R/day06.html#part-1", - "href": "2024/R/day06.html#part-1", - "title": "Day 6", + "objectID": "2024/R/day12.html#part-1", + "href": "2024/R/day12.html#part-1", + "title": "Day 12", "section": "Part 1", - "text": "Part 1\n\n# Guard functions --------------------------------------------------------------\nguards <- c(\"^\", \">\", \"v\", \"<\")\nguard_shift <- c(tail(guards, -1), head(guards, 1))\n\nrotate_guard <- function(cur) guard_shift[guards == cur]\n\nguard_dir <- function(char) {\n case_match(char,\n \"^\" ~ matrix(c(-1, 0), nrow = 1),\n \">\" ~ matrix(c( 0, 1), nrow = 1),\n \"v\" ~ matrix(c( 1, 0), nrow = 1),\n \"<\" ~ matrix(c( 0, -1), nrow = 1)\n )\n}\n\nin_bounds <- function(coord, mtx) {\n between(coord[1], 1, nrow(mtx)) & between(coord[2], 1, ncol(mtx))\n}\n\nmap_path <- function(mtx) {\n\n # Initiate guard's starting position and direction\n cur_char <- keep(mtx, ~ .x %in% guards)\n cur_coord <- which(mtx == cur_char, arr.ind = TRUE)\n cur_dir <- guard_dir(cur_char)\n \n # As long as the guard is in bounds, iteratively update its coords and direction\n repeat {\n next_coord <- cur_coord + cur_dir\n \n # If next step is out-of-bounds, update matrix and exit\n if (!in_bounds(next_coord, mtx)) {\n mtx[cur_coord] <- \"X\"\n break\n }\n # If next step is an obstacle, rotate the guard\n else if (mtx[next_coord] == '#') {\n cur_char <- rotate_guard(cur_char)\n cur_dir <- guard_dir(cur_char)\n }\n # Otherwise advance the guard forward\n else {\n mtx[cur_coord] <- \"X\"\n cur_coord <- next_coord\n }\n }\n \n mtx\n}\n\n\n# Convert input into a matrix\nmtx <- input |> \n str_split(\"\") |> \n unlist() |> \n matrix(nrow = length(input), byrow = TRUE)\n\n# Map the guard's path\nguard_path <- map_path(mtx)\n\n# Count distinct positions visited\nsum(guard_path == \"X\")", + "text": "Part 1\nFormat the input as a graph, with edges connecting neighbors of the same type:\n\n# Flag neighboring characters of the same value that border one other\nedges_wide <- input |> \n mutate(v = case_when(row + 1 == lead(row) ~ lead(idx)), .by = c(chr, col)) |> \n mutate(h = case_when(col + 1 == lead(col) ~ lead(idx)), .by = c(chr, row))\n\nedges_long <- edges_wide |> \n pivot_longer(\n c(v, h), \n names_to = NULL, \n values_to = \"target\", \n values_drop_na = TRUE\n )\n\n# Format neighbors as a list of edges and add to add a graph\ng <- edges_long |> \n transmute(\n edge_id = row_number(),\n src = idx, \n target\n ) |> \n pivot_longer(c(src, target)) |> \n arrange(edge_id, value) |> \n pull(value) |> \n make_graph(n = nrow(input), directed = FALSE)\n\nV(g)$name <- 1:nrow(input)\n\n# Separate out the resulting graph into sub-graphs of innerconnected regions\ndg <- decompose(g)\n\nCompute the perimeter, area, and cost of each subgraph then sum the total:\n\ndg |> \n map_int(\\(subgraph) {\n perim <- sum(4 - degree(subgraph))\n area <- gorder(subgraph)\n perim * area\n }) |> \n sum()\n\n[1] 1433460", "crumbs": [ "2024", - "Day 6" + "Day 12" ] }, { - "objectID": "2024/R/day06.html#part-2", - "href": "2024/R/day06.html#part-2", - "title": "Day 6", + "objectID": "2024/R/day12.html#part-2", + "href": "2024/R/day12.html#part-2", + "title": "Day 12", "section": "Part 2", - "text": "Part 2\nChange the path mapping function to test for loops\n\npath_loops <- function(mtx) {\n\n # Initiate guard's starting position and direction\n cur_char <- keep(mtx, ~ .x %in% guards)\n cur_coord <- which(mtx == cur_char, arr.ind = TRUE)\n cur_dir <- guard_dir(cur_char)\n path_hist <- matrix(\"\", nrow(mtx), ncol(mtx))\n\n # As long as the guard is in bounds, iteratively update its coords and direction\n repeat {\n next_coord <- cur_coord + cur_dir\n \n # Check if the guard is looping or if they have left the area\n if (str_detect(path_hist[cur_coord], fixed(cur_char))) \n return(TRUE)\n else if (!in_bounds(next_coord, mtx)) \n return(FALSE)\n \n # If next step is an obstacle, rotate the guard\n else if (mtx[next_coord] == '#') {\n # Update path history\n path_hist[cur_coord] <- str_c(path_hist[cur_coord], cur_char)\n # Update guard\n cur_char <- rotate_guard(cur_char)\n cur_dir <- guard_dir(cur_char)\n }\n # Otherwise advance the guard forward\n else {\n # Update path history\n path_hist[cur_coord] <- str_c(path_hist[cur_coord], cur_char)\n # Update guard\n cur_coord <- next_coord\n }\n }\n}\n\n\n# Create a variation of the map for each possible obstacle location\nobstacles <- which(guard_path == \"X\" & !(mtx %in% guards))\n\n# Test each obstacle location for loops and sum result\nobstacles |> \n map(~ replace(mtx, .x, \"#\")) |> \n map_lgl(path_loops) |> \n sum()", + "text": "Part 2\nUsed a hint from reddit: the number of corners is equal to the number of sides.\nA plot can have a convex corner or a concave corner.\n\nA cell has a convex corner for each pair of adjacent borders\nA cell has a concave corner if it has two adjacent cells of its same group, but its diagonal cell between the two has a different group.\n\n\n# Get original row/column input and join on the group output from the graph\ngroups <- left_join(\n input,\n imap_dfr(dg, \\(g, grp_idx) tibble(grp = grp_idx, idx = V(g)$name)),\n join_by(idx)\n) |> \n select(idx, grp, row, col)\n\n# For each of a cell's neighbors, flag if they're in the same group\nneighbors <- groups |> \n # Get group number of each adjacent cell (N/S/E/W)\n left_join(transmute(groups, n = grp, row = row + 1, col), join_by(row, col)) |> \n left_join(transmute(groups, w = grp, col = col + 1, row), join_by(row, col)) |> \n left_join(transmute(groups, s = grp, row = row - 1, col), join_by(row, col)) |> \n left_join(transmute(groups, e = grp, col = col - 1, row), join_by(row, col)) |> \n # Get group number of each diagonal cell (NW/NE/SW/SE)\n left_join(transmute(groups, nw = grp, row = row + 1, col = col + 1), join_by(row, col)) |> \n left_join(transmute(groups, ne = grp, row = row + 1, col = col - 1), join_by(row, col)) |> \n left_join(transmute(groups, sw = grp, row = row - 1, col = col + 1), join_by(row, col)) |> \n left_join(transmute(groups, se = grp, row = row - 1, col = col - 1), join_by(row, col)) |> \n select(-c(row, col)) |> \n # Compare group numbers of adjacent/diagonal cells to the current cell\n mutate(across(c(n, w, s, e, nw, ne, sw, se), ~ replace_na(.x == grp, FALSE)))\n\n# Compute total number of concave/convex corners for each cell\ncorners <- neighbors |> \n mutate(\n convex = (!n & !w) + (!s & !w) + (!s & !e) + (!n & !e),\n concave = (n & w & !nw) + (s & w & !sw) + (s & e & !se) + (n & e & !ne)\n )\n\nTotal the number of corners per group and multiply by the group’s area to get the total cost:\n\ncorners |> \n summarize(\n area = n(),\n num_sides = sum(convex + concave), \n .by = grp\n ) |> \n mutate(cost = area * num_sides) |> \n pull(cost) |> \n sum()\n\n[1] 855082", "crumbs": [ "2024", - "Day 6" + "Day 12" ] }, { - "objectID": "2024/R/day16.html", - "href": "2024/R/day16.html", - "title": "Day 16", + "objectID": "2024/R/day02.html", + "href": "2024/R/day02.html", + "title": "Day 2", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day16.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day02.txt\") |> \n map(~parse_number(str_split_1(.x, \" \")))", "crumbs": [ "2024", - "Day 16" + "Day 2" ] }, { - "objectID": "2024/R/day16.html#setup", - "href": "2024/R/day16.html#setup", - "title": "Day 16", + "objectID": "2024/R/day02.html#setup", + "href": "2024/R/day02.html#setup", + "title": "Day 2", "section": "", - "text": "# Libraries\nlibrary(tidyverse)\nlibrary(igraph)\n\n# Read input from file\ninput <- read_lines(\"../input/day16.txt\", skip_empty_rows = TRUE)", + "text": "# Libraries\nlibrary(tidyverse)\n\n# Read input from file\ninput <- read_lines(\"../input/day02.txt\") |> \n map(~parse_number(str_split_1(.x, \" \")))", "crumbs": [ "2024", - "Day 16" + "Day 2" ] }, { - "objectID": "2024/R/day16.html#part-1", - "href": "2024/R/day16.html#part-1", - "title": "Day 16", + "objectID": "2024/R/day02.html#part-1", + "href": "2024/R/day02.html#part-1", + "title": "Day 2", "section": "Part 1", - "text": "Part 1\nConvert text input into a weighted, undirected graph\n\n# Convert input to a data frame\ndf <- input |> \n str_split(\"\") |> \n unlist() |> \n as_tibble_col(column_name = \"cell\") |> \n mutate(\n input_id = row_number() - 1,\n row = floor(input_id / length(input)),\n col = floor(input_id %% length(input))\n )\n\n# Convert borders between grid cells to graph vertices and map edges by cell\nborders <- df |> \n mutate(border_e = (cell != \"#\" & lead(cell) != \"#\"), .by = row) |> \n mutate(border_s = (cell != \"#\" & lead(cell) != \"#\"), .by = col) |> \n mutate(\n vtx_id_e = case_when(border_e ~ cumsum(border_e)),\n vtx_id_s = case_when(border_s ~ cumsum(border_s) + max(vtx_id_e, na.rm = T))\n ) |> \n mutate(vtx_id_n = lag(vtx_id_s), .by = col) |> \n mutate(vtx_id_w = lag(vtx_id_e), .by = row) |> \n mutate(\n conn_ns = map2(vtx_id_n, vtx_id_s, ~ na.omit(c(.x, .y))),\n conn_ew = map2(vtx_id_e, vtx_id_w, ~ na.omit(c(.x, .y))),\n conn_ne = map2(vtx_id_n, vtx_id_e, ~ na.omit(c(.x, .y))),\n conn_nw = map2(vtx_id_n, vtx_id_w, ~ na.omit(c(.x, .y))),\n conn_se = map2(vtx_id_s, vtx_id_e, ~ na.omit(c(.x, .y))),\n conn_sw = map2(vtx_id_s, vtx_id_w, ~ na.omit(c(.x, .y))),\n )\n\n# Extract the list of all vertices\nvertices <- c(borders$vtx_id_e, borders$vtx_id_s) |> \n na.omit() |> \n sort()\n\n# Convert vertices and edges to an adjacency matrix\nmtx <- borders |> \n # Unnest lists of edge connections between vertices\n select(starts_with(\"conn\")) |> \n pivot_longer(everything(), names_to = \"conn\", names_prefix = \"conn_\") |> \n unnest_wider(value, names_sep = \"_\") |> \n drop_na(value_1, value_2) |> \n # Rotations get an extra 1k added to the weight\n mutate(weight = case_match(conn, c(\"ns\", \"ew\") ~ 1, .default = 1001)) |> \n select(-conn) |> \n # Convert to matrix format, where unconnected vertices have weight 0\n complete(value_1 = vertices, value_2 = vertices, fill = list(weight = 0)) |> \n arrange(value_1, value_2) |> \n pivot_wider(names_from = value_2, values_from = weight) |> \n column_to_rownames(var = \"value_1\") |> \n as.matrix()\n\n# Make matrix symmetric (for an undirected graph)\nsym_mtx <- pmax(mtx, t(mtx))\n\n# Convert adjacency matrix to a graph\ng <- graph_from_adjacency_matrix(sym_mtx, mode = \"undirected\", weighted = TRUE)\n\nDetermine possible starting and ending locations from the input\n\nspecial_cells <- borders |> \n filter(cell %in% c(\"S\", \"E\")) |> \n select(cell, starts_with(\"vtx_id\")) |> \n pivot_longer(\n starts_with(\"vtx_id\"), \n names_prefix = \"vtx_id_\",\n names_to = \"dir\",\n values_to = \"vertex\"\n ) |> \n drop_na(vertex)\n\n# Create all combinations of start & end cell borders\ncombos <- special_cells |> \n filter(cell == \"S\") |> \n mutate(\n init_rotation = case_match(dir, \"e\" ~ 0, c(\"n\", \"s\") ~ 1, \"w\" ~ 2) * 1000\n ) |>\n select(start_vertex = vertex, init_rotation) |> \n cross_join(\n special_cells |> \n filter(cell == \"E\") |> \n select(end_vertex = vertex)\n )\n\nFind the minimum path distance for each start/end vertex combo:\n\nmin_dist <- combos |> \n mutate(\n dist = map2_int(\n start_vertex, \n end_vertex, \n ~ distances(g, .x, .y)) + init_rotation + 1\n ) |> \n slice_min(dist)\n\nmin_dist |> \n pull(dist)\n\n[1] 102504", + "text": "Part 1\n\n# Compute difference between consecutive integers\nseq_gaps <- function(seq)\n head(lead(seq) - seq, -1)\n\n# Check whether the sequence is incr/decr with gaps between 1 and 3\ngaps_are_valid <- function(gaps)\n (all(gaps < 0) | all(gaps > 0)) & all(between(abs(gaps), 1, 3))\n\n# Count number of safe reports\ninput |> \n map(seq_gaps) |> \n map_lgl(gaps_are_valid) |> \n sum()\n\n[1] 306", "crumbs": [ "2024", - "Day 16" + "Day 2" ] }, { - "objectID": "2024/R/day16.html#part-2", - "href": "2024/R/day16.html#part-2", - "title": "Day 16", + "objectID": "2024/R/day02.html#part-2", + "href": "2024/R/day02.html#part-2", + "title": "Day 2", "section": "Part 2", - "text": "Part 2\nPull all paths that have the minimum distance from start to end:\n\nshortest_paths <- min_dist |> \n pmap(function(start_vertex, init_rotation, end_vertex, ...) {\n all_shortest_paths(g, start_vertex, end_vertex)$vpaths\n }) |> \n flatten() |> \n map(as.integer)\n\n\npath_vertices <- shortest_paths |> \n unlist() |> \n unique() |> \n sort()\n\nCount all non-wall cells with a border in the shortest path vertex list:\n\nborders |> \n select(cell, input_id, starts_with(\"vtx_id\")) |> \n pivot_longer(starts_with(\"vtx_id\")) |> \n drop_na(value) |> \n filter(map_lgl(value, ~ .x %in% path_vertices)) |> \n filter(cell != \"#\") |> \n distinct(input_id) |> \n nrow()\n\n[1] 535", + "text": "Part 2\n\ntibble(input) |> \n \n # For each report, create a set of versions where each level is removed\n mutate(\n id = row_number(),\n mod = map(input, \\(seq) map(1:length(seq), \\(n) seq[-c(n)])),\n ) |> \n unnest(mod) |> \n \n # Check validity of each report and its altered versions\n mutate(\n report_is_safe = map_lgl(input, ~ gaps_are_valid(seq_gaps(.x))),\n mod_is_safe = map_lgl(mod, ~ gaps_are_valid(seq_gaps(.x))),\n is_safe = report_is_safe | mod_is_safe\n ) |> \n summarize(is_safe = any(is_safe), .by = id) |> \n \n # Count all safe reports\n summarize(total = sum(is_safe)) |> \n pull()\n\n[1] 366", "crumbs": [ "2024", - "Day 16" + "Day 2" + ] + }, + { + "objectID": "2024/R/day17.html", + "href": "2024/R/day17.html", + "title": "Day 17", + "section": "", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day17.txt\", skip_empty_rows = TRUE) |> \n unglue_data(patterns = c(\n \"{label}: {value}\"\n ))", + "crumbs": [ + "2024", + "Day 17" + ] + }, + { + "objectID": "2024/R/day17.html#setup", + "href": "2024/R/day17.html#setup", + "title": "Day 17", + "section": "", + "text": "# Libraries\nlibrary(tidyverse)\nlibrary(unglue)\n\n# Read input from file\ninput <- read_lines(\"../input/day17.txt\", skip_empty_rows = TRUE) |> \n unglue_data(patterns = c(\n \"{label}: {value}\"\n ))", + "crumbs": [ + "2024", + "Day 17" + ] + }, + { + "objectID": "2024/R/day17.html#part-1", + "href": "2024/R/day17.html#part-1", + "title": "Day 17", + "section": "Part 1", + "text": "Part 1\nInitialize the machine from the text input:\n\nprogram <- input |> \n filter(label == \"Program\") |> \n pull(value) |> \n str_split_1(\",\") |> \n as.integer()\n\nA <- input |> \n filter(label == \"Register A\") |> \n pull(value) |> \n as.integer()\n\nB <- input |> \n filter(label == \"Register B\") |> \n pull(value) |> \n as.integer()\n\nC <- input |> \n filter(label == \"Register C\") |> \n pull(value) |> \n as.integer()\n\nmachine <- list(program = program, A = A, B = B, C = C, pointer = 0L, output = NULL)\n\nDefine machine’s helper functions:\n\ncombo <- function(machine, operand) {\n case_match(operand,\n 0 ~ 0,\n 1 ~ 1,\n 2 ~ 2,\n 3 ~ 3,\n 4 ~ machine$A,\n 5 ~ machine$B,\n 6 ~ machine$C\n )\n}\n\nrun_opcode <- function(machine, opcode, operand) {\n func <- case_match(opcode, \n 0 ~ \"adv\",\n 1 ~ \"bxl\",\n 2 ~ \"bst\",\n 3 ~ \"jnz\",\n 4 ~ \"bxc\",\n 5 ~ \"out\",\n 6 ~ \"bdv\",\n 7 ~ \"cdv\"\n )\n \n get(func)(machine, operand)\n}\n\nrun_machine <- function(machine) {\n while (machine$pointer < length(machine$program)) {\n opcode <- machine$program[machine$pointer + 1]\n operand <- machine$program[machine$pointer + 2]\n machine <- run_opcode(machine, opcode, operand)\n }\n print(machine$output)\n}\n\nDefine the opcode functions:\n\nadv <- function(machine, operand) {\n machine$A <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbxl <- function(machine, operand) {\n machine$B <- bitwXor(machine$B, operand)\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbst <- function(machine, operand) {\n machine$B <- combo(machine, operand) %% 8\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\njnz <- function(machine, operand) {\n if (machine$A != 0) \n machine$pointer <- operand\n else \n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbxc <- function(machine, operand) {\n machine$B <- bitwXor(machine$B, machine$C)\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nout <- function(machine, operand) {\n machine$output <- str_c(\n machine$output, \n combo(machine, operand) %% 8, \n sep = \",\"\n )\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nbdv <- function(machine, operand) {\n machine$B <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\ncdv <- function(machine, operand) {\n machine$C <- floor(machine$A / 2^combo(machine, operand))\n machine$pointer <- machine$pointer + 2\n return(machine)\n}\n\nRun on puzzle input:\n\nrun_machine(machine)\n\n[1] \"3,1,4,3,1,7,1,6,3\"", + "crumbs": [ + "2024", + "Day 17" ] }, {