Solution to problem 16 in Python
This commit is contained in:
parent
7cd34d954f
commit
5cf3d28cdc
302
src/Year_2021/P16.py
Normal file
302
src/Year_2021/P16.py
Normal file
@ -0,0 +1,302 @@
|
||||
# --- Day 16: Packet Decoder ---
|
||||
|
||||
# As you leave the cave and reach open waters, you receive a transmission from
|
||||
# the Elves back on the ship.
|
||||
|
||||
# The transmission was sent using the Buoyancy Interchange Transmission System
|
||||
# (BITS), a method of packing numeric expressions into a binary sequence. Your
|
||||
# submarine's computer has saved the transmission in hexadecimal (your puzzle
|
||||
# input).
|
||||
|
||||
# The first step of decoding the message is to convert the hexadecimal
|
||||
# representation into binary. Each character of hexadecimal corresponds to four
|
||||
# bits of binary data:
|
||||
|
||||
# 0 = 0000
|
||||
# 1 = 0001
|
||||
# 2 = 0010
|
||||
# 3 = 0011
|
||||
# 4 = 0100
|
||||
# 5 = 0101
|
||||
# 6 = 0110
|
||||
# 7 = 0111
|
||||
# 8 = 1000
|
||||
# 9 = 1001
|
||||
# A = 1010
|
||||
# B = 1011
|
||||
# C = 1100
|
||||
# D = 1101
|
||||
# E = 1110
|
||||
# F = 1111
|
||||
|
||||
# The BITS transmission contains a single packet at its outermost layer which
|
||||
# itself contains many other packets. The hexadecimal representation of this
|
||||
# packet might encode a few extra 0 bits at the end; these are not part of the
|
||||
# transmission and should be ignored.
|
||||
|
||||
# Every packet begins with a standard header: the first three bits encode the
|
||||
# packet version, and the next three bits encode the packet type ID. These two
|
||||
# values are numbers; all numbers encoded in any packet are represented as
|
||||
# binary with the most significant bit first. For example, a version encoded as
|
||||
# the binary sequence 100 represents the number 4.
|
||||
|
||||
# Packets with type ID 4 represent a literal value. Literal value packets encode
|
||||
# a single binary number. To do this, the binary number is padded with leading
|
||||
# zeroes until its length is a multiple of four bits, and then it is broken into
|
||||
# groups of four bits. Each group is prefixed by a 1 bit except the last group,
|
||||
# which is prefixed by a 0 bit. These groups of five bits immediately follow the
|
||||
# packet header. For example, the hexadecimal string D2FE28 becomes:
|
||||
|
||||
# 110100101111111000101000
|
||||
# VVVTTTAAAAABBBBBCCCCC
|
||||
|
||||
# Below each bit is a label indicating its purpose:
|
||||
|
||||
# The three bits labeled V (110) are the packet version, 6.
|
||||
# The three bits labeled T (100) are the packet type ID, 4, which means the
|
||||
# packet is a literal value.
|
||||
# The five bits labeled A (10111) start with a 1 (not the last group, keep
|
||||
# reading) and contain the first four bits of the number, 0111.
|
||||
# The five bits labeled B (11110) start with a 1 (not the last group, keep
|
||||
# reading) and contain four more bits of the number, 1110.
|
||||
# The five bits labeled C (00101) start with a 0 (last group, end of packet)
|
||||
# and contain the last four bits of the number, 0101.
|
||||
# The three unlabeled 0 bits at the end are extra due to the hexadecimal
|
||||
# representation and should be ignored.
|
||||
|
||||
# So, this packet represents a literal value with binary representation
|
||||
# 011111100101, which is 2021 in decimal.
|
||||
|
||||
# Every other type of packet (any packet with a type ID other than 4) represent
|
||||
# an operator that performs some calculation on one or more sub-packets
|
||||
# contained within. Right now, the specific operations aren't important; focus
|
||||
# on parsing the hierarchy of sub-packets.
|
||||
|
||||
# An operator packet contains one or more packets. To indicate which subsequent
|
||||
# binary data represents its sub-packets, an operator packet can use one of two
|
||||
# modes indicated by the bit immediately after the packet header; this is called
|
||||
# the length type ID:
|
||||
|
||||
# If the length type ID is 0, then the next 15 bits are a number that
|
||||
# represents the total length in bits of the sub-packets contained by this
|
||||
# packet.
|
||||
# If the length type ID is 1, then the next 11 bits are a number that
|
||||
# represents the number of sub-packets immediately contained by this packet.
|
||||
|
||||
# Finally, after the length type ID bit and the 15-bit or 11-bit field, the
|
||||
# sub-packets appear.
|
||||
|
||||
# For example, here is an operator packet (hexadecimal string 38006F45291200)
|
||||
# with length type ID 0 that contains two sub-packets:
|
||||
|
||||
# 00111000000000000110111101000101001010010001001000000000
|
||||
# VVVTTTILLLLLLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBBBBBB
|
||||
|
||||
# The three bits labeled V (001) are the packet version, 1.
|
||||
# The three bits labeled T (110) are the packet type ID, 6, which means the
|
||||
# packet is an operator.
|
||||
# The bit labeled I (0) is the length type ID, which indicates that the
|
||||
# length is a 15-bit number representing the number of bits in the sub-packets.
|
||||
# The 15 bits labeled L (000000000011011) contain the length of the
|
||||
# sub-packets in bits, 27.
|
||||
# The 11 bits labeled A contain the first sub-packet, a literal value
|
||||
# representing the number 10.
|
||||
# The 16 bits labeled B contain the second sub-packet, a literal value
|
||||
# representing the number 20.
|
||||
|
||||
# After reading 11 and 16 bits of sub-packet data, the total length indicated in
|
||||
# L (27) is reached, and so parsing of this packet stops.
|
||||
|
||||
# As another example, here is an operator packet (hexadecimal string
|
||||
# EE00D40C823060) with length type ID 1 that contains three sub-packets:
|
||||
|
||||
# 11101110000000001101010000001100100000100011000001100000
|
||||
# VVVTTTILLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCC
|
||||
|
||||
# The three bits labeled V (111) are the packet version, 7.
|
||||
# The three bits labeled T (011) are the packet type ID, 3, which means the
|
||||
# packet is an operator.
|
||||
# The bit labeled I (1) is the length type ID, which indicates that the
|
||||
# length is a 11-bit number representing the number of sub-packets.
|
||||
# The 11 bits labeled L (00000000011) contain the number of sub-packets, 3.
|
||||
# The 11 bits labeled A contain the first sub-packet, a literal value
|
||||
# representing the number 1.
|
||||
# The 11 bits labeled B contain the second sub-packet, a literal value
|
||||
# representing the number 2.
|
||||
# The 11 bits labeled C contain the third sub-packet, a literal value
|
||||
# representing the number 3.
|
||||
|
||||
# After reading 3 complete sub-packets, the number of sub-packets indicated in L
|
||||
# (3) is reached, and so parsing of this packet stops.
|
||||
|
||||
# For now, parse the hierarchy of the packets throughout the transmission and
|
||||
# add up all of the version numbers.
|
||||
|
||||
# Here are a few more examples of hexadecimal-encoded transmissions:
|
||||
|
||||
# 8A004A801A8002F478 represents an operator packet (version 4) which
|
||||
# contains an operator packet (version 1) which contains an operator packet
|
||||
# (version 5) which contains a literal value (version 6); this packet has a
|
||||
# version sum of 16.
|
||||
# 620080001611562C8802118E34 represents an operator packet (version 3) which
|
||||
# contains two sub-packets; each sub-packet is an operator packet that contains
|
||||
# two literal values. This packet has a version sum of 12.
|
||||
# C0015000016115A2E0802F182340 has the same structure as the previous
|
||||
# example, but the outermost packet uses a different length type ID. This packet
|
||||
# has a version sum of 23.
|
||||
# A0016C880162017C3686B18A3D4780 is an operator packet that contains an
|
||||
# operator packet that contains an operator packet that contains five literal
|
||||
# values; it has a version sum of 31.
|
||||
|
||||
# Decode the structure of your hexadecimal-encoded BITS transmission; what do
|
||||
# you get if you add up the version numbers in all packets?
|
||||
|
||||
with open("files/P16.txt") as f:
|
||||
data = "".join(f"{int(char, 16):04b}" for char in f.read().strip())
|
||||
|
||||
|
||||
def parse(number):
|
||||
idx = 0
|
||||
res = []
|
||||
while True:
|
||||
res.append(number[idx + 1 : idx + 5])
|
||||
if number[idx] == "0":
|
||||
break
|
||||
idx += 5
|
||||
return 5 * len(res)
|
||||
|
||||
|
||||
def part1(packet):
|
||||
version = int(packet[:3], base=2)
|
||||
type_id = int(packet[3:6], base=2)
|
||||
|
||||
idx = 6
|
||||
if type_id == 4:
|
||||
p = parse(packet[idx:])
|
||||
return version, idx + p
|
||||
|
||||
res = version
|
||||
if packet[idx] == "0":
|
||||
len_subpackets = int(packet[idx + 1 : idx + 16], base=2)
|
||||
idx += 16
|
||||
parse_until = idx + len_subpackets
|
||||
while idx < parse_until:
|
||||
v, i = part1(packet[idx:])
|
||||
idx += i
|
||||
res += v
|
||||
else:
|
||||
nsubpackets = int(packet[idx + 1 : idx + 12], base=2)
|
||||
idx += 12
|
||||
for _ in range(nsubpackets):
|
||||
v, i = part1(packet[idx:])
|
||||
idx += i
|
||||
res += v
|
||||
|
||||
return res, idx
|
||||
|
||||
|
||||
# --- Part Two ---
|
||||
|
||||
# Now that you have the structure of your transmission decoded, you can
|
||||
# calculate the value of the expression it represents.
|
||||
|
||||
# Literal values (type ID 4) represent a single number as described above. The
|
||||
# remaining type IDs are more interesting:
|
||||
|
||||
# Packets with type ID 0 are sum packets - their value is the sum of the
|
||||
# values of their sub-packets. If they only have a single sub-packet, their
|
||||
# value is the value of the sub-packet.
|
||||
# Packets with type ID 1 are product packets - their value is the result of
|
||||
# multiplying together the values of their sub-packets. If they only have a
|
||||
# single sub-packet, their value is the value of the sub-packet.
|
||||
# Packets with type ID 2 are minimum packets - their value is the minimum of
|
||||
# the values of their sub-packets.
|
||||
# Packets with type ID 3 are maximum packets - their value is the maximum of
|
||||
# the values of their sub-packets.
|
||||
# Packets with type ID 5 are greater than packets - their value is 1 if the
|
||||
# value of the first sub-packet is greater than the value of the second
|
||||
# sub-packet; otherwise, their value is 0. These packets always have exactly two
|
||||
# sub-packets.
|
||||
# Packets with type ID 6 are less than packets - their value is 1 if the
|
||||
# value of the first sub-packet is less than the value of the second sub-packet;
|
||||
# otherwise, their value is 0. These packets always have exactly two sub-packets.
|
||||
# Packets with type ID 7 are equal to packets - their value is 1 if the
|
||||
# value of the first sub-packet is equal to the value of the second sub-packet;
|
||||
# otherwise, their value is 0. These packets always have exactly two sub-packets.
|
||||
|
||||
# Using these rules, you can now work out the value of the outermost packet in
|
||||
# your BITS transmission.
|
||||
|
||||
# For example:
|
||||
|
||||
# C200B40A82 finds the sum of 1 and 2, resulting in the value 3.
|
||||
# 04005AC33890 finds the product of 6 and 9, resulting in the value 54.
|
||||
# 880086C3E88112 finds the minimum of 7, 8, and 9, resulting in the value 7.
|
||||
# CE00C43D881120 finds the maximum of 7, 8, and 9, resulting in the value 9.
|
||||
# D8005AC2A8F0 produces 1, because 5 is less than 15.
|
||||
# F600BC2D8F produces 0, because 5 is not greater than 15.
|
||||
# 9C005AC2F8F0 produces 0, because 5 is not equal to 15.
|
||||
# 9C0141080250320F1802104A08 produces 1, because 1 + 3 = 2 * 2.
|
||||
|
||||
# What do you get if you evaluate the expression represented by your
|
||||
# hexadecimal-encoded BITS transmission?
|
||||
|
||||
from math import prod
|
||||
|
||||
ops = {
|
||||
0: sum,
|
||||
1: prod,
|
||||
2: min,
|
||||
3: max,
|
||||
5: lambda x: int(x[0] > x[1]),
|
||||
6: lambda x: int(x[0] < x[1]),
|
||||
7: lambda x: int(x[0] == x[1]),
|
||||
}
|
||||
|
||||
|
||||
def parse_pt2(number):
|
||||
idx = 0
|
||||
res = []
|
||||
while True:
|
||||
res.append(number[idx + 1 : idx + 5])
|
||||
if number[idx] == "0":
|
||||
break
|
||||
idx += 5
|
||||
return 5 * len(res), int("".join(res), base=2)
|
||||
|
||||
|
||||
def part2(packet):
|
||||
version = int(packet[:3], base=2)
|
||||
type_id = int(packet[3:6], base=2)
|
||||
|
||||
idx = 6
|
||||
if type_id == 4:
|
||||
p, n = parse_pt2(packet[idx:])
|
||||
return version, idx + p, n
|
||||
|
||||
res = version
|
||||
packets = []
|
||||
if packet[idx] == "0":
|
||||
len_subpackets = int(packet[idx + 1 : idx + 16], base=2)
|
||||
idx += 16
|
||||
parse_until = idx + len_subpackets
|
||||
while idx < parse_until:
|
||||
v, i, n = part2(packet[idx:])
|
||||
idx += i
|
||||
res += v
|
||||
packets.append(n)
|
||||
else:
|
||||
nsubpackets = int(packet[idx + 1 : idx + 12], base=2)
|
||||
idx += 12
|
||||
for _ in range(nsubpackets):
|
||||
v, i, n = part2(packet[idx:])
|
||||
idx += i
|
||||
res += v
|
||||
packets.append(n)
|
||||
|
||||
return res, idx, ops[type_id](packets)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(part1(data)[0])
|
||||
print(part2(data)[2])
|
Loading…
Reference in New Issue
Block a user