1 Setup
1.1 Libraries
library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
library(tidyr)
1.2 Retrieve Data from AoC
session_cookie <- set_cookies(session = keyring::key_get("AoC-GitHub-Cookie"))
base_url <- paste0("https://adventofcode.com/", params$year, "/day/", params$task_nr)
puzzle <- GET(base_url,
session_cookie) %>%
content(encoding = "UTF-8") %>%
xml_find_all("///article") %>%
lapply(as.character)
parse_puzzle_data <- function(text_block = readClipboard()) {
if (length(text_block) == 1L) {
text_block <- text_block %>%
str_split("\n") %>%
extract2(1L)
}
text_block[nzchar(text_block)] %>%
str_extract_all("\\d+") %>%
extract2(1L) %>%
as.numeric() %>%
matrix(ncol = 2L, byrow = TRUE) %>%
set_colnames(c("from", "to")) %>%
as_tibble()
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 2
2.1 Part 1
2.1.1 Description
— Day 2: Gift Shop —
You get inside and take the elevator to its only other stop: the gift shop. “Thank you for visiting the North Pole!” gleefully exclaims a nearby sign. You aren’t sure who is even allowed to visit the North Pole, but you know you can access the lobby through here, and from there you can access the rest of the North Pole base.
As you make your way through the surprisingly extensive selection, one of the clerks recognizes you and asks for your help.
As it turns out, one of the younger Elves was playing on a gift shop computer and managed to add a whole bunch of invalid product IDs to their gift shop database! Surely, it would be no trouble for you to identify the invalid product IDs for them, right?
They’ve even checked most of the product ID ranges already; they only have a few product ID ranges (your puzzle input) that you’ll need to check. For example:
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,
1698522-1698528,446443-446449,38593856-38593862,565653-565659,
824824821-824824827,2121212118-2121212124
(The ID ranges are wrapped here for legibility; in your input, they appear on a single long line.)
The ranges are separated by commas (,); each range gives its first ID and last ID separated by a dash (-).
Since the young Elf was just doing silly patterns, you can find the invalid IDs by looking for any ID which is made only of some sequence of digits repeated twice. So, 55 (5 twice), 6464 (64 twice), and 123123 (123 twice) would all be invalid IDs.
None of the numbers have leading zeroes; 0101 isn’t an ID at all. (101 is a valid ID that you would ignore.)
Your job is to find all of the invalid IDs that appear in the given ranges. In the above example:
-
11-22has two invalid IDs,11and22. -
95-115has one invalid ID,99. -
998-1012has one invalid ID,1010. -
1188511880-1188511890has one invalid ID,1188511885. -
222220-222224has one invalid ID,222222. -
1698522-1698528contains no invalid IDs. -
446443-446449has one invalid ID,446446. -
38593856-38593862has one invalid ID,38593859. - The rest of the ranges contain no invalid IDs.
Adding up all the invalid IDs in this example produces 1227775554.
What do you get if you add up all of the invalid IDs?
2.1.2 Solution
For the first part we look at the digits of the limits. If both limits have an odd number of digits, there are no valid codes. If one limit has an odd and the other an even number of digits, we generate the largest (smallest) valid number with the even number of digits. Once we determined these 2 limits, we generate a list of valid digits at each position. Since we are looking at pairs, we only need to do that for the first half of positions. If the lower and the upper limit have the same digit at position i, we can only need to use that digit. This reduces the amount of numbers to generate quite a bit. Eventually we generate all numbers and check which are within the limits.
N.B. Some functionality (especially NA and include_middle) is only needed for the
second part.
get_nr_digits <- function(x) {
n <- log10(x) %>%
ceiling() %>%
as.integer()
if (n == log10(x)) {
n <- n + 1L
}
n
}
to_digits <- function(x) {
n <- get_nr_digits(x)
digits <- integer(n)
for (i in n:1) {
digits[i] <- x %% 10L
x <- x %/% 10L
}
digits
}
generate_twins <- function(..., len = NA_integer_) {
nr <- paste(..., sep = "")
if (any(is.na(c(...)))) {
-1
} else {
paste(nr, nr, sep = "") %>%
as.numeric()
}
}
generate_numbers <- function(from, to, gen_fn, include_middle) {
c1 <- get_nr_digits(from)
c2 <- get_nr_digits(to)
## assumption: max one digit longer
stopifnot(abs(c1 - c2) <= 1L)
add <- NULL
if (c1 %% 2L == 1L && c2 %% 2L == 1L) {
if (!include_middle) {
## both limits are odd => no valid codes
return(numeric(0L))
} else {
lwr <- from
upr <- to
len <- c1
}
}
if (c1 %% 2L == 0L && c2 %% 2L == 1L) {
if (c2 > 1L) {
base <- rep("1", c2) %>%
paste(collapse = "") %>%
as.numeric()
cand <- 1:9 * base
add <- cand[cand >= from & cand <= to]
}
lwr <- from
upr <- paste(rep("9", c1), collapse = "") %>%
as.numeric()
len <- c1
} else if (c1 %% 2L == 1L && c2 %% 2L == 0L) {
if (c1 > 1L) {
base <- rep("1", c1) %>%
paste(collapse = "") %>%
as.numeric()
cand <- 1:9 * base
cand <- cand + paste(rep("0", c1 - 1L), collapse = "") %>%
as.numeric()
add <- cand[cand >= from & cand <= to]
}
lwr <- paste(c("1", rep("0", c2 - 1L)), collapse = "") %>%
as.numeric()
upr <- to
len <- c2
} else {
lwr <- from
upr <- to
len <- c1
}
d1 <- to_digits(lwr)
d2 <- to_digits(upr)
rng <- rep(list(c(0:9, NA_integer_)), length(d1) / 2L)
for (i in seq_along(rng)) {
if (d1[[i]] == d2[[i]]) {
if (i == 1L) {
rng[[i]] <- d1[i]
} else {
rng[[i]] <- c(d1[i], NA_integer_)
}
} else {
break
}
}
comb <- do.call(expand.grid, rng) %>%
filter(pmap_lgl(., function(...) {
vals <- c(...)
na_pos <- is.na(vals)
!any(na_pos) || all(na_pos[which(na_pos)[1L]:length(vals)])
}))
res <- comb %>%
mutate(nr = pmap(., ~ gen_fn(..., len = len))) %>%
unnest(c(nr)) %>%
distinct(nr) %>%
filter(between(nr, from, to)) %>%
pull(nr)
if (include_middle) {
res <- c(res, add)
}
res
}
find_invalid_codes <- function(ranges, gen_fn, include_middle) {
ranges %>%
mutate(chk = map2(from, to,
~ generate_numbers(.x, .y, gen_fn, include_middle))) %>%
summarize(total = sum(unlist(chk))) %>%
pull(total)
}
find_invalid_codes(puzzle_data, generate_twins, FALSE)
## [1] 30599400849
2.2 Part 2
2.2.1 Description
— Part Two —
The clerk quickly discovers that there are still invalid IDs in the ranges in your list. Maybe the young Elf was doing other silly patterns as well?
Now, an ID is invalid if it is made only of some sequence of digits repeated at least twice. So, 12341234 (1234 two times), 123123123 (123 three times), 1212121212 (12 five times), and 1111111 (1 seven times) are all invalid IDs.
From the same example as before:
-
11-22still has two invalid IDs,11and22. -
95-115now has two invalid IDs,99and111. -
998-1012now has two invalid IDs,999and1010. -
1188511880-1188511890still has one invalid ID,1188511885. -
222220-222224still has one invalid ID,222222. -
1698522-1698528still contains no invalid IDs. -
446443-446449still has one invalid ID,446446. -
38593856-38593862still has one invalid ID,38593859. -
565653-565659now has one invalid ID,565656. -
824824821-824824827now has one invalid ID,824824824. -
2121212118-2121212124now has one invalid ID,2121212121.
Adding up all the invalid IDs in this example produces 4174379265.
What do you get if you add up all of the invalid IDs using these new rules?
2.2.2 Solution
For the second part, we also allow for NA in the candidate lists (except for the first
position). But we look only for candidates which have a consecutive block of NAs at the
end. After removing NAs we have a short block, which fulfills the requirement only if
its length is a divisor of the total length. In case of one uneven limit, we have to make
sure to include the single valid point made of the same digit (if it is within the
bounds).
generate_tuples <- function(..., len) {
vals <- c(...)
vals <- vals[!is.na(vals)]
if (length(vals) == 0L || len %% length(vals) != 0L) {
numeric(0L)
} else {
nr <- paste(vals, collapse = "")
rep(nr, len / length(vals)) %>%
paste(collapse = "") %>%
as.numeric()
}
}
find_invalid_codes(puzzle_data, generate_tuples, TRUE)
## [1] 46270373595