2019 day 9

This commit is contained in:
2025-08-01 17:35:51 +02:00
parent f705cf64fe
commit 854d0b51fb
5 changed files with 185 additions and 77 deletions

View File

@@ -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():

20
2019/day9/day9.py Normal file
View File

@@ -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())

23
2019/intcode/benchmark.py Normal file
View File

@@ -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))

View File

@@ -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

View File

@@ -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]