From 854d0b51fb5631f376003ea2e296df833c092b64 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Fri, 1 Aug 2025 17:35:51 +0200 Subject: [PATCH] 2019 day 9 --- 2019/day7/day7.py | 16 ++-- 2019/day9/day9.py | 20 +++++ 2019/intcode/benchmark.py | 23 ++++++ 2019/intcode/intcode.py | 163 +++++++++++++++++++++++--------------- 2019/intcode/tests.py | 40 ++++++++-- 5 files changed, 185 insertions(+), 77 deletions(-) create mode 100644 2019/day9/day9.py create mode 100644 2019/intcode/benchmark.py diff --git a/2019/day7/day7.py b/2019/day7/day7.py index 73b4283..040d103 100644 --- a/2019/day7/day7.py +++ b/2019/day7/day7.py @@ -14,7 +14,7 @@ def main(inp): max_ret = 0 for seq in permutations([0, 1, 2, 3, 4], 5): ret = amplifiers(mem, list(seq)) - max_ret = max(ret[0], max_ret) + max_ret = max(ret.stdout[0], max_ret) print("Part 1: ", max_ret) max_ret = 0 @@ -26,10 +26,10 @@ def main(inp): 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()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.stdout.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.stdout.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.stdout.pop()]) + ret = interpret_intcode(program[::], [sequence.pop(0), ret.stdout.pop()]) return ret @@ -58,20 +58,20 @@ 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] + assert res.stdout == [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] + assert res.stdout == [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] + assert res.stdout == [65210] def tests2(): diff --git a/2019/day9/day9.py b/2019/day9/day9.py new file mode 100644 index 0000000..6fbcab5 --- /dev/null +++ b/2019/day9/day9.py @@ -0,0 +1,20 @@ +# TODO replace PYTHONPATH hack with a proper solution, like making intcode an +# installed module https://stackoverflow.com/a/50194143 +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode")) + +from intcode import interpret_intcode, Interpreter + + +def main(inp): + mem = list(map(int, inp.readline().rstrip().split(","))) + print("Part 1: ", interpret_intcode(mem[::], stdin=[1]).stdout[0]) + print("Part 2: ", interpret_intcode(mem[::], stdin=[2]).stdout[0]) + + +if __name__ == "__main__": + import fileinput + main(fileinput.input()) + diff --git a/2019/intcode/benchmark.py b/2019/intcode/benchmark.py new file mode 100644 index 0000000..1e5eb7c --- /dev/null +++ b/2019/intcode/benchmark.py @@ -0,0 +1,23 @@ +# https://www.reddit.com/r/adventofcode/comments/egq9xn/2019_day_9_intcode_benchmarking_suite/ +from intcode import interpret_intcode + + +def isqrt(n): + program = [3,1,109,149,21101,0,15,0,20101,0,1,1,1105,1,18,204,1,99,22101,0,1,2,22101,0,1,1,21101,0,43,3,22101,0,1,4,22101,0,2,5,109,3,1105,1,78,109,-3,22102,-1,1,1,22201,1,4,3,22102,-1,1,1,1208,3,0,62,2105,-1,0,1208,3,1,69,2105,-1,0,22101,0,4,1,1105,1,26,1207,1,1,83,2105,-1,0,21101,0,102,3,22101,0,2,4,22101,0,1,5,109,3,1105,1,115,109,-3,22201,1,4,1,21101,0,2,2,1105,1,115,2102,-1,2,140,2101,0,2,133,22101,0,1,2,20001,133,140,1,1207,2,-1,136,2105,-1,0,21201,2,-1,2,22101,1,1,1,1105,1,131] + res = interpret_intcode(program, stdin=[n]) + return res.stdout.pop() + + +def sum_of_primes(n): + program = [3,100,1007,100,2,7,1105,-1,87,1007,100,1,14,1105,-1,27,101,-2,100,100,101,1,101,101,1105,1,9,101,105,101,105,101,2,104,104,101,1,102,102,1,102,102,103,101,1,103,103,7,102,101,52,1106,-1,87,101,105,102,59,1005,-1,65,1,103,104,104,101,105,102,83,1,103,83,83,7,83,105,78,1106,-1,35,1101,0,1,-1,1105,1,69,4,104,99] + res = interpret_intcode(program, stdin=[n]) + return res.stdout.pop() + + + +if __name__ == '__main__': + #import timeit + #print(timeit.timeit("test()", globals=locals())) + print(isqrt(111)) + print(sum_of_primes(10)) + diff --git a/2019/intcode/intcode.py b/2019/intcode/intcode.py index be112cb..52ceb63 100644 --- a/2019/intcode/intcode.py +++ b/2019/intcode/intcode.py @@ -1,9 +1,11 @@ #! /usr/bin/env python3 -from collections import namedtuple +from collections import defaultdict from enum import Enum import logging +import os -logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARN) +loglevel = (os.getenv("LOGLEVEL") or "WARN").upper() +logging.basicConfig(format="%(levelname)s:%(message)s", level=loglevel) def get_nth_digit(n, number): @@ -20,103 +22,135 @@ class Operation(Enum): JMP_IF_FALSE = 6 LESS_THAN = 7 EQUALS = 8 + BASE = 9 TERMINATION = 99 class Mode(Enum): POSITION = 0 IMMEDIATE = 1 + RELATIVE = 2 class Instruction: - def __init__(self, opcode): - self.opcode = opcode + def __init__(self, memory, ip, base): + self.ip = ip + self.memory = memory + self.opcode = memory[ip] # 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.operation = Operation(self.opcode % 100) + self.modes = [Mode(get_nth_digit(n, self.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.handler = getattr(self, self.handler_name, self.handle_unknown) self.input = None self.output = None self.halted = False + self.base = base def __repr__(self): return f"[{self.opcode}] Instruction({self.operation}, {self.modes})" - def handle(self, program, ip): - return self.handler(program, ip) + def handle(self): + return self.handler() - def handle_addition(self, program, ip): - first, second = self._get_parameters(program, ip) + def handle_addition(self): + first, second = self._get_parameters() 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 + address = self._get_write_addr(3) + logging.debug(f"{address}") + self._write(address, result) + self.ip += 4 + return self.ip - def handle_multiplication(self, program, ip): - first, second = self._get_parameters(program, ip) + def handle_multiplication(self): + first, second = self._get_parameters() 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 + address = self._get_write_addr(3) + self._write(address, result) + self.ip += 4 + return self.ip - def handle_input(self, program, ip): - program[program[ip + 1]] = self.input - ip += 2 - return ip + def handle_input(self): + address = self._get_write_addr(1) + self._write(address, self.input) + logging.debug(f"INP {address} {self.input}") + self.ip += 2 + return self.ip - def handle_output(self, program, ip): - self.output = self._get_param(program, ip) + def handle_output(self): + self.output = self._get_value(1) logging.debug(f"OUT {self.output}") - ip += 2 - return ip + self.ip += 2 + return self.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_true(self): + first, second = self._get_parameters() + logging.debug(f"JMPT {first} {second} {first != 0}") + return second if first != 0 else self.ip + 3 - def handle_jmp_if_false(self, program, ip): - first, second = self._get_parameters(program, ip) + def handle_jmp_if_false(self): + first, second = self._get_parameters() logging.debug(f"JMPF {first} {second}") - return second if first == 0 else ip + 3 + return second if first == 0 else self.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_less_than(self): + first, second = self._get_parameters() + logging.debug(f"LT {first} {second} {first < second}") + address = self._get_write_addr(3) + self._write(address, int(first < second)) + self.ip += 4 + return self.ip - def handle_equals(self, program, ip): - first, second = self._get_parameters(program, ip) + def handle_equals(self): + first, second = self._get_parameters() logging.debug(f"EQ {first} {second}") - program[program[ip + 3]] = int(first == second) - ip += 4 - return ip + address = self._get_write_addr(3) + self._write(address, int(first == second)) + self.ip += 4 + return self.ip - def handle_termination(self, program, ip): + def handle_termination(self): logging.debug("HALT") self.halted = True - return ip + return self.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 handle_base(self): + self.base += self._get_value(1) + logging.debug(f"BASE {self.base}") + self.ip += 2 + return self.ip - def _get_parameters(self, program, ip): - first = self._get_param(program, ip, 0) - second = self._get_param(program, ip, 1) + def handle_unknown(self): + raise ValueError(f"Unknown operation <{self.operation}> @ [{self.ip}]") + + def _get_value(self, offset): + value = self.memory[self.ip + offset] + match self.modes[offset - 1]: + case Mode.POSITION: return self.memory[value] if value < len(self.memory) else 0 + case Mode.IMMEDIATE: return value + case Mode.RELATIVE: return self.memory[value + self.base] + case _: raise ValueError(f"{self.modes[i]}") + + def _get_write_addr(self, offset): + value = self.memory[self.ip + offset] + match self.modes[offset - 1]: + case Mode.POSITION: return value + case Mode.RELATIVE: return value + self.base + case _: raise ValueError(f"{self.modes[i]}") + + def _get_parameters(self): + first = self._get_value(1) + second = self._get_value(2) return first, second + + def _write(self, address, value): + while address >= len(self.memory): + self.memory += [0] * len(self.memory) + self.memory[address] = value class Interpreter: @@ -124,17 +158,20 @@ class Interpreter: self.ip = 0 self.stdin = stdin self.stdout = [] - self.program = program + self.memory = program[::] self.halted = False + self.base = 0 def __repr__(self): - return f"Interpreter(ip={self.ip}, {self.stdin}, {self.stdout}, {self.halted})" + return f"Interpreter(ip={self.ip}, stdin={self.stdin}, stdout={self.stdout}, halted={self.halted})" def next_instruction(self): - instruction = Instruction(self.program[self.ip]) + instruction = Instruction(self.memory, self.ip, self.base) if instruction.operation == Operation.INPUT: instruction.input = self.stdin.pop(0) - self.ip = instruction.handle(self.program, self.ip) + self.ip = instruction.handle() + self.base = instruction.base + logging.debug(f"IP {self.ip}") if instruction.output is not None: self.stdout.append(instruction.output) elif instruction.halted: @@ -152,5 +189,5 @@ class Interpreter: def interpret_intcode(program, stdin=[]): interpreter = Interpreter(program, stdin) interpreter.interpret(break_on_output=False) - return interpreter.stdout + return interpreter diff --git a/2019/intcode/tests.py b/2019/intcode/tests.py index d12ff07..f3a6a8c 100644 --- a/2019/intcode/tests.py +++ b/2019/intcode/tests.py @@ -6,11 +6,10 @@ 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 + assert expected_mem == out.memory if expected_out is not None: - assert expected_out == out + assert expected_out == out.stdout def test_day2(): @@ -75,9 +74,38 @@ def test_day5_larger(): run_test(prog, stdin=[9], expected_out=[1001]) -def test(): +def test_day9_base(): + program = [109, 19, 109, 6, 99] + interpreter = Interpreter(program) + interpreter.base = 2000 + interpreter.interpret(break_on_output=False) + assert interpreter.base == 2025 + + +def test_day9_relative(): + program = [109,6,21001,9,25,1,104,0,99,49] + ret = interpret_intcode(program) + assert ret.stdout == [74] + + +def test_day9_quine(): + quine = [109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99] + run_test(quine, expected_out=quine) + + +def test_day9_relative(): + run_test([109, -1, 4, 1, 99], expected_out=[-1]) + run_test([109, -1, 104, 1, 99], expected_out=[1]) + run_test([109, -1, 204, 1, 99], expected_out=[109]) + run_test([109, 1, 9, 2, 204, -6, 99], expected_out=[204]) + run_test([109, 1, 109, 9, 204, -6, 99], expected_out=[204]) + run_test([109, 1, 209, -1, 204, -106, 99], expected_out=[204]) + run_test([109, 1, 3, 3, 204, 2, 99], stdin=[69], expected_out=[69]) + run_test([109, 1, 203, 2, 204, 2, 99], stdin=[1337], expected_out=[1337]) + + +def test_fac(): # 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}" - + assert res.stdout == [24]