mirror of
https://github.com/thib8956/advent-of-code.git
synced 2025-08-24 08:21:57 +00:00
chore: create new project structure and aoc.py runner script
This commit is contained in:
23
adventofcode/2019/intcode/benchmark.py
Normal file
23
adventofcode/2019/intcode/benchmark.py
Normal 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))
|
||||
|
194
adventofcode/2019/intcode/intcode.py
Normal file
194
adventofcode/2019/intcode/intcode.py
Normal file
@@ -0,0 +1,194 @@
|
||||
#! /usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
import logging
|
||||
import os
|
||||
|
||||
loglevel = (os.getenv("LOGLEVEL") or "WARN").upper()
|
||||
logging.basicConfig(format="%(levelname)s:%(message)s", level=loglevel)
|
||||
|
||||
|
||||
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
|
||||
BASE = 9
|
||||
TERMINATION = 99
|
||||
|
||||
|
||||
class Mode(Enum):
|
||||
POSITION = 0
|
||||
IMMEDIATE = 1
|
||||
RELATIVE = 2
|
||||
|
||||
|
||||
class Instruction:
|
||||
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(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_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):
|
||||
return self.handler()
|
||||
|
||||
def handle_addition(self):
|
||||
first, second = self._get_parameters()
|
||||
logging.debug(f"ADD {first} {second}")
|
||||
result = first + second
|
||||
address = self._get_write_addr(3)
|
||||
logging.debug(f"{address}")
|
||||
self._write(address, result)
|
||||
self.ip += 4
|
||||
return self.ip
|
||||
|
||||
def handle_multiplication(self):
|
||||
first, second = self._get_parameters()
|
||||
logging.debug(f"MUL {first} {second}")
|
||||
result = first * second
|
||||
address = self._get_write_addr(3)
|
||||
self._write(address, result)
|
||||
self.ip += 4
|
||||
return self.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):
|
||||
self.output = self._get_value(1)
|
||||
logging.debug(f"OUT {self.output}")
|
||||
self.ip += 2
|
||||
return self.ip
|
||||
|
||||
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):
|
||||
first, second = self._get_parameters()
|
||||
logging.debug(f"JMPF {first} {second}")
|
||||
return second if first == 0 else self.ip + 3
|
||||
|
||||
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):
|
||||
first, second = self._get_parameters()
|
||||
logging.debug(f"EQ {first} {second}")
|
||||
address = self._get_write_addr(3)
|
||||
self._write(address, int(first == second))
|
||||
self.ip += 4
|
||||
return self.ip
|
||||
|
||||
def handle_termination(self):
|
||||
logging.debug("HALT")
|
||||
self.halted = True
|
||||
return self.ip
|
||||
|
||||
def handle_base(self):
|
||||
self.base += self._get_value(1)
|
||||
logging.debug(f"BASE {self.base}")
|
||||
self.ip += 2
|
||||
return self.ip
|
||||
|
||||
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:
|
||||
def __init__(self, program, stdin=[]):
|
||||
self.ip = 0
|
||||
self.stdin = stdin
|
||||
self.stdout = []
|
||||
self.memory = program[::]
|
||||
self.halted = False
|
||||
self.base = 0
|
||||
|
||||
def __repr__(self):
|
||||
return f"Interpreter(ip={self.ip}, stdin={self.stdin}, stdout={self.stdout}, halted={self.halted})"
|
||||
|
||||
def interpret(self, break_on_input=False, break_on_output=True):
|
||||
while not self.halted:
|
||||
# fetch next instruction
|
||||
instruction = Instruction(self.memory, self.ip, self.base)
|
||||
# pause if INP + break on input
|
||||
if instruction.operation == Operation.INPUT:
|
||||
if break_on_input and self.stdin == []:
|
||||
break
|
||||
else:
|
||||
instruction.input = self.stdin.pop(0)
|
||||
# execute instruction
|
||||
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)
|
||||
if break_on_output:
|
||||
break
|
||||
if instruction.halted:
|
||||
self.halted = True
|
||||
|
||||
|
||||
def interpret_intcode(program, stdin=[]):
|
||||
interpreter = Interpreter(program, stdin)
|
||||
interpreter.interpret(break_on_output=False)
|
||||
return interpreter
|
||||
|
111
adventofcode/2019/intcode/tests.py
Normal file
111
adventofcode/2019/intcode/tests.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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 == out.memory
|
||||
if expected_out is not None:
|
||||
assert expected_out == out.stdout
|
||||
|
||||
|
||||
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_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.stdout == [24]
|
Reference in New Issue
Block a user