From 6ee685c69567686344b8053a5f3f267351c8a36e Mon Sep 17 00:00:00 2001 From: daviddoji Date: Mon, 23 Oct 2023 18:05:02 +0200 Subject: [PATCH] Solution to problem 16 in Python --- src/Year_2018/P16.py | 219 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/Year_2018/P16.py diff --git a/src/Year_2018/P16.py b/src/Year_2018/P16.py new file mode 100644 index 0000000..507fc3a --- /dev/null +++ b/src/Year_2018/P16.py @@ -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()