library(tidyverse)
library(stringr)
library(tictoc)
Read in the problem:
dat <- read_lines("aoc04.txt")
lets <- str_split_fixed(dat, "", n = str_length(dat[1]))
Here’s the left corner:
lets[1:10, 1:10]
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,] "S" "A" "M" "M" "S" "M" "S" "X" "M" "M"
[2,] "M" "A" "S" "A" "A" "A" "M" "A" "M" "M"
[3,] "S" "A" "M" "X" "S" "M" "S" "M" "M" "M"
[4,] "M" "A" "S" "A" "X" "A" "A" "A" "S" "X"
[5,] "S" "X" "M" "A" "S" "M" "S" "M" "S" "A"
[6,] "S" "X" "M" "A" "M" "A" "X" "A" "M" "M"
[7,] "M" "A" "X" "S" "X" "X" "M" "A" "M" "X"
[8,] "S" "S" "M" "M" "A" "M" "S" "A" "M" "X"
[9,] "X" "M" "A" "M" "A" "M" "S" "A" "S" "X"
[10,] "X" "S" "M" "M" "A" "X" "S" "A" "M" "X"
nrow(lets)
[1] 140
ncol(lets)
[1] 140
I don’t want to worry about walking off the edge of the matrix; this
means I don’t have to:
val <- function(mat, r, c) {
if (between(r, 1, nrow(mat)) &&
between(c, 1, ncol(mat)))
mat[r, c]
else
NA
}
Part 1
Turn a vector, potentially with missingness (assuming it’s at the
ends), into a string with NAs removed:
stringify <- function(vec) {
vec |> na.omit() |> paste(collapse = "")
}
Walk along the matrix, mat, starting at the start and
stepping the steps, e.g., using a negative step to move backwards.
walk <- function(mat, start_r, start_c, r_step, c_step, n) {
res <- rep(NA, n)
r <- start_r
c <- start_c
i <- 1
while (i <= n) {
res[i] <- mat |> val(r, c)
r <- r + r_step
c <- c + c_step
i <- i + 1
}
stringify(res)
}
For example, east:
lets |> walk(1, 1, 1, 1, 4)
[1] "SAMA"
South west:
lets |> walk(1, 4, 1, -1, 4)
[1] "MSAM"
All the directions we want to wander:
directions <- expand.grid(r_step = c(-1, 0, 1),
c_step = c(-1, 0, 1)) |>
filter(r_step != 0 | c_step != 0)
directions
Scan in all directions from point (r,c):
scan_point <- function(mat, r, c, n) {
map2_chr(directions$r_step,
directions$c_step,
\(r_step, c_step) walk(mat, r, c, r_step, c_step, n))
}
everywhere <- expand.grid(r = 1:nrow(lets),
c = 1:ncol(lets))
tic()
all_of_em <- map2(everywhere$r,
everywhere$c,
\(r, c) scan_point(lets, r, c, 4))
toc()
45.55 sec elapsed
That was very slow, maybe because I saved 156800 strings, whereas I
could just have counted matches.
The answer:
map_int(all_of_em, \(xs) sum(xs == "XMAS")) |> sum()
[1] 2547
Part 2
Sweep across, walking three steps southeast and southwest. Count how
often we get “MAS” or “SAM” on those.
target <- c("MAS", "SAM")
is_cross_mas <- function(mat, r, c) {
text_SE <- walk(mat, r - 1, c - 1, 1, 1, 3)
text_SW <- walk(mat, r - 1, c + 1, 1, -1, 3)
(text_SE %in% target) && (text_SW %in% target)
}
I could strip off the outer border where there can’t be a match;
however, val defined at the top means I don’t have to.
tic()
cross_count <- map2_lgl(
everywhere$r,
everywhere$c,
\(r, c) is_cross_mas(lets, r, c)
)
toc()
8.64 sec elapsed
The answer:
sum(cross_count)
[1] 1939
LS0tDQp0aXRsZTogIkRheSA0OiBDZXJlcyBTZWFyY2giDQphdXRob3I6IEFuZGlGDQpkYXRlOiA0IERlYyAyMDI0DQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUNCi0tLQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KHRpY3RvYykNCmBgYA0KDQpSZWFkIGluIHRoZSBwcm9ibGVtOg0KDQpgYGB7cn0NCmRhdCA8LSByZWFkX2xpbmVzKCJhb2MwNC50eHQiKQ0KbGV0cyA8LSBzdHJfc3BsaXRfZml4ZWQoZGF0LCAiIiwgbiA9IHN0cl9sZW5ndGgoZGF0WzFdKSkNCmBgYA0KDQpIZXJlJ3MgdGhlIGxlZnQgY29ybmVyOg0KDQpgYGB7cn0NCmxldHNbMToxMCwgMToxMF0NCmBgYA0KYGBge3J9DQpucm93KGxldHMpDQpuY29sKGxldHMpDQpgYGANCg0KSSBkb24ndCB3YW50IHRvIHdvcnJ5IGFib3V0IHdhbGtpbmcgb2ZmIHRoZSBlZGdlIG9mIHRoZSBtYXRyaXg7IHRoaXMgbWVhbnMgSSBkb24ndCBoYXZlIHRvOg0KDQpgYGB7cn0NCnZhbCA8LSBmdW5jdGlvbihtYXQsIHIsIGMpIHsNCiAgaWYgKGJldHdlZW4ociwgMSwgbnJvdyhtYXQpKSAmJg0KICAgICAgYmV0d2VlbihjLCAxLCBuY29sKG1hdCkpKQ0KICAgIG1hdFtyLCBjXQ0KICBlbHNlDQogICAgTkENCn0NCmBgYA0KDQoNCiMjIyBQYXJ0IDENCg0KVHVybiBhIHZlY3RvciwgcG90ZW50aWFsbHkgd2l0aCBtaXNzaW5nbmVzcyAoYXNzdW1pbmcgaXQncyBhdCB0aGUgZW5kcyksIGludG8gYSBzdHJpbmcgd2l0aCBOQXMgcmVtb3ZlZDoNCg0KYGBge3J9DQpzdHJpbmdpZnkgPC0gZnVuY3Rpb24odmVjKSB7DQogIHZlYyB8PiBuYS5vbWl0KCkgfD4gcGFzdGUoY29sbGFwc2UgPSAiIikNCn0NCmBgYA0KDQoNCldhbGsgYWxvbmcgdGhlIG1hdHJpeCwgX21hdF8sIHN0YXJ0aW5nIGF0IHRoZSBzdGFydCBhbmQgc3RlcHBpbmcgdGhlIHN0ZXBzLCBlLmcuLCB1c2luZyBhIG5lZ2F0aXZlIHN0ZXAgdG8gbW92ZSBiYWNrd2FyZHMuDQoNCmBgYHtyfQ0Kd2FsayA8LSBmdW5jdGlvbihtYXQsIHN0YXJ0X3IsIHN0YXJ0X2MsIHJfc3RlcCwgY19zdGVwLCBuKSB7DQogIHJlcyA8LSByZXAoTkEsIG4pDQoNCiAgciA8LSBzdGFydF9yDQogIGMgPC0gc3RhcnRfYw0KICBpIDwtIDENCiAgDQogIHdoaWxlIChpIDw9IG4pIHsNCiAgICByZXNbaV0gPC0gbWF0IHw+IHZhbChyLCBjKQ0KICAgIHIgPC0gciArIHJfc3RlcA0KICAgIGMgPC0gYyArIGNfc3RlcA0KICAgIGkgPC0gaSArIDENCiAgfQ0KICANCiAgc3RyaW5naWZ5KHJlcykNCn0NCmBgYA0KDQoNCkZvciBleGFtcGxlLCBlYXN0Og0KDQpgYGB7cn0NCmxldHMgfD4gd2FsaygxLCAxLCAxLCAxLCA0KQ0KYGBgDQoNClNvdXRoIHdlc3Q6DQoNCmBgYHtyfQ0KbGV0cyB8PiB3YWxrKDEsIDQsIDEsIC0xLCA0KQ0KYGBgDQoNCkFsbCB0aGUgZGlyZWN0aW9ucyB3ZSB3YW50IHRvIHdhbmRlcjoNCg0KYGBge3J9DQpkaXJlY3Rpb25zIDwtIGV4cGFuZC5ncmlkKHJfc3RlcCA9IGMoLTEsIDAsIDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjX3N0ZXAgPSBjKC0xLCAwLCAxKSkgfD4NCiAgZmlsdGVyKHJfc3RlcCAhPSAwIHwgY19zdGVwICE9IDApDQpkaXJlY3Rpb25zDQpgYGANCg0KDQpTY2FuIGluIGFsbCBkaXJlY3Rpb25zIGZyb20gcG9pbnQgKHIsYyk6DQoNCmBgYHtyfQ0Kc2Nhbl9wb2ludCA8LSBmdW5jdGlvbihtYXQsIHIsIGMsIG4pIHsNCiAgbWFwMl9jaHIoZGlyZWN0aW9ucyRyX3N0ZXAsDQogICAgICAgICAgIGRpcmVjdGlvbnMkY19zdGVwLA0KICAgICAgICAgICBcKHJfc3RlcCwgY19zdGVwKSB3YWxrKG1hdCwgciwgYywgcl9zdGVwLCBjX3N0ZXAsIG4pKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KZXZlcnl3aGVyZSA8LSBleHBhbmQuZ3JpZChyID0gMTpucm93KGxldHMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjID0gMTpuY29sKGxldHMpKQ0KYGBgDQoNCmBgYHtyfQ0KdGljKCkNCmFsbF9vZl9lbSA8LSBtYXAyKGV2ZXJ5d2hlcmUkciwNCiAgICAgICAgICAgICAgICAgIGV2ZXJ5d2hlcmUkYywNCiAgICAgICAgICAgICAgICAgIFwociwgYykgc2Nhbl9wb2ludChsZXRzLCByLCBjLCA0KSkNCnRvYygpDQpgYGANCg0KVGhhdCB3YXMgdmVyeSBzbG93LCBtYXliZSBiZWNhdXNlIEkgc2F2ZWQgYHIgbWFwX2ludChhbGxfb2ZfZW0sIFwoeHMpIGxlbmd0aCh4cykpIHw+IHN1bSgpYCBzdHJpbmdzLCB3aGVyZWFzIEkgY291bGQganVzdCBoYXZlIGNvdW50ZWQgbWF0Y2hlcy4NCg0KVGhlIGFuc3dlcjoNCg0KYGBge3J9DQptYXBfaW50KGFsbF9vZl9lbSwgXCh4cykgc3VtKHhzID09ICJYTUFTIikpIHw+IHN1bSgpDQpgYGANCg0KDQojIyMgUGFydCAyDQoNClN3ZWVwIGFjcm9zcywgd2Fsa2luZyB0aHJlZSBzdGVwcyBzb3V0aGVhc3QgYW5kIHNvdXRod2VzdC4gQ291bnQgaG93IG9mdGVuIHdlIGdldCAiTUFTIiBvciAiU0FNIiBvbiB0aG9zZS4NCg0KYGBge3J9DQp0YXJnZXQgPC0gYygiTUFTIiwgIlNBTSIpDQoNCmlzX2Nyb3NzX21hcyA8LSBmdW5jdGlvbihtYXQsIHIsIGMpIHsNCiAgdGV4dF9TRSA8LSB3YWxrKG1hdCwgciAtIDEsIGMgLSAxLCAxLCAxLCAzKQ0KICB0ZXh0X1NXIDwtIHdhbGsobWF0LCByIC0gMSwgYyArIDEsIDEsIC0xLCAzKQ0KICANCiAgKHRleHRfU0UgJWluJSB0YXJnZXQpICYmICh0ZXh0X1NXICVpbiUgdGFyZ2V0KQ0KfQ0KYGBgDQoNCg0KSSBjb3VsZCBzdHJpcCBvZmYgdGhlIG91dGVyIGJvcmRlciB3aGVyZSB0aGVyZSBjYW4ndCBiZSBhIG1hdGNoOyBob3dldmVyLCBfdmFsXyBkZWZpbmVkIGF0IHRoZSB0b3AgbWVhbnMgSSBkb24ndCBoYXZlIHRvLiANCg0KYGBge3J9DQp0aWMoKQ0KY3Jvc3NfY291bnQgPC0gbWFwMl9sZ2woDQogICAgICAgICAgICAgICAgZXZlcnl3aGVyZSRyLA0KICAgICAgICAgICAgICAgIGV2ZXJ5d2hlcmUkYywNCiAgICAgICAgICAgICAgICBcKHIsIGMpIGlzX2Nyb3NzX21hcyhsZXRzLCByLCBjKQ0KICAgICAgICAgICAgICAgKQ0KdG9jKCkNCmBgYA0KDQpUaGUgYW5zd2VyOg0KDQpgYGB7cn0NCnN1bShjcm9zc19jb3VudCkNCmBgYA0KDQoNCg0K