2019 day 7 part 1

This commit is contained in:
2025-08-01 00:02:28 +02:00
parent cf2d785c83
commit 5139d7c671
3 changed files with 268 additions and 0 deletions

53
2019/day7/day7.py Normal file
View File

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

132
2019/intcode/intcode.py Normal file
View File

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

83
2019/intcode/tests.py Normal file
View File

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