1 Setup
1.1 Libraries
library(httr)
library(xml2)
library(tibble)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
library(stringi)
library(knitr)
library(cli)
library(igraph)
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) %>%
keep(nzchar)
}
ops <- text_block %>%
str_split("\\s") %>%
map(function(ops) {
c(rep(NA_character_, 5L - length(ops)), ops)
}) %>%
do.call(rbind, .)
ops <- ops[, -4]
colnames(ops) <- c("lhs", "op", "rhs", "res")
as_tibble(ops)
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 7
2.1 Part 1
2.1.1 Description
— Day 7: Some Assembly Required —
This year, Santa brought little Bobby Tables a set of wires and bitwise logic gates! Unfortunately, little Bobby is a little under the recommended age range, and he needs help assembling the circuit.
Each wire has an identifier (some lowercase letters) and can carry a 16-bit signal (a number from 0 to 65535). A signal is provided to each wire by a gate, another wire, or some specific value. Each wire can only get a signal from one source, but can provide its signal to multiple destinations. A gate provides no signal until all of its inputs have a signal.
The included instructions booklet describes how to connect the parts together: x AND y -> z means to connect wires x and y to an AND gate, and then connect its output to wire z.
For example:
-
123 -> xmeans that the signal123is provided to wirex. -
x AND y -> zmeans that the bitwise AND of wirexand wireyis provided to wirez. -
p LSHIFT 2 -> qmeans that the value from wirepis left-shifted by2and then provided to wireq. -
NOT e -> fmeans that the bitwise complement of the value from wireeis provided to wiref.
Other possible gates include OR (bitwise OR) and RSHIFT (right-shift). If, for some reason, you’d like to emulate the circuit instead, almost all programming languages (for example, C, JavaScript, or Python) provide operators for these gates.
For example, here is a simple circuit:
123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i
After it is run, these are the signals on the wires:
d: 72
e: 507
f: 492
g: 114
h: 65412
i: 65079
x: 123
y: 456
In little Bobby’s kit’s instructions booklet (provided as your puzzle input), what signal is ultimately provided to wire a?
2.1.2 Solution
We solve this puzzle by means of graph theory.
- For each operation
lhs op rhs -> reswe create the nodeslhs -> op,rhs -> opandop -> res(note that theNOToperator has nolhsand a pure assignment has neitheropnorlhs). - We sort the graph then topologically, that is nodes with no incoming nodes (this will be the number literals) come first, followed by their dependent nodes and so on.
- Then all what is left, is to iterate through the graph in topological order:
- If we hit a node with a number literal, assign this value to the node.
- If we hit an operator node, perform the corresponding operation with the value from
its predecessors (some care has to be taken for the non-commutative
SHIFToperators to make sure their operands are used in the right order) - If we hit a output node, simply copy the value from its predecessor.
- The final result will then be stored in node
a.
construct_graph <- function(ops) {
ops <- ops %>%
mutate(
op_label = case_when(
is.na(op) ~ op,
is.na(lhs) ~ paste(op, rhs, sep = "_"),
TRUE ~ paste(lhs, op, rhs, sep = "_")
)
)
el <- matrix(character(0), 0, 2) %>%
set_colnames(c("from", "to"))
for (i in seq_len(nrow(ops))) {
gate <- ops %>%
slice(i) %>%
as.list()
if (is.na(gate$op)) {
## assignment operator
el <- el %>%
rbind(
cbind(gate$rhs, gate$res)
)
} else if (gate$op == "NOT") {
el <- el %>%
rbind(
cbind(gate$rhs, gate$op_label),
cbind(gate$op_label, gate$res)
)
} else {
el <- el %>%
rbind(
cbind(gate$lhs, gate$op_label),
cbind(gate$rhs, gate$op_label),
cbind(gate$op_label, gate$res)
)
}
}
G <- graph_from_edgelist(
el
)
V(G)$type <- case_when(
!is.na(strtoi(V(G)$name)) ~ "literal",
str_detect(V(G)$name, "^NOT") ~ "not",
str_detect(V(G)$name, "_LSHIFT_") ~ "lshift",
str_detect(V(G)$name, "_RSHIFT_") ~ "rshift",
str_detect(V(G)$name, "_AND_") ~ "and",
str_detect(V(G)$name, "_OR_") ~ "or",
TRUE ~ "var"
)
G
}
calculate_wires <- function(G) {
V(G)$value <- NA_integer_
v_sorted <- topo_sort(G)
for (vi in v_sorted) {
me <- V(G)[vi]$name
nbs <- neighbors(G, vi, "in")
type <- V(G)[vi]$type
if (type == "literal") {
stopifnot(length(nbs) == 0L)
## it is a literal node with a value
V(G)[vi]$value <- strtoi(V(G)[vi]$name)
} else if (type == "var") {
stopifnot(length(nbs) == 1L)
V(G)[vi]$value <- nbs$value
} else if (type == "not") {
stopifnot(length(nbs) == 1L)
V(G)[vi]$value <- bitwNot(nbs$value) %% 2 ^ 16
} else if (type == "and") {
stopifnot(length(nbs) == 2L)
V(G)[vi]$value <- bitwAnd(nbs[1L]$value, nbs[2L]$value) %% 2L ^ 16L
} else if (type == "or") {
stopifnot(length(nbs) == 2L)
V(G)[vi]$value <- bitwOr(nbs[1L]$value, nbs[2L]$value) %% 2L ^ 16L
} else if (type == "lshift") {
stopifnot(length(nbs) == 2L)
parts <- str_split(me, "_[^_]+_") %>%
extract2(1L)
nbs <- nbs[match(nbs$name, parts)]
V(G)[vi]$value <- bitwShiftL(nbs[1L]$value, nbs[2L]$value) %% 2L ^ 16L
} else if (type == "rshift") {
stopifnot(length(nbs) == 2L)
parts <- str_split(me, "_[^_]+_") %>%
extract2(1L)
nbs <- nbs[match(nbs$name, parts)]
V(G)[vi]$value <- bitwShiftR(nbs[1L]$value, nbs[2L]$value) %% 2L ^ 16L
}
}
G
}
G <- construct_graph(puzzle_data) %>%
calculate_wires()
(res <- V(G)["a"]$value)
## [1] 16076
2.2 Part 2
2.2.1 Description
— Part Two —
Now, take the signal you got on wire a, override wire b to that signal, and reset the other wires (including wire a). What new signal is ultimately provided to wire a?
2.2.2 Solution
We have to simply rename the incoming node to b to hold the result from part 1 and
recalculate the graph.
rewire_graph <- function(G, new_val) {
b_in <- neighbors(G, "b", "in")
V(G)[b_in]$name <- new_val
G
}
G2 <- rewire_graph(G, res) %>%
calculate_wires()
V(G2)["a"]$value
## [1] 2797