diff --git a/2019/day7/day7.py b/2019/day7/day7.py new file mode 100644 index 0000000..b8d6e58 --- /dev/null +++ b/2019/day7/day7.py @@ -0,0 +1,53 @@ +import sys +from pathlib import Path + +# TODO replace PYTHONPATH hack with a proper solution, like making intcode an +# installed module https://stackoverflow.com/a/50194143 +sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode")) + +import itertools +from intcode import interpret_intcode + + +def main(inp): + mem = list(map(int, inp.readline().rstrip().split(","))) + max_ret = 0 + for seq in itertools.permutations([0, 1, 2, 3, 4], 5): + ret = amplifiers(mem, list(seq)) + #print(ret) + max_ret = max(ret[0], max_ret) + print(max_ret) + + +def amplifiers(program, sequence): + ret = interpret_intcode(program[::], [sequence.pop(0), 0]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.pop()]) + return ret + +def tests(): + program = [3, 15, 3, 16, 1002, 16, 10, 16, 1, 16, 15, 15, 4, 15, 99, 0, 0] + sequence = [4, 3, 2, 1, 0] + res = amplifiers(program, sequence) + assert res == [43210] + + program = [3,23,3,24,1002,24,10,24,1002,23,-1,23, 101,5,23,23,1,24,23,23,4,23,99,0,0] + sequence = [0,1,2,3,4] + res = amplifiers(program, sequence) + assert res == [54321] + + + program = [3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33, 1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0] + sequence = [1,0,4,3,2] + res = amplifiers(program, sequence) + assert res == [65210] + + print("All tests passed") + +if __name__ == "__main__": + import fileinput + main(fileinput.input()) + tests() + diff --git a/2019/intcode/intcode.py b/2019/intcode/intcode.py new file mode 100644 index 0000000..5c1655c --- /dev/null +++ b/2019/intcode/intcode.py @@ -0,0 +1,132 @@ +#! /usr/bin/env python3 +from collections import namedtuple +from enum import Enum +import logging + +logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) + + +def get_nth_digit(n, number): + "Returns the nth digit of the input number" + return number // 10 ** n % 10 + + +class Operation(Enum): + ADDITION = 1 + MULTIPLICATION = 2 + INPUT = 3 + OUTPUT = 4 + JMP_IF_TRUE = 5 + JMP_IF_FALSE = 6 + LESS_THAN = 7 + EQUALS = 8 + TERMINATION = 99 + + +class Mode(Enum): + POSITION = 0 + IMMEDIATE = 1 + + +class Instruction: + def __init__(self, opcode): + self.opcode = opcode + # A B C D E + # 0 1 1 0 3 + # A B C modes, DE opcode + self.operation = Operation(opcode % 100) + self.modes = [Mode(get_nth_digit(n, opcode)) for n in range(2, 5)] + self.handler_name = f"handle_{self.operation.name.lower()}" + self.handler = getattr(self, self.handler_name, self.handle_termination) + self.input = None + self.output = None + + def __repr__(self): + return f"[{self.opcode}] Instruction({self.operation}, {self.modes})" + + def handle(self, program, ip): + return self.handler(program, ip) + + def handle_addition(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"ADD {first} {second}") + result = first + second + # the last mode should *always* be POSITION + program[program[ip + 3]] = result + ip += 4 + return ip + + def handle_multiplication(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"MUL {first} {second}") + result = first * second + # the last mode should *always* be POSITION + program[program[ip + 3]] = result + ip += 4 + return ip + + def handle_input(self, program, ip): + program[program[ip + 1]] = self.input + ip += 2 + return ip + + def handle_output(self, program, ip): + self.output = self._get_param(program, ip) + logging.debug(f"OUT {self.output}") + ip += 2 + return ip + + def handle_jmp_if_true(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"JMPT {first} {second}") + return second if first != 0 else ip + 3 + + def handle_jmp_if_false(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"JMPF {first} {second}") + return second if first == 0 else ip + 3 + + def handle_less_than(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"LT {first} {second}") + program[program[ip + 3]] = int(first < second) + ip += 4 + return ip + + def handle_equals(self, program, ip): + first, second = self._get_parameters(program, ip) + logging.debug(f"EQ {first} {second}") + program[program[ip + 3]] = int(first == second) + ip += 4 + return ip + + def handle_termination(self, program, ip): + logging.debug("HALT") + return ip + + def _get_param(self, program, ip, i=0): + return ( + program[ip + i + 1] + if self.modes[i] is Mode.IMMEDIATE + else program[program[ip + i + 1]] + ) + + def _get_parameters(self, program, ip): + first = self._get_param(program, ip, 0) + second = self._get_param(program, ip, 1) + return first, second + + +def interpret_intcode(program, stdin=[]): + ip = 0 + out = [] + while program[ip] != 99: + opcode = program[ip] + instruction = Instruction(opcode) + if instruction.operation == Operation.INPUT: + instruction.input=stdin.pop(0) + ip = instruction.handle(program, ip) + if instruction.output is not None: + out.append(instruction.output) + return out + diff --git a/2019/intcode/tests.py b/2019/intcode/tests.py new file mode 100644 index 0000000..d12ff07 --- /dev/null +++ b/2019/intcode/tests.py @@ -0,0 +1,83 @@ +# Run with pytest +import logging +from intcode import * + + +def run_test(program, expected_mem=None, stdin=[], expected_out=None): + mem = program[::] + out = interpret_intcode(mem, stdin=stdin) + + if expected_mem is not None: + assert expected_mem == mem + if expected_out is not None: + assert expected_out == out + + +def test_day2(): + tests = [ + [[1,0,0,0,99], [2,0,0,0,99]], # ADD 1 + 1 to location 0 + [[2,3,0,3,99], [2,3,0,6,99]], # MUL 2 * 3 to location 3 + [[2,4,4,5,99,0], [2,4,4,5,99,9801]], # MUL 99 * 99 to location 5 (9801) + [[1,1,1,4,99,5,6,0,99], [30,1,1,4,2,5,6,0,99]], + ] + for program, expected in tests: + run_test(program, expected) + + +def test_day5_basic(): + # Using position mode, consider whether the input is equal to 8; output 1 + # (if it is) or 0 (if it is not). + prog = [3,9,8,9,10,9,4,9,99,-1,8] + run_test(prog, stdin=[0], expected_out=[0]) + run_test(prog, stdin=[8], expected_out=[1]) + + # Using position mode, consider whether the input is less than 8; output 1 + # (if it is) or 0 (if it is not). + prog = [3,9,7,9,10,9,4,9,99,-1,8] + run_test(prog, stdin=[7], expected_out=[1]) + run_test(prog, stdin=[9], expected_out=[0]) + + # Using immediate mode, consider whether the input is equal to 8; output 1 + # (if it is) or 0 (if it is not). + prog = [3,3,1108,-1,8,3,4,3,99] + run_test(prog, stdin=[8], expected_out=[1]) + run_test(prog, stdin=[9], expected_out=[0]) + + # Using immediate mode, consider whether the input is less than 8; output 1 + # (if it is) or 0 (if it is not). + prog = [3,3,1107,-1,8,3,4,3,99] + run_test(prog, stdin=[7], expected_out=[1]) + run_test(prog, stdin=[9], expected_out=[0]) + + # Here are some jump tests that take an input, then output 0 if the input + # was zero or 1 if the input was non-zero: + prog = [3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9] # using position mode + run_test(prog, stdin=[0], expected_out=[0]) + run_test(prog, stdin=[4], expected_out=[1]) + + prog = [3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9] # using immediate mode + run_test(prog, stdin=[0], expected_out=[0]) + run_test(prog, stdin=[4], expected_out=[1]) + + +def test_day5_larger(): + """ + The above example program uses an input instruction to ask for a single + number. The program will then output 999 if the input value is below 8, + output 1000 if the input value is equal to 8, or output 1001 if the input + value is greater than 8. + """ + prog = [3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31, + 1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104, + 999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99] + run_test(prog, stdin=[7], expected_out=[999]) + run_test(prog, stdin=[8], expected_out=[1000]) + run_test(prog, stdin=[9], expected_out=[1001]) + + +def test(): + # factorial test + fac = [3,29,1007,29,2,28,1005,28,24,2,27,29,27,1001,29,-1,29,1101,0,0,28,1006,28,2,4,27,99,1,0,0] + res = interpret_intcode(fac, [4]) + assert res == [24], f"Expected 24 but got {res}" +