By Eric Burden | December 13, 2020
In the spirit of the holidays (and programming), I’ll be posting my solutions to the Advent of Code 2020 puzzles here, at least one day after they’re posted (no spoilers!). I’ll be implementing the solutions in R because, well, that’s what I like! What I won’t be doing is posting any of the actual answers, just the reasoning behind them.
Also, as a general convention, whenever the puzzle has downloadable input, I’m
saving it in a file named input.txt
.
Day 12 - Rain Risk
Find the problem description HERE.
Part One - Do a Barrel Roll
For part one, we’re directing our ferry to take evasive maneuvers by reading in a set of text instructions and ‘moving’ the ship as instructed. The solution is the total linear distance we’ve moved the ship from the starting point (the Manhattan distance).
test_input <- c("F10", "N3", "F7", "R90", "F11")
real_input <- readLines('input.txt')
# Helper function, given a list of strings in the format 'CN' where 'C' is an
# uppercase character (one of 'N', 'S', 'E', 'W', 'L', 'R', 'F') and 'N' is
# a numeric character or characters. Returns a list where each element is a
# named list of the format list(list(dir = 'C', mag = 'N'), ...)
parse_input <- function(input) {
navs <- strsplit(input, '(?<=[A-Z])(?=\\d)', perl = T)
lapply(navs, function(x) { list(dir = x[1], mag = as.numeric(x[2])) })
}
# Helper function, given a `dir` of 'L' or 'R', a magnitude `mag`, and the
# ship's current position in the structure we have defined. Returns the
# ship's new heading as 'N', 'E', 'S', or 'W' based on the indicated
# rotation of the ship
new_heading <- function(dir, mag, ship_pos) {
dirs <- c('N', 'E', 'S', 'W')
rotation <- if (dir == 'L') { -1 } else { 1 } # Direction of rotation
di <- rotation * ((mag/90) %% 4) # Times to move 90 degrees
# Changes the heading, wrapping around the ends of the `dirs` vector as needed
heading <- which(dirs == ship_pos[['H']]) + di
if (heading < 1) { heading <- heading + 4 }
if (heading > 4) { heading <- heading - 4 }
dirs[[heading]]
}
# Main function, moves the ship. Given a `dir` and `mag` from a navigation
# instruction and the ship's current position as `ship_pos`, returns the ship's
# new position after applying the instruction
move_ship <- function(dir, mag, ship_pos) {
# If `dir` is one of 'N', 'S', 'E', or 'W', change the ship's position that
# direction
if (dir == 'N') { ship_pos$Y <- ship_pos$Y + mag}
if (dir == 'S') { ship_pos$Y <- ship_pos$Y - mag}
if (dir == 'E') { ship_pos$X <- ship_pos$X + mag}
if (dir == 'W') { ship_pos$X <- ship_pos$X - mag}
# If the `dir` is 'L' or 'R', rotate the ship
if (dir %in% c('L', 'R')) { ship_pos[['H']] <- new_heading(dir, mag, ship_pos) }
# Move the ship 'forward' in it's current direction
if (dir == 'F') { ship_pos <- move_ship(ship_pos$H, mag, ship_pos) }
ship_pos
}
# A handy structure to store the ship's position
ship_pos <- list(X = 0, Y = 0, H = 'E')
navigation <- parse_input(real_input) # Parse input
for (nav in navigation) { ship_pos <- move_ship(nav$dir, nav$mag, ship_pos) }
answer1 <- abs(ship_pos$X) + abs(ship_pos$Y)
As far as puzzles go, this one is pretty straightforward. Parse the directions, follow the directions. The most complex bit is rotating directions, but since the rotational degrees are always in increments of ‘90’, it’s not too bad. Keeping track of the ship’s location in terms of ‘X’ and ‘Y’ really helps with calculating the final result.
Part Two - Read the Directions
So, it turns out that, much like an overconfident father on Christmas morning, we decided to ignore the instructions, forge ahead, realize we did it wrong, then slink back to the instructions humbled and just a bit wiser.
# Helper function, given a number of times to move the ship `n`, the ship's
# position `ship`, and a waypoint's position `waypoint`, 'move' the ship to
# the waypoint `n` times.
ship_to_waypoint <- function(n, ship, waypoint) {
for (i in seq(n)) {
ship$X <- ship$X + waypoint$X
ship$Y <- ship$Y + waypoint$Y
}
ship
}
# Helper function, given a direction to rotate `dir`, an amount to rotate
# `mag`, and a waypoint's position `waypoint`, rotate the waypoint's position
# around the ship.
rotate <- function(dir, mag, waypoint) {
rotation_dir <- if (dir == 'L') { -1 } else { 1 }
rotation_mag <- mag %% 360 # Wraps around if mag > 360
# If `mag` is 0 or a multiple of 360, just return the waypoint
if (rotation_mag == 0) { return(waypoint) }
# Otherwise, modify the waypoint's X and Y coordinates to simulate rotation
if (rotation_mag == 90) {
new_X <- waypoint$Y * rotation_dir
new_Y <- waypoint$X * -rotation_dir
}
if (rotation_mag == 180) {
new_X <- -waypoint$X
new_Y <- -waypoint$Y
}
if (rotation_mag == 270) {
new_X <- waypoint$Y * -rotation_dir
new_Y <- waypoint$X * rotation_dir
}
# Set the waypoint's X and Y coordinates and return the waypoint
waypoint$X <- new_X
waypoint$Y <- new_Y
waypoint
}
# Modified main function, moves the ship. Given a `dir` and `mag` from a
# navigation instruction and the set of objects we're tracking `system`,
# returns an updated `system` that reflects movements in the ship and
# waypoint
move <- function(dir, mag, system) {
within(system, {
if (dir == 'N') { waypoint$Y <- waypoint$Y + mag}
if (dir == 'S') { waypoint$Y <- waypoint$Y - mag}
if (dir == 'E') { waypoint$X <- waypoint$X + mag}
if (dir == 'W') { waypoint$X <- waypoint$X - mag}
if (dir == 'F') { ship <- ship_to_waypoint(mag, ship, waypoint)}
if (dir %in% c('L', 'R')) { waypoint <- rotate(dir, mag, waypoint)}
})
}
# Keep track of the waypoint and ship locations in X and Y coordinates as
# a group, `system`
system <- list(waypoint = list(X = 10, Y = 1), ship = list(X = 0, Y = 0))
navigation <- parse_input(real_input)
for (nav in navigation) { system <- move(nav$dir, nav$mag, system) }
answer2 <- abs(system$ship$X) + abs(system$ship$Y)
Now we’re keeping track of a waypoint’s position in addition to the position of
the ship, and we’ve modified the rotate()
and move()
functions to
accommodate the changes, as well as a ship_to_waypoint()
function that
increment’s the ship’s ‘X’ and ‘Y’ coordinates by the difference between the
ship’s position and the waypoint’s position.
Wrap-Up
This was a relatively relaxing day. A straightforward strategy of creating a function for each individual operation then composing them together helped with creating a pretty readable solution. Probably the biggest breakthrough was realizing that ‘rotating’ the waypoint just meant transposing the ‘X’ and ‘Y’ coordinates.
If you found a different solution (or spotted a mistake in one of mine), please drop me a line!