Solution to problem 16 in Python
This commit is contained in:
parent
ad4181cda0
commit
6ee685c695
219
src/Year_2018/P16.py
Normal file
219
src/Year_2018/P16.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user