library(tidyverse)
library(stringr)
library(tictoc)
dat <- read_lines("aoc08.txt")
mapmat <- str_split_fixed(dat, "", n = str_length(dat[1]))
dim(mapmat)
[1] 50 50

Part 1

Save the antinodes (not only do we have a while loop in this one but also a global variable; just need a goto statement):

antinodes <- matrix(0, nrow = nrow(mapmat), ncol = nrow(mapmat))

addnode <- function(r, c) {
  if (between(r, 1, nrow(antinodes)) && between(c, 1, ncol(antinodes))) {
    antinodes[r, c] <<- antinodes[r, c] + 1
  }
}

Frequencies:

freqs <- mapmat |> as.vector() |> unique() |> setdiff(".") |> sort()
freqs
 [1] "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "A" "d" "D" "e" "E" "g"
[18] "G" "h" "H" "i" "I" "k" "K" "l" "L" "m" "M" "n" "N" "o" "O" "q" "Q"
[35] "r" "R" "s" "S" "t" "T" "u" "U" "v" "V" "w" "W" "x" "X" "y" "Y" "z"
[52] "Z"
find_antennas <- function(the_map, the_freq) {
  which(the_map == the_freq, arr.ind = TRUE)
}
find_antinodes <- function(p1, p2) {
  dist_row <- abs(p1[1] - p2[1])
  points <- bind_rows(p1, p2) |>
    as.data.frame() |>
    arrange(row)
  mod <- lm(col ~ row, data = points) # LEL
  stopifnot(!(mod |> coef() |> is.na() |> any()))

  min_row <- points[1,]$row - dist_row
  max_row <- points[2,]$row + dist_row
  min_col <- predict(mod, newdat = data.frame(row = min_row))
  max_col <- predict(mod, newdat = data.frame(row = max_row))
  
  addnode(min_row, min_col |> round())
  addnode(max_row, max_col |> round())
}
scan_pairs <- function(the_map, the_freq) {
  locations <- find_antennas(the_map, the_freq)
  pair_i    <- combn(1:nrow(locations), 2)
  
  for (col in 1:ncol(pair_i)) {
    first_r  <- pair_i[1, col]
    second_r <- pair_i[2, col]
    
    find_antinodes(locations[first_r,],
                   locations[second_r,])
  }
}
for (f in freqs) {
  scan_pairs(mapmat, f)
}
sum(antinodes > 0)
[1] 327

Part 2

I deleted this, started again with an even easier edit, and ran it again and it worked.

Reset the antinodes:

antinodes <- matrix(0, nrow = nrow(mapmat), ncol = nrow(mapmat))

Mild tweak to scan across all the rows.

find_antinodes <- function(p1, p2) {
  points <- bind_rows(p1, p2) |>
    as.data.frame() |>
    arrange(row)
  mod <- lm(col ~ row, data = points) # LEL
  stopifnot(!(mod |> coef() |> is.na() |> any()))
  
  for (r in 1:nrow(mapmat)) {
    c <- predict(mod, newdat = data.frame(row = r))
    if (abs(c - round(c)) < 1e-6)
      addnode(r, c |> round())
  }
}
tic()
for (f in freqs) {
  scan_pairs(mapmat, f)
}
toc()
6.08 sec elapsed
sum(antinodes > 0)
[1] 1233
LS0tDQp0aXRsZTogIkRheSA4OiBSZXNvbmFudCBDb2xsaW5lYXJpdHkiDQphdXRob3I6IEFuZGlGDQpkYXRlOiA4IERlYyAyMDI0DQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUNCi0tLQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KHRpY3RvYykNCmBgYA0KDQpgYGB7cn0NCmRhdCA8LSByZWFkX2xpbmVzKCJhb2MwOC50eHQiKQ0KbWFwbWF0IDwtIHN0cl9zcGxpdF9maXhlZChkYXQsICIiLCBuID0gc3RyX2xlbmd0aChkYXRbMV0pKQ0KYGBgDQoNCg0KYGBge3J9DQpkaW0obWFwbWF0KQ0KYGBgDQoNCiMjIyBQYXJ0IDENCg0KU2F2ZSB0aGUgYW50aW5vZGVzIChub3Qgb25seSBkbyB3ZSBoYXZlIGEgd2hpbGUgbG9vcCBpbiB0aGlzIG9uZSBidXQgYWxzbyBhIGdsb2JhbCB2YXJpYWJsZTsganVzdCBuZWVkIGEgZ290byBzdGF0ZW1lbnQpOg0KDQpgYGB7cn0NCmFudGlub2RlcyA8LSBtYXRyaXgoMCwgbnJvdyA9IG5yb3cobWFwbWF0KSwgbmNvbCA9IG5yb3cobWFwbWF0KSkNCg0KYWRkbm9kZSA8LSBmdW5jdGlvbihyLCBjKSB7DQogIGlmIChiZXR3ZWVuKHIsIDEsIG5yb3coYW50aW5vZGVzKSkgJiYgYmV0d2VlbihjLCAxLCBuY29sKGFudGlub2RlcykpKSB7DQogICAgYW50aW5vZGVzW3IsIGNdIDw8LSBhbnRpbm9kZXNbciwgY10gKyAxDQogIH0NCn0NCmBgYA0KDQoNCkZyZXF1ZW5jaWVzOg0KDQpgYGB7cn0NCmZyZXFzIDwtIG1hcG1hdCB8PiBhcy52ZWN0b3IoKSB8PiB1bmlxdWUoKSB8PiBzZXRkaWZmKCIuIikgfD4gc29ydCgpDQpmcmVxcw0KYGBgDQoNCg0KYGBge3J9DQpmaW5kX2FudGVubmFzIDwtIGZ1bmN0aW9uKHRoZV9tYXAsIHRoZV9mcmVxKSB7DQogIHdoaWNoKHRoZV9tYXAgPT0gdGhlX2ZyZXEsIGFyci5pbmQgPSBUUlVFKQ0KfQ0KYGBgDQoNCg0KYGBge3J9DQpmaW5kX2FudGlub2RlcyA8LSBmdW5jdGlvbihwMSwgcDIpIHsNCiAgZGlzdF9yb3cgPC0gYWJzKHAxWzFdIC0gcDJbMV0pDQogIHBvaW50cyA8LSBiaW5kX3Jvd3MocDEsIHAyKSB8Pg0KICAgIGFzLmRhdGEuZnJhbWUoKSB8Pg0KICAgIGFycmFuZ2Uocm93KQ0KICBtb2QgPC0gbG0oY29sIH4gcm93LCBkYXRhID0gcG9pbnRzKSAjIExFTA0KICBzdG9waWZub3QoIShtb2QgfD4gY29lZigpIHw+IGlzLm5hKCkgfD4gYW55KCkpKQ0KDQogIG1pbl9yb3cgPC0gcG9pbnRzWzEsXSRyb3cgLSBkaXN0X3Jvdw0KICBtYXhfcm93IDwtIHBvaW50c1syLF0kcm93ICsgZGlzdF9yb3cNCiAgbWluX2NvbCA8LSBwcmVkaWN0KG1vZCwgbmV3ZGF0ID0gZGF0YS5mcmFtZShyb3cgPSBtaW5fcm93KSkNCiAgbWF4X2NvbCA8LSBwcmVkaWN0KG1vZCwgbmV3ZGF0ID0gZGF0YS5mcmFtZShyb3cgPSBtYXhfcm93KSkNCiAgDQogIGFkZG5vZGUobWluX3JvdywgbWluX2NvbCB8PiByb3VuZCgpKQ0KICBhZGRub2RlKG1heF9yb3csIG1heF9jb2wgfD4gcm91bmQoKSkNCn0NCmBgYA0KDQoNCmBgYHtyfQ0Kc2Nhbl9wYWlycyA8LSBmdW5jdGlvbih0aGVfbWFwLCB0aGVfZnJlcSkgew0KICBsb2NhdGlvbnMgPC0gZmluZF9hbnRlbm5hcyh0aGVfbWFwLCB0aGVfZnJlcSkNCiAgcGFpcl9pICAgIDwtIGNvbWJuKDE6bnJvdyhsb2NhdGlvbnMpLCAyKQ0KICANCiAgZm9yIChjb2wgaW4gMTpuY29sKHBhaXJfaSkpIHsNCiAgICBmaXJzdF9yICA8LSBwYWlyX2lbMSwgY29sXQ0KICAgIHNlY29uZF9yIDwtIHBhaXJfaVsyLCBjb2xdDQogICAgDQogICAgZmluZF9hbnRpbm9kZXMobG9jYXRpb25zW2ZpcnN0X3IsXSwNCiAgICAgICAgICAgICAgICAgICBsb2NhdGlvbnNbc2Vjb25kX3IsXSkNCiAgfQ0KfQ0KYGBgDQoNCg0KYGBge3J9DQpmb3IgKGYgaW4gZnJlcXMpIHsNCiAgc2Nhbl9wYWlycyhtYXBtYXQsIGYpDQp9DQpgYGANCg0KDQpgYGB7cn0NCnN1bShhbnRpbm9kZXMgPiAwKQ0KYGBgDQoNCg0KIyMjIFBhcnQgMg0KDQpJIGRlbGV0ZWQgdGhpcywgc3RhcnRlZCBhZ2FpbiB3aXRoIGFuIGV2ZW4gZWFzaWVyIGVkaXQsIGFuZCByYW4gaXQgYWdhaW4gYW5kIGl0IHdvcmtlZC4NCg0KUmVzZXQgdGhlIGFudGlub2RlczoNCg0KYGBge3J9DQphbnRpbm9kZXMgPC0gbWF0cml4KDAsIG5yb3cgPSBucm93KG1hcG1hdCksIG5jb2wgPSBucm93KG1hcG1hdCkpDQpgYGANCg0KTWlsZCB0d2VhayB0byBzY2FuIGFjcm9zcyBhbGwgdGhlIHJvd3MuDQoNCmBgYHtyfQ0KZmluZF9hbnRpbm9kZXMgPC0gZnVuY3Rpb24ocDEsIHAyKSB7DQogIHBvaW50cyA8LSBiaW5kX3Jvd3MocDEsIHAyKSB8Pg0KICAgIGFzLmRhdGEuZnJhbWUoKSB8Pg0KICAgIGFycmFuZ2Uocm93KQ0KICBtb2QgPC0gbG0oY29sIH4gcm93LCBkYXRhID0gcG9pbnRzKSAjIExFTA0KICBzdG9waWZub3QoIShtb2QgfD4gY29lZigpIHw+IGlzLm5hKCkgfD4gYW55KCkpKQ0KICANCiAgZm9yIChyIGluIDE6bnJvdyhtYXBtYXQpKSB7DQogICAgYyA8LSBwcmVkaWN0KG1vZCwgbmV3ZGF0ID0gZGF0YS5mcmFtZShyb3cgPSByKSkNCiAgICBpZiAoYWJzKGMgLSByb3VuZChjKSkgPCAxZS02KQ0KICAgICAgYWRkbm9kZShyLCBjIHw+IHJvdW5kKCkpDQogIH0NCn0NCmBgYA0KDQoNCmBgYHtyfQ0KdGljKCkNCmZvciAoZiBpbiBmcmVxcykgew0KICBzY2FuX3BhaXJzKG1hcG1hdCwgZikNCn0NCnRvYygpDQpgYGANCg0KDQpgYGB7cn0NCnN1bShhbnRpbm9kZXMgPiAwKQ0KYGBgDQoNCg0KDQo=