From c6c3acaa9a873abd3132b547a32eec8b246eb1a3 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Tue, 3 Dec 2024 15:26:47 +0100 Subject: [PATCH] import 2019 aoc --- .gitignore | 131 ++++++++++++++++++++++++++++++ 2019/day1/day1.py | 41 ++++++++++ 2019/day1/input.txt | 100 +++++++++++++++++++++++ 2019/day2/day2.py | 69 ++++++++++++++++ 2019/day2/input.txt | 1 + 2019/day3/day3.py | 78 ++++++++++++++++++ 2019/day3/input.txt | 2 + 2019/day4/day4.py | 37 +++++++++ 2019/day5/day5.py | 163 +++++++++++++++++++++++++++++++++++++ 2019/day5/day52.py | 192 ++++++++++++++++++++++++++++++++++++++++++++ 2019/day5/input.txt | 1 + 11 files changed, 815 insertions(+) create mode 100644 .gitignore create mode 100644 2019/day1/day1.py create mode 100644 2019/day1/input.txt create mode 100644 2019/day2/day2.py create mode 100644 2019/day2/input.txt create mode 100644 2019/day3/day3.py create mode 100644 2019/day3/input.txt create mode 100644 2019/day4/day4.py create mode 100644 2019/day5/day5.py create mode 100644 2019/day5/day52.py create mode 100644 2019/day5/input.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13d1490 --- /dev/null +++ b/.gitignore @@ -0,0 +1,131 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + diff --git a/2019/day1/day1.py b/2019/day1/day1.py new file mode 100644 index 0000000..55009a3 --- /dev/null +++ b/2019/day1/day1.py @@ -0,0 +1,41 @@ +#! /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__": + print("Part 1 - total mass: ", calculate_total_fuel_mass("./input.txt")) + + test_part2() + print( + "Part 2 -- total mass: ", + calculate_total_fuel_mass("./input.txt", calculate_fuel_iterative), + ) diff --git a/2019/day1/input.txt b/2019/day1/input.txt new file mode 100644 index 0000000..ab07505 --- /dev/null +++ b/2019/day1/input.txt @@ -0,0 +1,100 @@ +62259 +75368 +93740 +119724 +112546 +137714 +96999 +130673 +102398 +73819 +100734 +85337 +62764 +82115 +127696 +54391 +103213 +77954 +112513 +112392 +138404 +92989 +108521 +83163 +109720 +91918 +114443 +54306 +90623 +66833 +58505 +85919 +77539 +149419 +128385 +66452 +94677 +109179 +62072 +137245 +136226 +145783 +60689 +103320 +145931 +101286 +63458 +122468 +87858 +105675 +146185 +57417 +96883 +70739 +97494 +140951 +149416 +83137 +66122 +134319 +58511 +139600 +102929 +112240 +149634 +64142 +83332 +129526 +99058 +148889 +50087 +74961 +133606 +143518 +68849 +97045 +73920 +61357 +115941 +56740 +111773 +77880 +90792 +77103 +111355 +125898 +56547 +84918 +113822 +74113 +98557 +80928 +60519 +146379 +59354 +102490 +72584 +59000 +63151 +114253 diff --git a/2019/day2/day2.py b/2019/day2/day2.py new file mode 100644 index 0000000..7913f2e --- /dev/null +++ b/2019/day2/day2.py @@ -0,0 +1,69 @@ +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() + with open("input.txt") 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 diff --git a/2019/day2/input.txt b/2019/day2/input.txt new file mode 100644 index 0000000..8a0df07 --- /dev/null +++ b/2019/day2/input.txt @@ -0,0 +1 @@ +1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,1,13,19,1,9,19,23,1,6,23,27,2,27,9,31,2,6,31,35,1,5,35,39,1,10,39,43,1,43,13,47,1,47,9,51,1,51,9,55,1,55,9,59,2,9,59,63,2,9,63,67,1,5,67,71,2,13,71,75,1,6,75,79,1,10,79,83,2,6,83,87,1,87,5,91,1,91,9,95,1,95,10,99,2,9,99,103,1,5,103,107,1,5,107,111,2,111,10,115,1,6,115,119,2,10,119,123,1,6,123,127,1,127,5,131,2,9,131,135,1,5,135,139,1,139,10,143,1,143,2,147,1,147,5,0,99,2,0,14,0 diff --git a/2019/day3/day3.py b/2019/day3/day3.py new file mode 100644 index 0000000..7188378 --- /dev/null +++ b/2019/day3/day3.py @@ -0,0 +1,78 @@ +#!/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() + + with open("input.txt") 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)) diff --git a/2019/day3/input.txt b/2019/day3/input.txt new file mode 100644 index 0000000..ef1fbc2 --- /dev/null +++ b/2019/day3/input.txt @@ -0,0 +1,2 @@ +R997,U849,R349,U641,R581,D39,R285,U139,R455,D346,L965,D707,R393,D302,L263,U58,R950,U731,R858,D748,R302,U211,R588,D441,L153,D417,R861,U775,R777,U204,R929,U868,L62,U163,R841,D214,L648,U626,R501,D751,L641,D961,L23,D430,L73,D692,R49,U334,L601,U996,R444,D658,R633,D30,L861,D811,R10,D394,R9,U227,L848,U420,L378,D622,L501,U397,R855,U369,R615,U591,L674,D166,L181,U61,L224,U463,L203,U594,R93,U614,L959,U198,L689,D229,L674,U255,R843,D382,R538,U923,L960,D775,L879,U97,R137,U665,L340,D941,L775,D57,R852,D167,R980,U704,L843,U989,L611,D32,L724,D790,L32,U984,L39,U671,L994,U399,R475,D85,L322,D936,R117,D261,R705,D696,L523,D433,L239,U477,L247,D465,R560,D902,L589,U682,R645,U376,L989,D121,L215,U514,R519,U407,L218,D444,R704,D436,L680,U759,R937,U400,R533,D860,R782,D233,R840,D549,L508,U380,L992,U406,L213,D403,L413,D532,L429,U186,R262,U313,L913,U873,L838,D882,R851,U70,R185,D131,R945,D595,L330,U446,R88,D243,L561,D952,R982,D395,L708,U459,L82,D885,L996,U955,L406,U697,L183,U266,L878,D839,R843,D891,R118,U772,R590,D376,L500,U370,R607,D12,L310,D436,L602,D365,R886,U239,L471,D418,L122,U18,R879,D693,R856,U848,L657,D911,L63,U431,R41,U752,R919,U323,L61,D263,L370,D85,R929,D213,R350,U818,R458,D912,R509,U394,L734,U49,R810,D87,L870,U658,R499,U550,L402,U244,L112,U859,R836,U951,R222,D944,L691,D731,R742,D52,R984,D453,L514,U692,R812,U35,L844,D177,L110,D22,R61,U253,R618,D51,R163,U835,R704,U148,R766,U297,R457,D170,L104,D441,R330,D330,R989,D538,R668,D811,R62,D67,L470,D526,R788,U376,R708,U3,R961 +L1009,D381,R970,U429,L230,D909,R516,D957,R981,U609,L480,D139,L861,U168,L48,U620,R531,D466,L726,D380,R977,D454,L318,D397,R994,U402,L77,U93,L359,D72,R968,D956,L174,D22,R218,U619,R593,U32,L154,U55,L169,U415,L171,U666,R617,U109,L265,U773,R541,D808,L797,U478,R731,U379,R311,D137,L806,U298,R362,D458,L254,D539,R700,U853,R246,D588,L28,U203,L432,U946,R663,D408,R974,U59,L683,D36,L139,U738,L780,U414,L401,D93,R212,D973,L710,U892,L357,D177,R823,D4,R46,D924,L235,D898,R67,U220,L592,U87,R94,U584,R979,D843,L299,D648,L491,U360,R824,D245,L944,D24,R616,U975,L4,U42,L984,U181,R902,D835,L687,D413,L767,U632,L754,U270,R413,U51,L825,D377,L596,U960,L378,U706,L859,D708,L156,D991,L814,U351,R923,D749,L16,D651,R20,D86,R801,U811,L228,U161,L871,U129,R215,U235,L784,U896,R94,U145,R822,U494,R248,D98,R494,U156,L495,U311,R66,D873,L294,D620,L885,U395,R778,D227,R966,U756,L694,D707,R983,D950,R706,D730,R415,U886,L465,D622,L13,D938,R324,D464,R723,U804,R942,D635,L729,D317,L522,U469,R550,D141,R302,U999,L642,U509,R431,D380,R18,D676,R449,D759,L495,U901,R1,D745,L655,U449,L439,D818,R55,D541,R420,U764,L426,D176,L520,U3,L663,D221,L80,D449,L987,U349,L71,U632,L887,D231,R655,D208,R698,D639,R804,U616,R532,U846,R363,D141,R659,U470,L798,U144,L675,U483,L944,U380,L329,U72,L894,D130,R53,U109,R610,U770,R778,U493,R972,D340,L866,U980,L305,D812,R130,D954,R253,D33,L912,U950,L438,D680,R891,U725,R171,D587,R549,D367,L4,U313,R522,D128,L711,D405,L769,D496,L527,U373,R725,D261,L268,D939,L902,D58,L858,D190,L442 \ No newline at end of file diff --git a/2019/day4/day4.py b/2019/day4/day4.py new file mode 100644 index 0000000..360be68 --- /dev/null +++ b/2019/day4/day4.py @@ -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)) diff --git a/2019/day5/day5.py b/2019/day5/day5.py new file mode 100644 index 0000000..02254fb --- /dev/null +++ b/2019/day5/day5.py @@ -0,0 +1,163 @@ +#! /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) + + 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): + ip += 2 + program[program[ip + 1]] = int(input("Enter ID > ")) + return ip + + def handle_output(self, program, ip): + print("OUT ", program[program[ip + 1]]) + 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=None): + ip = 0 + while program[ip] != 99: + opcode = program[ip] + instruction = Instruction(opcode) + ip = instruction.handle(program, ip) + + +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." + print("\nAll tests passed.\n") + + +def run_input_program(filename): + import os + + SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(SCRIPT_DIR, filename)) as inp: + print("Start of input program.") + memory = [int(x) for x in inp.readline().strip().split(",")] + interpret_intcode(memory, 5) + + +if __name__ == "__main__": + tests() + # a = [3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9] + # b = [3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1] + + # interpret_intcode(b, 0) + # interpret_intcode(a, 0) + run_input_program("input.txt") diff --git a/2019/day5/day52.py b/2019/day5/day52.py new file mode 100644 index 0000000..d31af56 --- /dev/null +++ b/2019/day5/day52.py @@ -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) diff --git a/2019/day5/input.txt b/2019/day5/input.txt new file mode 100644 index 0000000..425efbc --- /dev/null +++ b/2019/day5/input.txt @@ -0,0 +1 @@ +3,225,1,225,6,6,1100,1,238,225,104,0,1102,67,92,225,1101,14,84,225,1002,217,69,224,101,-5175,224,224,4,224,102,8,223,223,101,2,224,224,1,224,223,223,1,214,95,224,101,-127,224,224,4,224,102,8,223,223,101,3,224,224,1,223,224,223,1101,8,41,225,2,17,91,224,1001,224,-518,224,4,224,1002,223,8,223,101,2,224,224,1,223,224,223,1101,37,27,225,1101,61,11,225,101,44,66,224,101,-85,224,224,4,224,1002,223,8,223,101,6,224,224,1,224,223,223,1102,7,32,224,101,-224,224,224,4,224,102,8,223,223,1001,224,6,224,1,224,223,223,1001,14,82,224,101,-174,224,224,4,224,102,8,223,223,101,7,224,224,1,223,224,223,102,65,210,224,101,-5525,224,224,4,224,102,8,223,223,101,3,224,224,1,224,223,223,1101,81,9,224,101,-90,224,224,4,224,102,8,223,223,1001,224,3,224,1,224,223,223,1101,71,85,225,1102,61,66,225,1102,75,53,225,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,8,226,226,224,102,2,223,223,1005,224,329,1001,223,1,223,1108,677,677,224,1002,223,2,223,1006,224,344,101,1,223,223,1007,226,677,224,102,2,223,223,1005,224,359,101,1,223,223,1007,677,677,224,1002,223,2,223,1006,224,374,101,1,223,223,1108,677,226,224,1002,223,2,223,1005,224,389,1001,223,1,223,108,226,677,224,102,2,223,223,1006,224,404,101,1,223,223,1108,226,677,224,102,2,223,223,1005,224,419,101,1,223,223,1008,677,677,224,102,2,223,223,1005,224,434,101,1,223,223,7,677,226,224,1002,223,2,223,1005,224,449,101,1,223,223,1008,226,226,224,102,2,223,223,1005,224,464,1001,223,1,223,107,226,677,224,1002,223,2,223,1006,224,479,1001,223,1,223,107,677,677,224,102,2,223,223,1005,224,494,1001,223,1,223,1008,226,677,224,102,2,223,223,1006,224,509,1001,223,1,223,1107,677,226,224,102,2,223,223,1005,224,524,101,1,223,223,1007,226,226,224,1002,223,2,223,1006,224,539,1001,223,1,223,107,226,226,224,102,2,223,223,1006,224,554,101,1,223,223,108,677,677,224,1002,223,2,223,1006,224,569,1001,223,1,223,7,226,677,224,102,2,223,223,1006,224,584,1001,223,1,223,8,677,226,224,102,2,223,223,1005,224,599,101,1,223,223,1107,677,677,224,1002,223,2,223,1005,224,614,101,1,223,223,8,226,677,224,102,2,223,223,1005,224,629,1001,223,1,223,7,226,226,224,1002,223,2,223,1006,224,644,1001,223,1,223,108,226,226,224,1002,223,2,223,1006,224,659,101,1,223,223,1107,226,677,224,1002,223,2,223,1006,224,674,101,1,223,223,4,223,99,226 \ No newline at end of file