2021-12-16 - Packet Decoder
(original .ipynb)
Day 16 puzzle input is a sequence of hex digits which represent a nested structure of packets (mine is here). Part 1 involves doing parsing and expanding the nested packets, retrieving the "version" and summing this for each packet. Part 2 involves fully parsing the packets, interpreting them as mathematical operators finding the value of the calculation the input represents.
puzzle_input_str = open("puzzle_input/day16.txt").read()
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]
def decode_input(input_str):
bit_chunks = []
for chunk in chunks(input_str, 2):
bit_string = bin(int(chunk, 16))[2:]
padding = "0" * (8 - len(bit_string))
bit_chunks.append(padding + bit_string)
return "".join(bit_chunks)
def parse_literal(packet_version, body):
bit_chunks = []
for chunk in chunks(body, 5):
keep_going = chunk[0]
chunk_bits = chunk[1:]
bit_chunks.append(chunk_bits)
if keep_going == "0":
break
literal = "".join(bit_chunks)
bits_read = len(literal) + len(literal)//4
return (packet_version, 4, int(literal, 2), []), body[bits_read:]
def parse_operator(packet_version, packet_type_id, body):
bit_mode_length = body[0] == "0"
length_bits = 15 if bit_mode_length else 11
length = int(body[1:1+length_bits], 2)
packets = []
body = body[1+length_bits:]
while length != 0:
packet, rest = parse_packet(body)
packet_length = len(body) - len(rest)
packets.append(packet)
body = rest
if bit_mode_length:
length -= packet_length
else:
length -= 1
return (packet_version, packet_type_id, None, packets), body
def parse_packet(packet):
packet_version = int(packet[ :3], 2)
packet_type_id = int(packet[3:6], 2)
packet_body = packet[6:]
if packet_type_id == 4:
return parse_literal(packet_version, packet_body)
else:
return parse_operator(packet_version, packet_type_id, packet_body)
def sum_versions(packet):
version, type_id, value, subpackets = packet
return version + sum(versions(p) for p in subpackets)
def part_one(input_str):
packet_bits = decode_input(input_str)
packet, _ = parse_packet(packet_bits)
return versions(packet)
assert 16 == part_one("8A004A801A8002F478")
assert 12 == part_one("620080001611562C8802118E34")
assert 23 == part_one("C0015000016115A2E0802F182340")
assert 31 == part_one("A0016C880162017C3686B18A3D4780")
print("part one:", part_one(puzzle_input_str))
part one: 920
from functools import reduce
op = [
lambda packets,v: sum(evaluate(p) for p in packets),
lambda packets,v: reduce(lambda a,b:a*b, (evaluate(p) for p in packets)),
lambda packets,v: min(evaluate(p) for p in packets),
lambda packets,v: max(evaluate(p) for p in packets),
lambda packets,v: v,
lambda packets,v: 1 if evaluate(packets[0]) > evaluate(packets[1]) else 0,
lambda packets,v: 1 if evaluate(packets[0]) < evaluate(packets[1]) else 0,
lambda packets,v: 1 if evaluate(packets[0]) == evaluate(packets[1]) else 0
]
def evaluate(packet):
version, type_id, value, subpackets = packet
return op[type_id](subpackets, value)
def part_two(input_str):
packet_bits = decode_input(input_str)
packet, _ = parse_packet(packet_bits)
return evaluate(packet)
assert 3 == part_two("C200B40A82")
assert 7 == part_two("880086C3E88112")
assert 9 == part_two("CE00C43D881120")
assert 1 == part_two("D8005AC2A8F0")
assert 0 == part_two("9C005AC2F8F0")
assert 1 == part_two("9C0141080250320F1802104A08")
print("part two:", part_two(puzzle_input_str))
part two: 10185143721112