1 Setup
1.1 Libraries
library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
library(pracma)
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)
}
res <- text_block[nzchar(text_block)] %>%
str_extract_all("-?\\d+") %>%
do.call(rbind, .) %>%
set_colnames(c("px", "py", "pz", "vx", "vy", "vz"))
storage.mode(res) <- "numeric"
res
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 24
2.1 Part 1
2.1.1 Description
— Day 24: Never Tell Me The Odds —
It seems like something is going wrong with the snow-making process. Instead of forming snow, the water that’s been absorbed into the air seems to be forming hail!
Maybe there’s something you can do to break up the hailstones?
Due to strong, probably-magical winds, the hailstones are all flying through the air in perfectly linear trajectories. You make a note of each hailstone’s position and velocity (your puzzle input). For example:
19, 13, 30 @ -2, 1, -2
18, 19, 22 @ -1, -1, -2
20, 25, 34 @ -2, -2, -4
12, 31, 28 @ -1, -2, -1
20, 19, 15 @ 1, -5, -3
Each line of text corresponds to the position and velocity of a single hailstone. The positions indicate where the hailstones are right now (at time 0). The velocities are constant and indicate exactly how far each hailstone will move in one nanosecond.
Each line of text uses the format px py pz @ vx vy vz. For instance, the hailstone specified by 20, 19, 15 @ 1, -5, -3 has initial X position 20, Y position 19, Z position 15, X velocity 1, Y velocity -5, and Z velocity -3. After one nanosecond, the hailstone would be at 21, 14, 12.
Perhaps you won’t have to do anything. How likely are the hailstones to collide with each other and smash into tiny ice crystals?
To estimate this, consider only the X and Y axes; ignore the Z axis. Looking forward in time, how many of the hailstones’ paths will intersect within a test area? (The hailstones themselves don’t have to collide, just test for intersections between the paths they will trace.)
In this example, look for intersections that happen with an X and Y position each at least 7 and at most 27; in your actual data, you’ll need to check a much larger test area. Comparing all pairs of hailstones’ future paths produces the following results:
Hailstone A: 19, 13, 30 @ -2, 1, -2
Hailstone B: 18, 19, 22 @ -1, -1, -2
Hailstones' paths will cross inside the test area (at x=14.333, y=15.333).
Hailstone A: 19, 13, 30 @ -2, 1, -2
Hailstone B: 20, 25, 34 @ -2, -2, -4
Hailstones' paths will cross inside the test area (at x=11.667, y=16.667).
Hailstone A: 19, 13, 30 @ -2, 1, -2
Hailstone B: 12, 31, 28 @ -1, -2, -1
Hailstones' paths will cross outside the test area (at x=6.2, y=19.4).
Hailstone A: 19, 13, 30 @ -2, 1, -2
Hailstone B: 20, 19, 15 @ 1, -5, -3
Hailstones' paths crossed in the past for hailstone A.
Hailstone A: 18, 19, 22 @ -1, -1, -2
Hailstone B: 20, 25, 34 @ -2, -2, -4
Hailstones' paths are parallel; they never intersect.
Hailstone A: 18, 19, 22 @ -1, -1, -2
Hailstone B: 12, 31, 28 @ -1, -2, -1
Hailstones' paths will cross outside the test area (at x=-6, y=-5).
Hailstone A: 18, 19, 22 @ -1, -1, -2
Hailstone B: 20, 19, 15 @ 1, -5, -3
Hailstones' paths crossed in the past for both hailstones.
Hailstone A: 20, 25, 34 @ -2, -2, -4
Hailstone B: 12, 31, 28 @ -1, -2, -1
Hailstones' paths will cross outside the test area (at x=-2, y=3).
Hailstone A: 20, 25, 34 @ -2, -2, -4
Hailstone B: 20, 19, 15 @ 1, -5, -3
Hailstones' paths crossed in the past for hailstone B.
Hailstone A: 12, 31, 28 @ -1, -2, -1
Hailstone B: 20, 19, 15 @ 1, -5, -3
Hailstones' paths crossed in the past for both hailstones.
So, in this example, 2 hailstones’ future paths cross inside the boundaries of the test area.
However, you’ll need to search a much larger test area if you want to see if any hailstones might collide. Look for intersections that happen with an X and Y position each at least 200000000000000 and at most 400000000000000. Disregard the Z axis entirely.
Considering only the X and Y axes, check all pairs of hailstones’ future paths for intersections. How many of these intersections occur within the test area?
2.1.2 Solution
The system defines a set of equations for each pair of hailstones. We can solve these equations to find the collision point and check if it is within the specified range.
get_collision_point <- function(v1, v2, p1, p2) {
M <- cbind(v1, -v2)
P <- matrix(p2 - p1, ncol = 1L)
if (kappa(M) < 1e12) {
cc <- solve(M, P)
if (all(cc >= 0)) {
return (matrix(p1 + cc[1L] * v1, ncol = 1L))
}
}
matrix(Inf, 2L, 1L)
}
count_colliding <- function(hailstorms, range) {
n <- nrow(hailstorms)
colliding <- matrix(FALSE, n, n)
for (i in seq_len(n - 1L)) {
for (j in seq(i + 1L, n)) {
p <- get_collision_point(hailstorms[i, 4:5], hailstorms[j, 4:5],
hailstorms[i, 1:2], hailstorms[j, 1:2])
if (all(p >= range[1L]) && all(p <= range[2L])) {
colliding[i, j] <- TRUE
}
}
}
sum(colliding)
}
count_colliding(puzzle_data, c(200000000000000, 400000000000000))
## [1] 12740
2.2 Part 2
2.2.1 Description
— Part Two —
Upon further analysis, it doesn’t seem like any hailstones will naturally collide. It’s up to you to fix that!
You find a rock on the ground nearby. While it seems extremely unlikely, if you throw it just right, you should be able to hit every hailstone in a single throw!
You can use the probably-magical winds to reach any integer position you like and to propel the rock at any integer velocity. Now including the Z axis in your calculations, if you throw the rock at time 0, where do you need to be so that the rock perfectly collides with every hailstone? Due to probably-magical inertia, the rock won’t slow down or change direction when it collides with a hailstone.
In the example above, you can achieve this by moving to position 24, 13, 10 and throwing the rock at velocity -3, 1, 2. If you do this, you will hit every hailstone as follows:
Hailstone: 19, 13, 30 @ -2, 1, -2
Collision time: 5
Collision position: 9, 18, 20
Hailstone: 18, 19, 22 @ -1, -1, -2
Collision time: 3
Collision position: 15, 16, 16
Hailstone: 20, 25, 34 @ -2, -2, -4
Collision time: 4
Collision position: 12, 17, 18
Hailstone: 12, 31, 28 @ -1, -2, -1
Collision time: 6
Collision position: 6, 19, 22
Hailstone: 20, 19, 15 @ 1, -5, -3
Collision time: 1
Collision position: 21, 14, 12
Above, each hailstone is identified by its initial position and its velocity. Then, the time and position of that hailstone’s collision with your rock are given.
After 1 nanosecond, the rock has exactly the same position as one of the hailstones, obliterating it into ice dust! Another hailstone is smashed to bits two nanoseconds after that. After a total of 6 nanoseconds, all of the hailstones have been destroyed.
So, at time 0, the rock needs to be at X position 24, Y position 13, and Z position 10. Adding these three coordinates together produces 47. (Don’t add any coordinates from the rock’s velocity.)
Determine the exact position and velocity the rock needs to have at time 0 so that it perfectly collides with every hailstone. What do you get if you add up the X, Y, and Z coordinates of that initial position?
2.2.2 Solution
Each hailstone moves along the line given by
\[H_i(t) = P_i +tV_i\] while the stone moves along the line given by
\[R(t) = R_0 + tV_0\] where \(R_0\) is the initial position of the rock and \(V_0\) is its velocity, both vectors must be \(R_0\in\mathbb{Z}^3\) and \(V_0\in\{-1,0,1\}^3\). For each hailstone we can derive the intersection point by \[ R_0 + t_iR_v = P_i +t_iV_i \Leftrightarrow R_0 - P_i = t_i(V_i - R_v) \] \(t_i\) is unknown, but we observe that \(R_0 - P_i\) must be parallel to \(V_i - R_v\). This can be expressed by using the cross product. The cross product of two parallel vectors is zero, so we have \[ (R_0 - P_i) \times (V_i - R_v) = 0 \] This equation can be rewritten as: \[ (R_0 - P_i) \times (V_i - R_v) = R_0 \times V_i - R_0 \times R_v - P_i \times V_i + P_i \times R_v \] which includes the non linear term \(R_0 \times R_v\). However, we can eliminate this term by looking at all (in fact some are already efficient) different hailstones. For two different hailstones \(i\) and \(j\) we have: \[ \begin{aligned} (R_0 - P_i) \times (V_i - R_v) & = 0 \\ (R_0 - P_j) \times (V_j - R_v) & = 0 \end{aligned} \]
We can subtract the second equation from the first one to get rid of the non linear term:
\[ \begin{aligned} R_0 \times V_i - P_i \times V_i + P_i \times R_v &= R_0 \times V_j - P_j \times V_j + P_j \times R_v\\ R_0 \times (V_i - V_j) + R_v \times (P_i - P_j) &= P_i \times V_i - P_j \times V_j \end{aligned} \] On the LHS we have now only linear terms in \(R_0\) and \(R_v\) and on the RHS we have only known values. We can write this as a linear system of equations and solve it to find \(R_0\) and \(R_v\). We need to do this for at least three different pairs of hailstones to get enough equations to solve for the six unknowns in \(R_0\) and \(R_v\).
We can rewrite the cross product as a matrix multiplication to get a more compact representation of the equations. The cross product of two vectors \(a\) and \(b\) can be written as: \[ a \times b = \begin{bmatrix} 0 & -a_z & a_y \\ a_z & 0 & -a_x \\ -a_y & a_x & 0 \end{bmatrix} \cdot b \] Now we have all the ingredients to solve the problem. We can create a matrix \(A\) that contains the coefficients of \(R_0\) and \(R_v\) and a vector \(b\) that contains the known values from the RHS of the equations. We can then solve the linear system \(Ax = b\) to find \(R_0\) and \(R_v\).
solve_rock <- function(M) {
P <- M[, 1:3, drop = FALSE]
V <- M[, 4:6, drop = FALSE]
cp <- function(v) {
matrix(c(
0, -v[3], v[2],
v[3], 0, -v[1],
-v[2], v[1], 0
), 3L, 3L, byrow = TRUE)
}
block_ij <- function(i, j) {
A1 <- V[i, ] - V[j, ]
A2 <- P[j, ] - P[i, ]
rhs <- cross(P[i, ], V[i, ]) - cross(P[j, ], V[j, ])
A <- cbind(cp(A1), cp(A2))
b <- -rhs
list(A = A, b = b)
}
A <- b <- NULL
## create equation system for all pairs of hailstones
all_pairs <- combn(nrow(P), 2L)
## chose the first 6 pairs to create the system of equations, this
## should be enough to solve for the 6 unknowns
sample_n <- min(ncol(all_pairs), 6L)
for (i in seq_len(sample_n)) {
blk <- block_ij(all_pairs[1L, i], all_pairs[2L, i])
A <- rbind(A, blk$A)
b <- c(b, blk$b)
}
## solve system
sol <- qr.solve(A, b)
R0 <- round(sol[1:3])
sum(R0)
}
solve_rock(puzzle_data) %>%
print(digit = 16L)
## [1] 741991571910536