library(tidyverse)
Day 16
Advent of Code: Worked Solutions
Setup
Import libraries:
Read input from file:
<- read_lines(file = "../input/day16.txt") input
Part 1
Convert hex input to a binary string:
<- input |>
bin str_split_1("") |>
strtoi(base = 16L) |>
map_chr(
~ .x |>
intToBits() |>
as.integer() |>
rev() |>
tail(4) |>
str_flatten()
|>
) str_flatten()
Define a helper function to parse binary strings larger than 32 bits (which can’t be handled by the built-in strtoi
function):
<- function(str) {
strtodbl |>
str str_split_1("") |>
rev() |>
as.integer() |>
imap_dbl(\(x, idx) if_else(x == 1, 2^(idx - 1), 0)) |>
sum()
}
Define a function to convert a binary string into an integer following the designated decoding pattern, where values are grouped into sets of 5 bits:
<- function(bin) {
read_literal <- str_extract(bin, "^(1....)*(0....)")
str <- str_replace_all(str, "(.(....))", "\\2")
bin <- strtoi(bin, base = 2)
val
# strtoi can only handle 32 bit integers. Use helper func for larger cases
if (is.na(val)) {
<- strtodbl(bin)
val
}
lst(value = val, nbits = str_length(str))
}
Define a function to read the header info from a packet and delagate the rest of the packet to the correct function to parse:
<- function(bin) {
read_packet
# Return early if the end is an artifact of decimal representation
if (str_length(bin) < 4 | str_detect(bin, "^0+$"))
return(lst(ver = NULL, type = NULL, nbits = str_length(bin), value = NULL))
# Read in packet header/metadata
<- str_sub(bin, start = 1L, end = 3L) |> strtoi(base = 2)
ver <- str_sub(bin, start = 4L, end = 6L) |> strtoi(base = 2)
type <- str_sub(bin, start = 7L, end = -1L)
bin
# Choose the correct function to read in the packet based on type ID
<- get(case_match(type, 4 ~ "read_literal", .default = "read_subpackets"))
fn <- fn(bin)
output
lst(ver = ver, type = type, nbits = output$nbits + 6L, value = output$value)
}
Define a function to read and merge sub-packets from within an operator packet:
<- function(bin) {
read_subpackets
# Determine whether the length is in terms of bits or # of subpackets
<- str_sub(bin, end = 1L) |> as.integer()
type
# Pull length value as an integer and assign the max packet number if applcbl
<- case_match(type, 0 ~ 15L, 1 ~ 11L)
bits <- str_sub(bin, start = 2L, end = bits + 1L) |> strtoi(base = 2)
len <- case_match(type, 0 ~ Inf, 1 ~ len)
nmax
# Trim the binary str to begin after the header and end at designated length
<- str_sub(
bin
bin, start = bits + 2L,
end = case_match(type, 0 ~ bits + 1L + len, 1 ~ -1L)
)
# Initialize the loop
<- list()
packets <- bits + 1L
nbits
# Parse subpackets until the end of the binary value or max count is reached
while (str_length(bin) > 0 & length(packets) < nmax) {
<- read_packet(bin)
output <- nbits + output$nbits
nbits <- c(packets, list(output))
packets <- str_sub(bin, start = output$nbits + 1L)
bin
}
return(lst(nbits = nbits, value = packets))
}
Parse puzzle input into a set of nested packets of integers:
<- read_packet(bin) parsed
Sum the version numbers from each packet:
|>
parsed unlist() |>
keep_at(~ str_detect(.x, "ver")) |>
sum()
Part 2
Type IDs (other than 4) now represent operations. Map these IDs to their operation:
<- c("+", "*", "min", "max", "I", ">", "<", "==")
op_lst <- \(id) get(op_lst[id + 1]) id_to_op
Define a function that parses the nested packet structure and calculates the appropriate output:
<- function(packet) {
calc_packet if (packet$type == 4) return(packet$value)
$value |>
packetmap_dbl(calc_packet) |>
reduce(id_to_op(packet$type)) |>
as.numeric()
}
Calculate the output:
calc_packet(parsed)