Solution to problem 16 in Python

This commit is contained in:
David Doblas Jiménez 2023-10-23 18:05:02 +02:00
parent ad4181cda0
commit 6ee685c695
1 changed files with 219 additions and 0 deletions

219
src/Year_2018/P16.py Normal file
View File

@ -0,0 +1,219 @@
# --- Day 16: Chronal Classification ---
# As you see the Elves defend their hot chocolate successfully, you go back to
# falling through time. This is going to become a problem.
# If you're ever going to return to your own time, you need to understand how
# this device on your wrist works. You have a little while before you reach your
# next destination, and with a bit of trial and error, you manage to pull up a
# programming manual on the device's tiny screen.
# According to the manual, the device has four registers (numbered 0 through 3)
# that can be manipulated by instructions containing one of 16 opcodes. The
# registers start with the value 0.
# Every instruction consists of four values: an opcode, two inputs (named A and
# B), and an output (named C), in that order. The opcode specifies the behavior
# of the instruction and how the inputs are interpreted. The output, C, is
# always treated as a register.
# In the opcode descriptions below, if something says "value A", it means to
# take the number given as A literally. (This is also called an "immediate"
# value.) If something says "register A", it means to use the number given as A
# to read from (or write to) the register with that number. So, if the opcode
# addi adds register A and value B, storing the result in register C, and the
# instruction addi 0 7 3 is encountered, it would add 7 to the value contained
# by register 0 and store the sum in register 3, never modifying registers 0, 1,
# or 2 in the process.
# Many opcodes are similar except for how they interpret their arguments. The
# opcodes fall into seven general categories:
# Addition:
# addr (add register) stores into register C the result of adding register A
# and register B.
# addi (add immediate) stores into register C the result of adding register
# A and value B.
# Multiplication:
# mulr (multiply register) stores into register C the result of multiplying
# register A and register B.
# muli (multiply immediate) stores into register C the result of multiplying
# register A and value B.
# Bitwise AND:
# banr (bitwise AND register) stores into register C the result of the
# bitwise AND of register A and register B.
# bani (bitwise AND immediate) stores into register C the result of the
# bitwise AND of register A and value B.
# Bitwise OR:
# borr (bitwise OR register) stores into register C the result of the
# bitwise OR of register A and register B.
# bori (bitwise OR immediate) stores into register C the result of the
# bitwise OR of register A and value B.
# Assignment:
# setr (set register) copies the contents of register A into register C.
# (Input B is ignored.)
# seti (set immediate) stores value A into register C. (Input B is ignored.)
# Greater-than testing:
# gtir (greater-than immediate/register) sets register C to 1 if value A is
# greater than register B. Otherwise, register C is set to 0.
# gtri (greater-than register/immediate) sets register C to 1 if register A
# is greater than value B. Otherwise, register C is set to 0.
# gtrr (greater-than register/register) sets register C to 1 if register A
# is greater than register B. Otherwise, register C is set to 0.
# Equality testing:
# eqir (equal immediate/register) sets register C to 1 if value A is equal
# to register B. Otherwise, register C is set to 0.
# eqri (equal register/immediate) sets register C to 1 if register A is
# equal to value B. Otherwise, register C is set to 0.
# eqrr (equal register/register) sets register C to 1 if register A is equal
# to register B. Otherwise, register C is set to 0.
# Unfortunately, while the manual gives the name of each opcode, it doesn't seem
# to indicate the number. However, you can monitor the CPU to see the contents
# of the registers before and after instructions are executed to try to work
# them out. Each opcode has a number from 0 through 15, but the manual doesn't
# say which is which. For example, suppose you capture the following sample:
# Before: [3, 2, 1, 1]
# 9 2 1 2
# After: [3, 2, 2, 1]
# This sample shows the effect of the instruction 9 2 1 2 on the registers.
# Before the instruction is executed, register 0 has value 3, register 1 has
# value 2, and registers 2 and 3 have value 1. After the instruction is
# executed, register 2's value becomes 2.
# The instruction itself, 9 2 1 2, means that opcode 9 was executed with A=2,
# B=1, and C=2. Opcode 9 could be any of the 16 opcodes listed above, but only
# three of them behave in a way that would cause the result shown in the sample:
# Opcode 9 could be mulr: register 2 (which has a value of 1) times register
# 1 (which has a value of 2) produces 2, which matches the value stored in the
# output register, register 2.
# Opcode 9 could be addi: register 2 (which has a value of 1) plus value 1
# produces 2, which matches the value stored in the output register, register 2.
# Opcode 9 could be seti: value 2 matches the value stored in the output
# register, register 2; the number given for B is irrelevant.
# None of the other opcodes produce the result captured in the sample. Because
# of this, the sample above behaves like three opcodes.
# You collect many of these samples (the first section of your puzzle input).
# The manual also includes a small test program (the second section of your
# puzzle input) - you can ignore it for now.
# Ignoring the opcode numbers, how many samples in your puzzle input behave like
# three or more opcodes?
import re
from collections import defaultdict
with open("files/P16.txt") as f:
instructions = list(
map(int, re.findall(r"\d+", f.read().split("\n\n\n")[0]))
)
operators = {
"addr": lambda regs, a, b: regs[a] + regs[b],
"addi": lambda regs, a, b: regs[a] + b,
"mulr": lambda regs, a, b: regs[a] * regs[b],
"muli": lambda regs, a, b: regs[a] * b,
"banr": lambda regs, a, b: regs[a] & regs[b],
"bani": lambda regs, a, b: regs[a] & b,
"borr": lambda regs, a, b: regs[a] | regs[b],
"bori": lambda regs, a, b: regs[a] | b,
"setr": lambda regs, a, b: regs[a],
"seti": lambda regs, a, b: a,
"gtir": lambda regs, a, b: 1 if a > regs[b] else 0,
"gtri": lambda regs, a, b: 1 if regs[a] > b else 0,
"gtrr": lambda regs, a, b: 1 if regs[a] > regs[b] else 0,
"eqir": lambda regs, a, b: 1 if a == regs[b] else 0,
"eqri": lambda regs, a, b: 1 if regs[a] == b else 0,
"eqrr": lambda regs, a, b: 1 if regs[a] == regs[b] else 0,
}
def part1():
sample_count = 0
possible_opnames = defaultdict(lambda: set(operators.keys()))
for i in range(0, len(instructions), 12):
before = instructions[i : i + 4]
opcode, A, B, C = instructions[i + 4 : i + 8]
after = instructions[i + 8 : i + 12]
count = 0
for op in operators:
result = operators[op](before, A, B)
if [*before[:C], result, *before[C + 1 :]] == after:
count += 1
elif op in possible_opnames[opcode]:
possible_opnames[opcode].remove(op)
if count >= 3:
sample_count += 1
print(f"There are {sample_count} samples")
# # --- Part Two ---
# # Using the samples you collected, work out the number of each opcode and
# # execute the test program (the second section of your puzzle input).
# # What value is contained in register 0 after executing the test program?
def part2():
possible_opnames = defaultdict(lambda: set(operators.keys()))
for i in range(0, len(instructions), 12):
before = instructions[i : i + 4]
opcode, A, B, C = instructions[i + 4 : i + 8]
after = instructions[i + 8 : i + 12]
count = 0
for op in operators:
result = operators[op](before, A, B)
if [*before[:C], result, *before[C + 1 :]] == after:
count += 1
elif op in possible_opnames[opcode]:
possible_opnames[opcode].remove(op)
program = {}
while any(possible_opnames.values()):
for idx, op_codes in possible_opnames.items():
if len(op_codes) == 1:
program[idx] = op = op_codes.pop()
for remaining in possible_opnames.values():
remaining.discard(op)
registers = [0] * 4
with open("files/P16.txt") as f:
lines = [f.read().split("\n\n\n")[1]][0].split("\n")
lines = [line for line in lines if line]
for line in lines:
opcode, A, B, C = [int(number) for number in line.split()]
registers[C] = operators[program[opcode]](registers, A, B)
print(f"Register[0] is {registers[0]} after execution")
if __name__ == "__main__":
part1()
part2()