chore: create new project structure and aoc.py runner script

This commit is contained in:
2025-08-04 16:23:06 +02:00
parent f76375d835
commit e2964c6c36
91 changed files with 177 additions and 113 deletions

View File

View 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),
)

View 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

View File

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

View 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

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

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

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

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

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

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

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

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

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

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

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