mirror of
https://github.com/thib8956/advent-of-code.git
synced 2025-08-24 00:11:57 +00:00
chore: create new project structure and aoc.py runner script
This commit is contained in:
0
adventofcode/2019/__init__.py
Normal file
0
adventofcode/2019/__init__.py
Normal file
44
adventofcode/2019/day1/day1.py
Normal file
44
adventofcode/2019/day1/day1.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
|
||||
def calculate_fuel_iterative(mass):
|
||||
total = 0
|
||||
new_mass = mass
|
||||
while True:
|
||||
new_mass = new_mass // 3 - 2
|
||||
if new_mass < 0:
|
||||
break
|
||||
total += new_mass
|
||||
return total
|
||||
|
||||
|
||||
def calculate_total_fuel_mass(input_file, mass_function=lambda x: x // 3 - 2):
|
||||
total = 0
|
||||
with open(input_file) as masses:
|
||||
for mass in masses:
|
||||
total += mass_function(int(mass))
|
||||
return total
|
||||
|
||||
|
||||
def test_part2():
|
||||
inputs = (14, 1969, 100756)
|
||||
expected = (2, 966, 50346)
|
||||
for i, inp in enumerate(inputs):
|
||||
result = calculate_fuel_iterative(inp)
|
||||
assert result == expected[i], "Result for {} should be {}, was {}".format(
|
||||
inp, expected[i], result
|
||||
)
|
||||
print("All tests passed for part 2.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt"
|
||||
|
||||
print("Part 1 - total mass: ", calculate_total_fuel_mass(infile))
|
||||
|
||||
test_part2()
|
||||
print(
|
||||
"Part 2 -- total mass: ",
|
||||
calculate_total_fuel_mass(infile, calculate_fuel_iterative),
|
||||
)
|
65
adventofcode/2019/day11/day11.py
Normal file
65
adventofcode/2019/day11/day11.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# 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 paint(program, initial_state):
|
||||
interpreter = Interpreter(program)
|
||||
pos = 0 + 0j
|
||||
direction = 0 - 1j # initially facing up ^
|
||||
colors = {}
|
||||
colors[pos] = initial_state
|
||||
while True:
|
||||
interpreter.stdin.append(colors.get(pos, 0))
|
||||
interpreter.interpret(break_on_output=True)
|
||||
interpreter.interpret(break_on_output=True)
|
||||
if interpreter.halted:
|
||||
return colors
|
||||
|
||||
color = interpreter.stdout.pop(0)
|
||||
colors[pos] = color
|
||||
|
||||
turn = interpreter.stdout.pop(0)
|
||||
if turn == 0:
|
||||
direction *= -1j # turn left
|
||||
elif turn == 1:
|
||||
direction *= 1j # turn right
|
||||
else:
|
||||
assert False
|
||||
|
||||
pos += direction
|
||||
|
||||
|
||||
def main(data, part=1):
|
||||
program = list(map(int, data.readline().rstrip().split(",")))
|
||||
if part == 1:
|
||||
colors = paint(program, initial_state=0)
|
||||
print("Part 1: ", len(colors))
|
||||
else:
|
||||
colors = paint(program, initial_state=1)
|
||||
part2(colors)
|
||||
|
||||
|
||||
def part2(colors):
|
||||
min_x = int(min(x.real for x in colors.keys()))
|
||||
max_x = int(max(x.real for x in colors.keys()))
|
||||
min_y = int(min(x.imag for x in colors.keys()))
|
||||
max_y = int(max(x.imag for x in colors.keys()))
|
||||
for y in range(min_y, max_y + 1):
|
||||
l = []
|
||||
for x in range(min_x, max_x + 1):
|
||||
point = complex(x, y)
|
||||
l.append("\u2588" if point in colors and colors[point] == 1 else " ")
|
||||
print("".join(l))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
with fileinput.input() as f:
|
||||
main(f, part=1)
|
||||
#with fileinput.input() as f:
|
||||
#main(f, part=2) # FIXME unable to run both parts simultaneously
|
||||
|
0
adventofcode/2019/day13/__init__.py
Normal file
0
adventofcode/2019/day13/__init__.py
Normal file
117
adventofcode/2019/day13/day13.py
Normal file
117
adventofcode/2019/day13/day13.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
# TODO replace PYTHONPATH hack with a proper solution, like making intcode an
|
||||
# installed module https://stackoverflow.com/a/50194143
|
||||
import sys
|
||||
import time
|
||||
from itertools import zip_longest
|
||||
from pathlib import Path
|
||||
from collections import defaultdict, Counter
|
||||
from dataclasses import dataclass
|
||||
|
||||
sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode"))
|
||||
from intcode import interpret_intcode, Interpreter
|
||||
|
||||
@dataclass
|
||||
class State:
|
||||
grid: ...
|
||||
score = 0
|
||||
bounds = None
|
||||
paddle_pos = None
|
||||
ball_pos = None
|
||||
stopped = False
|
||||
|
||||
|
||||
def grouper(n, iterable):
|
||||
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
|
||||
args = [iter(iterable)] * n
|
||||
return zip_longest(*args)
|
||||
|
||||
|
||||
def part1(program):
|
||||
interpreter = interpret_intcode(program)
|
||||
game = { (x, y): tile for x, y, tile in grouper(3, interpreter.stdout) }
|
||||
print("Part 1: ", len([x for x in game.values() if x == 2]))
|
||||
|
||||
|
||||
def parse_map(output):
|
||||
grid = {}
|
||||
score = None
|
||||
for x, y, tile in grouper(3, output):
|
||||
if (x, y) == (-1, 0):
|
||||
score = tile
|
||||
else:
|
||||
grid[x,y] = tile
|
||||
paddle = next((k for k, v in grid.items() if v == 3), None)
|
||||
ball = next((k for k, v in grid.items() if v == 4), None)
|
||||
bounds = (max(x for x, y in grid.keys()),
|
||||
max(y for x, y in grid.keys()))
|
||||
return grid, bounds, score, paddle, ball
|
||||
|
||||
|
||||
def update_state(state: State, stdout):
|
||||
grid, bounds, score, paddle, ball = parse_map(stdout)
|
||||
if ball is None:
|
||||
state.stopped = True
|
||||
if state.bounds is None: # only set bounds the first time
|
||||
state.bounds = bounds
|
||||
if score is not None:
|
||||
state.score = score
|
||||
if paddle is not None:
|
||||
state.paddle_pos = paddle
|
||||
if ball is not None:
|
||||
state.ball_pos = ball
|
||||
if grid is None:
|
||||
state.grid = grid
|
||||
else:
|
||||
# merge grid
|
||||
for (x, y), v in grid.items():
|
||||
state.grid[x,y] = v
|
||||
return state
|
||||
|
||||
|
||||
def part2(program):
|
||||
program[0] = 2
|
||||
interpreter = Interpreter(program)
|
||||
state = State({})
|
||||
while not interpreter.halted:
|
||||
interpreter.interpret(break_on_output=False, break_on_input=True)
|
||||
state = update_state(state, interpreter.stdout)
|
||||
if state.stopped:
|
||||
break
|
||||
#print_map(state)
|
||||
interpreter.stdout.clear()
|
||||
paddle_x, ball_x = state.paddle_pos[0], state.ball_pos[0]
|
||||
if paddle_x < ball_x: # move right
|
||||
interpreter.stdin.append(1)
|
||||
elif paddle_x > ball_x: # move left
|
||||
interpreter.stdin.append(-1)
|
||||
else:
|
||||
interpreter.stdin.append(0)
|
||||
print("Part 2: ", state.score)
|
||||
|
||||
|
||||
def print_map(state):
|
||||
clear = "\033[2J"
|
||||
tiles = { 0: " ", 1: "#", 2: "~", 3: "_", 4: "O" }
|
||||
max_x, max_y = state.bounds
|
||||
print(clear)
|
||||
for y in range(max_y + 1):
|
||||
l = []
|
||||
for x in range(max_x + 1):
|
||||
tile = state.grid[x,y]
|
||||
l.append(tiles[tile])
|
||||
print("".join(l))
|
||||
time.sleep(1/60)
|
||||
|
||||
|
||||
def main(inp):
|
||||
program = [int(x) for x in inp.readline().rstrip().split(",")]
|
||||
part1(program)
|
||||
part2(program)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt"
|
||||
with open(infile) as raw_input:
|
||||
main(raw_input)
|
||||
|
71
adventofcode/2019/day2/day2.py
Normal file
71
adventofcode/2019/day2/day2.py
Normal file
@@ -0,0 +1,71 @@
|
||||
def interpret_intcode(input_prog):
|
||||
# Instruction pointer: index of the next element to be executed
|
||||
ip = 0
|
||||
while ip < len(input_prog):
|
||||
instruction = input_prog[ip]
|
||||
if instruction == 99:
|
||||
break
|
||||
|
||||
elif instruction == 1:
|
||||
# The operands to sum are at the memory location ip+1 and ip+2.
|
||||
operands = (input_prog[input_prog[ip + 1]], input_prog[input_prog[ip + 2]])
|
||||
result = sum(operands)
|
||||
target = input_prog[ip + 3]
|
||||
input_prog[target] = result
|
||||
ip += 4
|
||||
|
||||
elif instruction == 2:
|
||||
# The operands to multiply are at the memory location ip+1 and ip+2.
|
||||
operands = (input_prog[input_prog[ip + 1]], input_prog[input_prog[ip + 2]])
|
||||
result = operands[0] * operands[1]
|
||||
target = input_prog[ip + 3]
|
||||
input_prog[target] = result
|
||||
ip += 4
|
||||
|
||||
|
||||
def tests():
|
||||
inputs = (
|
||||
[1, 0, 0, 0, 99], # ADD 1 + 1 to location 0
|
||||
[2, 3, 0, 3, 99], # MUL 2 * 3 to location 3
|
||||
[2, 4, 4, 5, 99, 0],
|
||||
[1, 1, 1, 4, 99, 5, 6, 0, 99],
|
||||
)
|
||||
expected_outputs = (
|
||||
[2, 0, 0, 0, 99], # 1 + 1 = 2
|
||||
[2, 3, 0, 6, 99], # 3 * 2 = 6
|
||||
[2, 4, 4, 5, 99, 9801], # 99 * 99 = 9801
|
||||
[30, 1, 1, 4, 2, 5, 6, 0, 99],
|
||||
)
|
||||
for i, inp in enumerate(inputs):
|
||||
result = inp[:]
|
||||
interpret_intcode(result)
|
||||
assert (
|
||||
result == expected_outputs[i]
|
||||
), f"Expected output for {inp} is {expected_outputs[i]}, but found {result} instead."
|
||||
print("All tests passed.")
|
||||
|
||||
|
||||
def run_program(memory, noun, verb):
|
||||
memory[1] = noun
|
||||
memory[2] = verb
|
||||
interpret_intcode(memory)
|
||||
return memory[0]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests()
|
||||
import sys
|
||||
infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt"
|
||||
with open(infile) as inp:
|
||||
memory = [int(x) for x in inp.readline().strip().split(",")]
|
||||
# Pass a copy to avoid modifying the original memory
|
||||
print("Part 1 answer: ", run_program(memory[:], 12, 2))
|
||||
|
||||
# Part 2
|
||||
result = 0
|
||||
for verb in range(99):
|
||||
for noun in range(99):
|
||||
if run_program(memory[:], noun, verb) == 19690720:
|
||||
print(f"Part 2: noun={noun}, verb={verb}")
|
||||
print("Result = ", 100 * noun + verb)
|
||||
break
|
79
adventofcode/2019/day3/day3.py
Normal file
79
adventofcode/2019/day3/day3.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
def manhattan_distance(p):
|
||||
return abs(p[0]) + abs(p[1])
|
||||
|
||||
|
||||
def points_for_wire(wire):
|
||||
x, y, count = 0, 0, 0
|
||||
points = {}
|
||||
# (x, y)
|
||||
directions = {"R": (1, 0), "L": (-1, 0), "U": (0, 1), "D": (0, -1)}
|
||||
for p in wire:
|
||||
# D42 -> for _ in range(42)
|
||||
for _ in range(int(p[1:])):
|
||||
offset = directions[p[0]]
|
||||
x += offset[0]
|
||||
y += offset[1]
|
||||
count += 1
|
||||
points[(x ,y)] = count
|
||||
|
||||
return points
|
||||
|
||||
|
||||
def find_min_distance(wire1, wire2):
|
||||
points1 = points_for_wire(wire1)
|
||||
points2 = points_for_wire(wire2)
|
||||
|
||||
intersections = points1.keys() & points2.keys()
|
||||
closest = min((intersection for intersection in intersections), key=manhattan_distance)
|
||||
|
||||
return manhattan_distance(closest)
|
||||
|
||||
|
||||
def find_least_steps(wire1, wire2):
|
||||
points1 = points_for_wire(wire1)
|
||||
points2 = points_for_wire(wire2)
|
||||
|
||||
intersections = points1.keys() & points2.keys()
|
||||
# Intersection with the least steps
|
||||
least_steps = min(intersections, key=lambda x: points1[x] + points2[x])
|
||||
|
||||
return points1[least_steps] + points2[least_steps]
|
||||
|
||||
|
||||
def tests():
|
||||
inputs = (
|
||||
(("R8", "U5", "L5", "D3"), ("U7", "R6", "D4", "L4")),
|
||||
(("R75","D30","R83", "U83", "L12", "D49", "R71", "U7", "L72"), ("U62", "R66", "U55", "R34", "D71", "R55", "D58", "R83")),
|
||||
(("R98", "U47", "R26", "D63", "R33", "U87", "L62", "D20", "R33", "U53", "R51"), ("U98", "R91", "D20", "R16", "D67", "R40", "U7", "R15", "U6", "R7"))
|
||||
)
|
||||
|
||||
# Part 1
|
||||
expected = (6, 159, 135)
|
||||
for i, inp in enumerate(inputs):
|
||||
result = find_min_distance(inp[0], inp[1])
|
||||
assert result == expected[i], "Result for {} should be {}, was {}".format(
|
||||
inp, expected[i], result
|
||||
)
|
||||
|
||||
# Part 2
|
||||
# expected number of steps
|
||||
expected_part2 = (30, 610, 410)
|
||||
print("All tests passed.")
|
||||
for i, inp in enumerate(inputs):
|
||||
result = find_least_steps(inp[0], inp[1])
|
||||
assert result == expected_part2[i], "Result for {} should be {}, was {}".format(
|
||||
inp, expected_part2[i], result
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests()
|
||||
import sys
|
||||
infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt"
|
||||
with open(infile) as raw_input:
|
||||
lines = raw_input.readlines()
|
||||
wire1, wire2 = [line.strip("\n").split(",") for line in lines]
|
||||
print("Part 1 -- distance = ", find_min_distance(wire1, wire2))
|
||||
print("Part 2 -- steps = ", find_least_steps(wire1, wire2))
|
37
adventofcode/2019/day4/day4.py
Normal file
37
adventofcode/2019/day4/day4.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
|
||||
def check_increase(number):
|
||||
num = str(number)
|
||||
for i in range(len(num) - 1):
|
||||
if num[i+1] < num[i]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_adjacent(number):
|
||||
num = str(number)
|
||||
for digit in num:
|
||||
count = num.count(digit)
|
||||
if count == 2: # Part one : <= 2
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def tests():
|
||||
assert check_increase(123456) == True
|
||||
assert check_increase(123454) == False
|
||||
assert check_adjacent(112345) == True
|
||||
assert check_adjacent(123445) == True
|
||||
assert check_adjacent(123456) == False
|
||||
|
||||
|
||||
def main(start, end):
|
||||
matches = 0
|
||||
for n in range(start, end + 1):
|
||||
if check_increase(n) and check_adjacent(n):
|
||||
matches += 1
|
||||
return matches
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests()
|
||||
print("Matches : ", main(367479, 893698))
|
177
adventofcode/2019/day5/day5.py
Normal file
177
adventofcode/2019/day5/day5.py
Normal file
@@ -0,0 +1,177 @@
|
||||
#! /usr/bin/env python3
|
||||
from collections import namedtuple
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARN)
|
||||
|
||||
|
||||
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.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 = 0
|
||||
self.output = 0
|
||||
|
||||
def __repr__(self):
|
||||
return f"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):
|
||||
param = (
|
||||
program[ip + 1]
|
||||
if self.modes[0] is Mode.IMMEDIATE
|
||||
else program[program[ip + 1]]
|
||||
)
|
||||
|
||||
self.output = param
|
||||
print("OUT ", param)
|
||||
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):
|
||||
print("HALT")
|
||||
return ip
|
||||
|
||||
def _get_parameters(self, program, ip):
|
||||
first = (
|
||||
program[ip + 1]
|
||||
if self.modes[0] is Mode.IMMEDIATE
|
||||
else program[program[ip + 1]]
|
||||
)
|
||||
second = (
|
||||
program[ip + 2]
|
||||
if self.modes[1] is Mode.IMMEDIATE
|
||||
else program[program[ip + 2]]
|
||||
)
|
||||
return first, second
|
||||
|
||||
|
||||
def interpret_intcode(program, stdin=[]):
|
||||
ip = 0
|
||||
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)
|
||||
return instruction.output
|
||||
|
||||
|
||||
def tests():
|
||||
inputs = (
|
||||
[1, 0, 0, 0, 99], # ADD 1 + 1 to location 0
|
||||
[2, 3, 0, 3, 99], # MUL 2 * 3 to location 3
|
||||
[2, 4, 4, 5, 99, 0],
|
||||
[1, 1, 1, 4, 99, 5, 6, 0, 99],
|
||||
[1101, 1, 1, 0, 99], # ADD 1 + 1 to location 0 (direct access)
|
||||
)
|
||||
expected_outputs = (
|
||||
[2, 0, 0, 0, 99], # 1 + 1 = 2
|
||||
[2, 3, 0, 6, 99], # 3 * 2 = 6
|
||||
[2, 4, 4, 5, 99, 9801], # 99 * 99 = 9801
|
||||
[30, 1, 1, 4, 2, 5, 6, 0, 99],
|
||||
[2, 1, 1, 0, 99], # 1 + 1 = 2 (direct access)
|
||||
)
|
||||
for i, inp in enumerate(inputs):
|
||||
result = inp[:]
|
||||
interpret_intcode(result)
|
||||
assert (
|
||||
result == expected_outputs[i]
|
||||
), f"Expected output for {inp} is {expected_outputs[i]}, but found {result} instead."
|
||||
|
||||
# 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}"
|
||||
|
||||
print("\nAll tests passed.\n")
|
||||
|
||||
|
||||
def run_input_program(inp):
|
||||
print("Start of input program.")
|
||||
memory = [int(x) for x in inp.readline().strip().split(",")]
|
||||
part1 = interpret_intcode(memory[::], [1])
|
||||
print("Part 1: ", part1)
|
||||
part2 = interpret_intcode(memory[::], [5])
|
||||
print("Part 2: ", part2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests()
|
||||
import fileinput
|
||||
run_input_program(fileinput.input())
|
||||
|
192
adventofcode/2019/day5/day52.py
Normal file
192
adventofcode/2019/day5/day52.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import logging
|
||||
|
||||
logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG)
|
||||
|
||||
|
||||
class Instruction:
|
||||
code, argument_num = 0, 0
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
pass
|
||||
|
||||
def new_pc(self, intcode):
|
||||
return intcode.pc + self.argument_num + 1
|
||||
|
||||
|
||||
class PlusInstruction(Instruction):
|
||||
code, argument_num = 1, 3
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
logging.debug(f"ADD {arguments[0].value} {arguments[1].value}")
|
||||
intcode.set(arguments[2].address, arguments[0].value + arguments[1].value)
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class MultiplyInstruction(Instruction):
|
||||
code, argument_num = 2, 3
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
logging.debug(f"MUL {arguments[0].value} {arguments[1].value}")
|
||||
intcode.set(arguments[2].address, arguments[0].value * arguments[1].value)
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class InputInstruction(Instruction):
|
||||
code, argument_num = 3, 1
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
intcode.set(arguments[0].address, intcode.get_input())
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class OutputInstruction(Instruction):
|
||||
code, argument_num = 4, 1
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
intcode.output = arguments[0].value
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class JumpIfTrueInstruction(Instruction):
|
||||
code, argument_num = 5, 2
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
logging.debug(f"JMPT {arguments[0].value} {arguments[1].value}")
|
||||
return arguments[1].value if arguments[0].value != 0 else self.new_pc(intcode)
|
||||
|
||||
|
||||
class JumpIfFalseInstruction(Instruction):
|
||||
code, argument_num = 6, 2
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
logging.debug(f"JMPF {arguments[0].value} {arguments[1].value}")
|
||||
return arguments[1].value if arguments[0].value == 0 else self.new_pc(intcode)
|
||||
|
||||
|
||||
class LessThanInstruction(Instruction):
|
||||
code, argument_num = 7, 3
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
logging.debug(f"LT {arguments[0].value} {arguments[1].value}")
|
||||
intcode.set(arguments[2].address, int(arguments[0].value < arguments[1].value))
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class EqualsInstruction(Instruction):
|
||||
code, argument_num = 8, 3
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
intcode.set(arguments[2].address, int(arguments[0].value == arguments[1].value))
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class RelativeBaseOffsetInstruction(Instruction):
|
||||
code, argument_num = 9, 1
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
intcode.relative_base += arguments[0].value
|
||||
return self.new_pc(intcode)
|
||||
|
||||
|
||||
class HaltInstruction(Instruction):
|
||||
code, argument_num = 99, 0
|
||||
|
||||
def execute(self, intcode, arguments):
|
||||
intcode.halted = True
|
||||
return None
|
||||
|
||||
|
||||
class Argument:
|
||||
def __init__(self, value, address):
|
||||
self.value = value
|
||||
self.address = address
|
||||
|
||||
|
||||
class IntCode:
|
||||
def __init__(self, program, inputs=[], input_func=None):
|
||||
self.program = program[:]
|
||||
self.inputs = inputs[::-1]
|
||||
self.input_func = input_func
|
||||
self.relative_base = 0
|
||||
self.memory = {}
|
||||
self.output = None
|
||||
self.halted = False
|
||||
self.pc = 0
|
||||
|
||||
def _get_instruction(self, instruction_code):
|
||||
return next(
|
||||
cls for cls in Instruction.__subclasses__() if cls.code == instruction_code
|
||||
)
|
||||
|
||||
def _parse_arguments(self, argument_num):
|
||||
modes = str(self.program[self.pc]).zfill(5)[:3][::-1]
|
||||
arguments = []
|
||||
for i in range(argument_num):
|
||||
value = self.program[self.pc + i + 1] + (
|
||||
self.relative_base if modes[i] == "2" else 0
|
||||
)
|
||||
if modes[i] == "1":
|
||||
arguments.append(Argument(value, value))
|
||||
else:
|
||||
arguments.append(
|
||||
Argument(
|
||||
self.program[value]
|
||||
if value < len(self.program)
|
||||
else self.memory.get(value, 0),
|
||||
value,
|
||||
)
|
||||
)
|
||||
return arguments
|
||||
|
||||
def run(self):
|
||||
self.output = None
|
||||
while not self.halted and self.output is None:
|
||||
instruction = self._get_instruction(self.program[self.pc] % 100)
|
||||
arguments = self._parse_arguments(instruction.argument_num)
|
||||
self.pc = instruction().execute(self, arguments)
|
||||
return self.output
|
||||
|
||||
def execute(self):
|
||||
last_output = None
|
||||
while not self.halted:
|
||||
output = self.run()
|
||||
if not self.halted:
|
||||
last_output = output
|
||||
return last_output
|
||||
|
||||
def clone(self):
|
||||
cloned = IntCode(self.program)
|
||||
cloned.inputs = self.inputs[:]
|
||||
cloned.input_func = self.input_func
|
||||
cloned.relative_base = self.relative_base
|
||||
cloned.memory = {key: value for key, value in self.memory.items()}
|
||||
cloned.output = self.output
|
||||
cloned.halted = self.halted
|
||||
cloned.pc = self.pc
|
||||
return cloned
|
||||
|
||||
def get(self, address):
|
||||
return (
|
||||
self.program[address]
|
||||
if address < len(self.program)
|
||||
else self.memory.get(value + self.relative_base, 0)
|
||||
)
|
||||
|
||||
def set(self, address, value):
|
||||
target = self.program if address < len(self.program) else self.memory
|
||||
target[address] = value
|
||||
|
||||
def input(self, value):
|
||||
self.inputs = [value] + self.inputs
|
||||
|
||||
def get_input(self):
|
||||
return self.inputs.pop() if self.inputs else self.input_func()
|
||||
|
||||
|
||||
import os
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
code = list(map(int, open(os.path.join(SCRIPT_DIR, "input.txt")).read().split(",")))
|
||||
|
||||
part2 = IntCode(code, [5]).execute()
|
||||
print(part2)
|
46
adventofcode/2019/day6/day6.py
Normal file
46
adventofcode/2019/day6/day6.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def path(graph, start, goal):
|
||||
path = []
|
||||
node = start
|
||||
while node != goal:
|
||||
node = graph[node]
|
||||
path.append(node)
|
||||
return path
|
||||
|
||||
|
||||
def find_first_common(x, y):
|
||||
"returns the indices i and j of the first common element of x and y"
|
||||
for i, xx in enumerate(x):
|
||||
for j, yy in enumerate(y):
|
||||
if xx == yy:
|
||||
return i, j
|
||||
|
||||
|
||||
def main(inp):
|
||||
graph = {}
|
||||
for l in inp:
|
||||
left, right = l.rstrip().split(")")
|
||||
graph[right] = left
|
||||
|
||||
total = 0
|
||||
for node, child in graph.items():
|
||||
cnt = 1
|
||||
while child != "COM":
|
||||
child = graph[child]
|
||||
cnt += 1
|
||||
total += cnt
|
||||
print("Part 1: ", total)
|
||||
|
||||
p1 = path(graph, "YOU", "COM")
|
||||
p2 = path(graph, "SAN", "COM")
|
||||
a, b = find_first_common(p1, p2)
|
||||
total_path = p1[:a] + p2[b::-1] # p1 + p2 - p1 n p2
|
||||
print("Part 2: ", len(total_path) - 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
93
adventofcode/2019/day7/day7.py
Normal file
93
adventofcode/2019/day7/day7.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode"))
|
||||
|
||||
from itertools import cycle, permutations
|
||||
from intcode import interpret_intcode, Interpreter
|
||||
|
||||
def main(inp):
|
||||
mem = list(map(int, inp.readline().rstrip().split(",")))
|
||||
max_ret = 0
|
||||
for seq in permutations([0, 1, 2, 3, 4], 5):
|
||||
ret = amplifiers(mem, list(seq))
|
||||
max_ret = max(ret.stdout[0], max_ret)
|
||||
print("Part 1: ", max_ret)
|
||||
|
||||
max_ret = 0
|
||||
for seq in permutations((5, 6, 7, 8, 9)):
|
||||
ret = part2(mem, list(seq))
|
||||
max_ret = max(max_ret, ret)
|
||||
print("Part 2: ", max_ret)
|
||||
|
||||
|
||||
def amplifiers(program, sequence):
|
||||
ret = interpret_intcode(program[::], [sequence.pop(0), 0])
|
||||
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
|
||||
|
||||
|
||||
def part2(program, sequence):
|
||||
amplifiers = [Interpreter(program[::], [sequence.pop(0)]) for _ in range(5)]
|
||||
it = cycle(enumerate(amplifiers))
|
||||
|
||||
id_, amp = next(it)
|
||||
inp = 0
|
||||
max_signal = 0
|
||||
while True:
|
||||
max_signal = max(max_signal, inp)
|
||||
amp.stdin.append(inp)
|
||||
amp.interpret()
|
||||
out = amp.stdout
|
||||
if amp.halted:
|
||||
break
|
||||
next_id, next_amp = next(it)
|
||||
inp = out.pop(0)
|
||||
amp = next_amp
|
||||
id_= next_id
|
||||
return max_signal
|
||||
|
||||
|
||||
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.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.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.stdout == [65210]
|
||||
|
||||
|
||||
def tests2():
|
||||
program = [3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,
|
||||
27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5]
|
||||
sequence = [9,8,7,6,5]
|
||||
assert part2(program, sequence) == 139629729
|
||||
|
||||
|
||||
program = [3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,
|
||||
-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,
|
||||
53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10]
|
||||
sequence = [9,7,8,5,6]
|
||||
assert part2(program, sequence) == 18216
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
tests()
|
||||
tests2()
|
||||
main(fileinput.input())
|
||||
|
39
adventofcode/2019/day8/day8.py
Normal file
39
adventofcode/2019/day8/day8.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from itertools import zip_longest
|
||||
|
||||
|
||||
def grouper(n, iterable):
|
||||
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
|
||||
args = [iter(iterable)] * n
|
||||
return zip_longest(*args)
|
||||
|
||||
|
||||
def main(inp):
|
||||
data = inp.readline().rstrip()
|
||||
width, height = 25, 6
|
||||
layers = [x for x in grouper(width*height, data)]
|
||||
layer_i, _ = min(((i, x.count("0")) for i, x in enumerate(layers)), key=lambda x: x[1])
|
||||
ones = layers[layer_i].count("1")
|
||||
twos = layers[layer_i].count("2")
|
||||
print("Part 1: ", ones * twos)
|
||||
|
||||
image = ["2"] * width * height
|
||||
layers = [x for x in grouper(width*height, data)]
|
||||
layer_i, _ = min(((i, x.count("0")) for i, x in enumerate(layers)), key=lambda x: x[1])
|
||||
for l in layers[::-1]:
|
||||
# 0 is black, 1 is white, and 2 is transparent.
|
||||
for i, x in enumerate(l):
|
||||
if x == "2":
|
||||
continue
|
||||
image[i] = x
|
||||
|
||||
print("Part 2:")
|
||||
for i in range(height):
|
||||
b, w = " ", "\u2588"
|
||||
im = [b if x == "0" else w for x in image[width*i:width*(i+1)]]
|
||||
print("".join(im))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
20
adventofcode/2019/day9/day9.py
Normal file
20
adventofcode/2019/day9/day9.py
Normal 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
|
||||
|
||||
|
||||
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
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