├── .gitignore ├── README.md ├── day01 ├── day01.py └── input.txt ├── day02 └── day02.py ├── day03 ├── day03.py └── day03_2.py ├── day04 └── day04.py ├── day05 ├── day05.py └── intcode.py ├── day06 ├── day06.py └── input.txt ├── day07 ├── amplifier.py ├── day07.py └── day07_part2.py ├── day08 ├── day08.py └── day08.txt ├── day09 └── day09.py ├── day10 └── day10.py ├── day11 └── day11.py ├── day12 └── day12.py ├── day13 ├── day13.py ├── day13.txt ├── day13_optimized.py └── intcode.py ├── day14 ├── day14.py └── day14.txt ├── day15 ├── day15.py └── day15_copy.py ├── day16 └── day16.py ├── day17 ├── Untitled-1 ├── day17.py └── day17.txt ├── day18 ├── day18.py ├── day18.txt ├── day18_part2.py └── day18_part2.txt ├── day19 └── day19.py ├── day20 ├── day20.py ├── day20.txt └── day20_part2.py ├── day21 ├── day21.py ├── day21.scratchwork └── day21.scratchwork2 ├── day22 ├── day22.py ├── day22.txt └── day22_part2.py ├── day23 ├── day23.py └── day23_part2.py ├── day24 ├── day24.py └── day24_part2.py └── day25 ├── day25.py ├── day25.txt └── gen.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advent2019 2 | advent of code 2019 3 | -------------------------------------------------------------------------------- /day01/day01.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fuel required to launch a given module is based on its mass. 3 | Specifically, to find the fuel required for a module, take its mass, 4 | divide by three, round down, and subtract 2. 5 | 6 | For example: 7 | 8 | For a mass of 12, divide by 3 and round down to get 4, then subtract 2 to get 2. 9 | For a mass of 14, dividing by 3 and rounding down still yields 4, so the fuel required is also 2. 10 | For a mass of 1969, the fuel required is 654. 11 | For a mass of 100756, the fuel required is 33583. 12 | """ 13 | def fuel(mass: int) -> int: 14 | return mass // 3 - 2 15 | 16 | assert fuel(12) == 2 17 | assert fuel(14) == 2 18 | assert fuel(1969) == 654 19 | assert fuel(100756) == 33583 20 | 21 | with open('input.txt') as f: 22 | masses = [int(line.strip()) for line in f] 23 | part1 = sum(fuel(mass) for mass in masses) 24 | 25 | print(part1) 26 | 27 | # For part2 we want to add fuel for the fuel, etc 28 | 29 | def fuel_for_the_fuel(mass: int) -> int: 30 | """We need fuel for the fuel, fuel for that fuel, etc""" 31 | total = 0 32 | 33 | next_fuel = fuel(mass) 34 | 35 | while next_fuel > 0: 36 | total += next_fuel 37 | next_fuel = fuel(next_fuel) 38 | 39 | return total 40 | 41 | assert fuel_for_the_fuel(14) == 2 42 | assert fuel_for_the_fuel(1969) == 966 43 | assert fuel_for_the_fuel(100756) == 50346 44 | 45 | part2 = sum(fuel_for_the_fuel(mass) for mass in masses) 46 | print(part2) -------------------------------------------------------------------------------- /day01/input.txt: -------------------------------------------------------------------------------- 1 | 109364 2 | 144584 3 | 87498 4 | 130293 5 | 91960 6 | 117563 7 | 91730 8 | 138879 9 | 144269 10 | 89058 11 | 89982 12 | 115609 13 | 114728 14 | 85422 15 | 111803 16 | 148524 17 | 130035 18 | 107558 19 | 138936 20 | 95622 21 | 58042 22 | 50697 23 | 86848 24 | 123301 25 | 123631 26 | 143125 27 | 76434 28 | 78004 29 | 91115 30 | 89062 31 | 58465 32 | 141127 33 | 139993 34 | 80958 35 | 104184 36 | 145131 37 | 87438 38 | 74385 39 | 102113 40 | 97392 41 | 105986 42 | 58600 43 | 147156 44 | 54377 45 | 61409 46 | 73552 47 | 87138 48 | 63168 49 | 149602 50 | 111776 51 | 113191 52 | 80137 53 | 145985 54 | 145177 55 | 73192 56 | 141631 57 | 132979 58 | 52565 59 | 126574 60 | 92005 61 | 134655 62 | 115894 63 | 89175 64 | 127328 65 | 139873 66 | 50072 67 | 78814 68 | 134750 69 | 120848 70 | 132950 71 | 126523 72 | 58206 73 | 70885 74 | 85482 75 | 70889 76 | 100029 77 | 68447 78 | 95111 79 | 79896 80 | 138650 81 | 83079 82 | 83112 83 | 117762 84 | 57223 85 | 138122 86 | 145193 87 | 85251 88 | 103331 89 | 134501 90 | 77023 91 | 148189 92 | 141341 93 | 75994 94 | 67024 95 | 137767 96 | 86260 97 | 58705 98 | 58771 99 | 60684 100 | 79655 -------------------------------------------------------------------------------- /day02/day02.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | Program = List[int] 4 | 5 | def run(program: Program) -> None: 6 | pos = 0 7 | 8 | while program[pos] != 99: # halt 9 | opcode, loc1, loc2, loc3 = program[pos], program[pos + 1], program[pos + 2], program[pos + 3] 10 | if opcode == 1: 11 | program[loc3] = program[loc1] + program[loc2] 12 | elif opcode == 2: 13 | program[loc3] = program[loc1] * program[loc2] 14 | else: 15 | raise RuntimeError(f"invalid opcode: {program[pos]}") 16 | 17 | pos += 4 18 | 19 | prog1 = [1,0,0,0,99]; run(prog1); assert prog1 == [2,0,0,0,99] 20 | prog2 = [2,3,0,3,99]; run(prog2); assert prog2 == [2,3,0,6,99] 21 | prog3 = [2,4,4,5,99,0]; run(prog3); assert prog3 == [2,4,4,5,99,9801] 22 | prog4 = [1,1,1,4,99,5,6,0,99]; run(prog4); assert prog4 == [30,1,1,4,2,5,6,0,99] 23 | 24 | def alarm(program: Program, noun: int = 12, verb: int = 2) -> int: 25 | program = program[:] 26 | program[1] = noun 27 | program[2] = verb 28 | run(program) 29 | return program[0] 30 | 31 | PROGRAM = [1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,6,1,19,1,19,5,23,2,10,23,27,2,27,13,31,1,10,31,35,1,35,9,39,2,39,13,43,1,43,5,47,1,47,6,51,2,6,51,55,1,5,55,59,2,9,59,63,2,6,63,67,1,13,67,71,1,9,71,75,2,13,75,79,1,79,10,83,2,83,9,87,1,5,87,91,2,91,6,95,2,13,95,99,1,99,5,103,1,103,2,107,1,107,10,0,99,2,0,14,0] 32 | 33 | # print(alarm(PROGRAM)) 34 | 35 | TARGET = 19690720 36 | 37 | for noun in range(100): 38 | for verb in range(100): 39 | output = alarm(PROGRAM, noun, verb) 40 | if output == TARGET: 41 | print(noun, verb, 100 * noun + verb) 42 | break -------------------------------------------------------------------------------- /day03/day03.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Set 2 | 3 | class XY(NamedTuple): 4 | x: int 5 | y: int 6 | 7 | 8 | def locations(path: str) -> Set[XY]: 9 | # R8,U5,L5,D3 10 | x = y = 0 11 | 12 | visited = set() 13 | 14 | for line in path.split(","): 15 | direction = line[0] 16 | distance = int(line[1:]) 17 | 18 | for _ in range(distance): 19 | if direction == "U": 20 | y += 1 21 | elif direction == "D": 22 | y -= 1 23 | elif direction == "R": 24 | x += 1 25 | elif direction == "L": 26 | x -= 1 27 | else: 28 | raise RuntimeError(f"bad direction: {direction}") 29 | 30 | visited.add(XY(x, y)) 31 | 32 | return visited 33 | 34 | def all_intersections(path1: str, path2: str) -> Set[XY]: 35 | locations1 = locations(path1) 36 | locations2 = locations(path2) 37 | return locations1.intersection(locations2) 38 | 39 | def manhattan_distance(xy: XY) -> int: 40 | return abs(xy.x) + abs(xy.y) 41 | 42 | def closest_intersection(path1: str, path2: str) -> int: 43 | ai = all_intersections(path1, path2) 44 | return min(manhattan_distance(i) for i in ai) 45 | 46 | assert closest_intersection("R8,U5,L5,D3", "U7,R6,D4,L4") == 6 47 | assert closest_intersection("R75,D30,R83,U83,L12,D49,R71,U7,L72", 48 | "U62,R66,U55,R34,D71,R55,D58,R83") == 159 49 | assert closest_intersection("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51", 50 | "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7") == 135 51 | 52 | PATH1 = "R994,U598,L555,D997,R997,U529,L251,U533,R640,U120,L813,U927,L908,U214,L276,U306,L679,U187,R156,D654,L866,D520,R299,U424,R683,U49,R965,U531,R303,D4,L210,U425,R99,D892,R564,D671,L294,D908,L89,U855,R275,U790,R214,D588,L754,D873,R297,D97,R979,U850,L953,D281,L580,D254,L747,U115,L996,U641,R976,U585,L383,U498,L112,U329,R650,U772,L952,U325,L861,U831,R71,D853,R696,D812,R389,U456,L710,D116,R789,D829,L57,D940,R908,U569,R617,D832,L492,D397,R152,U898,L960,D806,L867,U928,L617,D281,L516,D214,R426,U530,R694,U774,L752,U215,L930,U305,R463,U774,R234,U786,R425,U470,R90,D383,R692,D626,L160,D588,L141,D351,R574,D237,L869,D499,R873,U856,R148,D919,L582,D804,L413,U201,L247,U907,L828,D279,L28,D950,L587,U290,R636,U344,L591,U118,L614,U203,R381,U634,L301,D197,R594,D373,L459,U504,L703,U852,L672,U613,R816,D712,R813,U97,R824,D690,L556,D308,L568,D924,L384,U540,R745,D679,R705,D808,L346,U927,R145,U751,L769,D152,L648,D553,L738,U456,R864,U486,R894,D923,R76,U211,L78,U145,R977,U297,R93,U200,L71,U665,L392,D309,L399,D594,R118,U552,L328,U317,R369,D109,L673,D306,R441,U836,L305,D59,L870,U648,L817,D381,R676,U711,R115,U344,L815,U286,R194,U526,R844,U106,L547,D312,L116,U783,R786,D390,L115,D483,R691,U802,R569,U13,R854,D90,R22,D819,L440,D13,R438,D640,L952,D394,R984,D825,R1,D554,R349,U746,L816,U301,L397,D85,R437,D746,L698,D75,L964,U155,L268,U612,R838,D338,L188,U38,R830,U538,L245,D885,R194,D989,R8,D69,L268,D677,R163,U784,L308,U605,L737,U919,R117,U449,R698,U547,L134,D860,L234,U923,R495,D55,R954,D531,L212" 53 | PATH2 = "L1005,D937,L260,D848,R640,U358,R931,U495,R225,U344,R595,U754,L410,D5,R52,D852,L839,D509,R755,D983,R160,U522,R795,D465,R590,U558,R552,U332,R330,U752,R860,D503,L456,U254,R878,D164,R991,U569,R44,U112,L258,U168,L552,U68,R414,U184,R458,D58,R319,U168,R501,D349,R204,D586,R241,U575,L981,D819,L171,D811,L960,U495,R192,D725,R718,D346,R399,D692,L117,D215,L390,U364,L700,D207,R372,U767,L738,D844,L759,D211,R287,U964,R328,D800,R823,U104,L524,D68,R714,D633,R565,D373,R883,U327,R222,D318,L58,D451,R555,D687,R807,U638,L717,U298,R849,D489,L159,D692,L136,U242,R884,U202,R419,U41,L980,U483,R966,D513,L870,D306,R171,D585,R71,D320,R914,U991,R706,U440,R542,D219,L969,U9,R481,U164,R919,U17,L750,U775,R173,U515,L191,D548,L515,U54,L132,U56,R203,U544,L796,D508,L321,D517,L358,U12,L892,D472,L378,U121,L974,U36,R56,D758,L680,D17,L369,D72,L926,D466,L866,U850,R300,D597,L848,U17,L890,D739,L275,U560,L640,U602,R238,U919,R636,D188,R910,D992,L13,U241,R77,U857,R453,U883,L881,D267,R28,U928,R735,U731,L701,D795,R371,U652,R416,D129,R142,D30,R442,U513,R827,U455,L429,D804,R966,D565,R326,U398,R621,U324,L684,D235,L467,D575,L200,D442,R320,D550,R278,U929,R555,U537,L416,U98,R991,D271,L764,U841,L273,D782,R356,D447,R340,U413,R543,U260,L365,D529,R721,U542,L648,U366,R494,U243,L872,U201,L440,U232,R171,D608,R282,U484,R81,D320,R274,D760,L250,U749,L132,D162,L340,D308,L149,D5,L312,U547,R686,D684,R133,D876,L531,U572,R62,D142,L218,U703,L884,U64,L889,U887,R228,U534,R624,D524,R522,D452,L550,U959,R981,U139,R35,U98,R212" 54 | 55 | print(closest_intersection(PATH1, PATH2)) -------------------------------------------------------------------------------- /day03/day03_2.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, Set, Dict 2 | 3 | class XY(NamedTuple): 4 | x: int 5 | y: int 6 | 7 | 8 | def locations(path: str) -> Dict[XY, int]: 9 | """ 10 | return the minimum steps to each location 11 | """ 12 | x = y = num_steps = 0 13 | 14 | visited = {} 15 | 16 | for line in path.split(","): 17 | direction = line[0] 18 | distance = int(line[1:]) 19 | 20 | for _ in range(distance): 21 | num_steps += 1 22 | 23 | if direction == "U": 24 | y += 1 25 | elif direction == "D": 26 | y -= 1 27 | elif direction == "R": 28 | x += 1 29 | elif direction == "L": 30 | x -= 1 31 | else: 32 | raise RuntimeError(f"bad direction: {direction}") 33 | 34 | loc = XY(x, y) 35 | if loc not in visited: 36 | visited[loc] = num_steps 37 | 38 | return visited 39 | 40 | def closest_intersection(path1: str, path2: str) -> int: 41 | distances1 = locations(path1) 42 | distances2 = locations(path2) 43 | intersections = set(distances1).intersection(set(distances2)) 44 | 45 | return min(distances1[loc] + distances2[loc] for loc in intersections) 46 | 47 | assert closest_intersection("R8,U5,L5,D3", "U7,R6,D4,L4") == 30 48 | assert closest_intersection("R75,D30,R83,U83,L12,D49,R71,U7,L72", 49 | "U62,R66,U55,R34,D71,R55,D58,R83") == 610 50 | assert closest_intersection("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51", 51 | "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7") == 410 52 | 53 | PATH1 = "R994,U598,L555,D997,R997,U529,L251,U533,R640,U120,L813,U927,L908,U214,L276,U306,L679,U187,R156,D654,L866,D520,R299,U424,R683,U49,R965,U531,R303,D4,L210,U425,R99,D892,R564,D671,L294,D908,L89,U855,R275,U790,R214,D588,L754,D873,R297,D97,R979,U850,L953,D281,L580,D254,L747,U115,L996,U641,R976,U585,L383,U498,L112,U329,R650,U772,L952,U325,L861,U831,R71,D853,R696,D812,R389,U456,L710,D116,R789,D829,L57,D940,R908,U569,R617,D832,L492,D397,R152,U898,L960,D806,L867,U928,L617,D281,L516,D214,R426,U530,R694,U774,L752,U215,L930,U305,R463,U774,R234,U786,R425,U470,R90,D383,R692,D626,L160,D588,L141,D351,R574,D237,L869,D499,R873,U856,R148,D919,L582,D804,L413,U201,L247,U907,L828,D279,L28,D950,L587,U290,R636,U344,L591,U118,L614,U203,R381,U634,L301,D197,R594,D373,L459,U504,L703,U852,L672,U613,R816,D712,R813,U97,R824,D690,L556,D308,L568,D924,L384,U540,R745,D679,R705,D808,L346,U927,R145,U751,L769,D152,L648,D553,L738,U456,R864,U486,R894,D923,R76,U211,L78,U145,R977,U297,R93,U200,L71,U665,L392,D309,L399,D594,R118,U552,L328,U317,R369,D109,L673,D306,R441,U836,L305,D59,L870,U648,L817,D381,R676,U711,R115,U344,L815,U286,R194,U526,R844,U106,L547,D312,L116,U783,R786,D390,L115,D483,R691,U802,R569,U13,R854,D90,R22,D819,L440,D13,R438,D640,L952,D394,R984,D825,R1,D554,R349,U746,L816,U301,L397,D85,R437,D746,L698,D75,L964,U155,L268,U612,R838,D338,L188,U38,R830,U538,L245,D885,R194,D989,R8,D69,L268,D677,R163,U784,L308,U605,L737,U919,R117,U449,R698,U547,L134,D860,L234,U923,R495,D55,R954,D531,L212" 54 | PATH2 = "L1005,D937,L260,D848,R640,U358,R931,U495,R225,U344,R595,U754,L410,D5,R52,D852,L839,D509,R755,D983,R160,U522,R795,D465,R590,U558,R552,U332,R330,U752,R860,D503,L456,U254,R878,D164,R991,U569,R44,U112,L258,U168,L552,U68,R414,U184,R458,D58,R319,U168,R501,D349,R204,D586,R241,U575,L981,D819,L171,D811,L960,U495,R192,D725,R718,D346,R399,D692,L117,D215,L390,U364,L700,D207,R372,U767,L738,D844,L759,D211,R287,U964,R328,D800,R823,U104,L524,D68,R714,D633,R565,D373,R883,U327,R222,D318,L58,D451,R555,D687,R807,U638,L717,U298,R849,D489,L159,D692,L136,U242,R884,U202,R419,U41,L980,U483,R966,D513,L870,D306,R171,D585,R71,D320,R914,U991,R706,U440,R542,D219,L969,U9,R481,U164,R919,U17,L750,U775,R173,U515,L191,D548,L515,U54,L132,U56,R203,U544,L796,D508,L321,D517,L358,U12,L892,D472,L378,U121,L974,U36,R56,D758,L680,D17,L369,D72,L926,D466,L866,U850,R300,D597,L848,U17,L890,D739,L275,U560,L640,U602,R238,U919,R636,D188,R910,D992,L13,U241,R77,U857,R453,U883,L881,D267,R28,U928,R735,U731,L701,D795,R371,U652,R416,D129,R142,D30,R442,U513,R827,U455,L429,D804,R966,D565,R326,U398,R621,U324,L684,D235,L467,D575,L200,D442,R320,D550,R278,U929,R555,U537,L416,U98,R991,D271,L764,U841,L273,D782,R356,D447,R340,U413,R543,U260,L365,D529,R721,U542,L648,U366,R494,U243,L872,U201,L440,U232,R171,D608,R282,U484,R81,D320,R274,D760,L250,U749,L132,D162,L340,D308,L149,D5,L312,U547,R686,D684,R133,D876,L531,U572,R62,D142,L218,U703,L884,U64,L889,U887,R228,U534,R624,D524,R522,D452,L550,U959,R981,U139,R35,U98,R212" 55 | 56 | print(closest_intersection(PATH1, PATH2)) -------------------------------------------------------------------------------- /day04/day04.py: -------------------------------------------------------------------------------- 1 | """ 2 | It is a six-digit number. 3 | The value is within the range given in your puzzle input. 4 | Two adjacent digits are the same (like 22 in 122345). 5 | Going from left to right, the digits never decrease; they only ever increase or stay the same (like 111123 or 135679). 6 | Other than the range rule, the following are true: 7 | 8 | 111111 meets these criteria (double 11, never decreases). 9 | 223450 does not meet these criteria (decreasing pair of digits 50). 10 | 123789 does not meet these criteria (no double). 11 | """ 12 | from typing import List 13 | from collections import Counter 14 | 15 | def digits(n: int, num_digits: int = 6) -> List[int]: 16 | d = [] 17 | for _ in range(num_digits): 18 | d.append(n % 10) 19 | n = n // 10 20 | return list(reversed(d)) 21 | 22 | assert digits(123) == [0, 0, 0, 1, 2, 3] 23 | assert digits(594383) == [5, 9, 4, 3, 8, 3] 24 | 25 | def is_increasing(ds: List[int]) -> bool: 26 | return all(x <= y for x, y in zip(ds, ds[1:])) 27 | 28 | def adjacent_same(ds: List[int]) -> bool: 29 | return any(x == y for x, y in zip(ds, ds[1:])) 30 | 31 | def is_valid(n: int) -> bool: 32 | d = digits(n) 33 | return is_increasing(d) and adjacent_same(d) 34 | 35 | def has_group_of_two(ds: List[int]) -> bool: 36 | counts = Counter(ds) 37 | return any(v == 2 for v in counts.values()) 38 | 39 | def is_valid2(n: int) -> bool: 40 | d = digits(n) 41 | return is_increasing(d) and has_group_of_two(d) 42 | 43 | assert is_valid(111111) 44 | assert not is_valid(223450) 45 | assert is_valid(223459) 46 | assert not is_valid(123789) 47 | 48 | LO = 372304 49 | HI = 847060 50 | 51 | # print(sum(is_valid(d) for d in range(LO, HI+1))) 52 | 53 | def is_valid2(n: int) -> bool: 54 | d = digits(n) 55 | return is_increasing(d) and has_group_of_two(d) 56 | 57 | assert is_valid2(112233) 58 | assert not is_valid2(123444) 59 | assert is_valid2(111122) 60 | 61 | print(sum(is_valid2(d) for d in range(LO, HI+1))) -------------------------------------------------------------------------------- /day05/day05.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple, Tuple 2 | 3 | def parse_opcode(opcode: int) -> Tuple[int, List[int]]: 4 | opcode_part = opcode % 100 5 | 6 | hundreds_digit = (opcode // 100) % 10 7 | thousands_digit = (opcode // 1000) % 10 8 | ten_thousands_digit = (opcode // 10000) % 10 9 | 10 | return opcode_part, [hundreds_digit, thousands_digit, ten_thousands_digit] 11 | 12 | # 1 is immediate mode, 0 is position mode 13 | 14 | Program = List[int] 15 | 16 | def run(program: Program, input: List[int]) -> List[int]: 17 | program = program[:] 18 | output = [] 19 | 20 | pos = 0 21 | 22 | while program[pos] != 99: # halt 23 | opcode, modes = parse_opcode(program[pos]) 24 | print(pos, opcode, modes) 25 | 26 | if opcode == 1: 27 | # Add 28 | if modes[0] == 0: 29 | value1 = program[program[pos + 1]] 30 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 31 | else: 32 | value1 = program[pos + 1] 33 | print(f"value1 is immediate {value1}") 34 | 35 | if modes[1] == 0: 36 | value2 = program[program[pos + 2]] 37 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 38 | else: 39 | value2 = program[pos + 2] 40 | print(f"value2 is immediate {value2}") 41 | 42 | print("adding", value1, "and", value2, "and sticking the result at", program[pos+3]) 43 | 44 | program[program[pos + 3]] = value1 + value2 45 | pos += 4 46 | elif opcode == 2: 47 | # Multiply 48 | if modes[0] == 0: 49 | value1 = program[program[pos + 1]] 50 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 51 | else: 52 | value1 = program[pos + 1] 53 | print(f"value1 is immediate {value1}") 54 | 55 | if modes[1] == 0: 56 | value2 = program[program[pos + 2]] 57 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 58 | else: 59 | value2 = program[pos + 2] 60 | print(f"value2 is immediate {value2}") 61 | 62 | print("multiplying", value1, "and", value2, "and sticking the result at", program[pos+3]) 63 | 64 | 65 | program[program[pos + 3]] = value1 * value2 66 | pos += 4 67 | elif opcode == 3: 68 | # Get input and store at location 69 | loc = program[pos + 1] 70 | input_value = input[0] 71 | input = input[1:] 72 | program[loc] = input_value 73 | pos += 2 74 | elif opcode == 4: 75 | # Get output from location 76 | if modes[0] == 0: 77 | loc = program[pos + 1] 78 | value = program[loc] 79 | else: 80 | value = program[pos + 1] 81 | 82 | output.append(value) 83 | pos += 2 84 | 85 | elif opcode == 5: 86 | # jump if true 87 | if modes[0] == 0: 88 | value1 = program[program[pos + 1]] 89 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 90 | else: 91 | value1 = program[pos + 1] 92 | print(f"value1 is immediate {value1}") 93 | 94 | if modes[1] == 0: 95 | value2 = program[program[pos + 2]] 96 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 97 | else: 98 | value2 = program[pos + 2] 99 | print(f"value2 is immediate {value2}") 100 | 101 | if value1 != 0: 102 | pos = value2 103 | else: 104 | pos += 3 105 | 106 | elif opcode == 6: 107 | # jump if false 108 | if modes[0] == 0: 109 | value1 = program[program[pos + 1]] 110 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 111 | else: 112 | value1 = program[pos + 1] 113 | print(f"value1 is immediate {value1}") 114 | 115 | if modes[1] == 0: 116 | value2 = program[program[pos + 2]] 117 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 118 | else: 119 | value2 = program[pos + 2] 120 | print(f"value2 is immediate {value2}") 121 | 122 | if value1 == 0: 123 | pos = value2 124 | else: 125 | pos += 3 126 | 127 | elif opcode == 7: 128 | # less than 129 | if modes[0] == 0: 130 | value1 = program[program[pos + 1]] 131 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 132 | else: 133 | value1 = program[pos + 1] 134 | print(f"value1 is immediate {value1}") 135 | 136 | if modes[1] == 0: 137 | value2 = program[program[pos + 2]] 138 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 139 | else: 140 | value2 = program[pos + 2] 141 | print(f"value2 is immediate {value2}") 142 | 143 | if value1 < value2: 144 | program[program[pos + 3]] = 1 145 | else: 146 | program[program[pos + 3]] = 0 147 | pos += 4 148 | 149 | 150 | elif opcode == 8: 151 | # equals 152 | if modes[0] == 0: 153 | value1 = program[program[pos + 1]] 154 | print(f"loc1 is {program[pos + 1]} so value1 is {value1}") 155 | else: 156 | value1 = program[pos + 1] 157 | print(f"value1 is immediate {value1}") 158 | 159 | if modes[1] == 0: 160 | value2 = program[program[pos + 2]] 161 | print(f"loc2 is {program[pos + 2]} so value2 is {value2}") 162 | else: 163 | value2 = program[pos + 2] 164 | print(f"value2 is immediate {value2}") 165 | 166 | if value1 == value2: 167 | program[program[pos + 3]] = 1 168 | else: 169 | program[program[pos + 3]] = 0 170 | pos += 4 171 | 172 | else: 173 | raise RuntimeError(f"invalid opcode: {opcode}") 174 | 175 | return output 176 | 177 | PROGRAM = [3,225,1,225,6,6,1100,1,238,225,104,0,1002,43,69,224,101,-483,224,224,4,224,1002,223,8,223,1001,224,5,224,1,224,223,223,1101,67,60,225,1102,5,59,225,1101,7,16,225,1102,49,72,225,101,93,39,224,101,-98,224,224,4,224,102,8,223,223,1001,224,6,224,1,224,223,223,1102,35,82,225,2,166,36,224,101,-4260,224,224,4,224,102,8,223,223,101,5,224,224,1,223,224,223,102,66,48,224,1001,224,-4752,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1001,73,20,224,1001,224,-55,224,4,224,102,8,223,223,101,7,224,224,1,223,224,223,1102,18,41,224,1001,224,-738,224,4,224,102,8,223,223,101,6,224,224,1,224,223,223,1101,68,71,225,1102,5,66,225,1101,27,5,225,1101,54,63,224,1001,224,-117,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1,170,174,224,101,-71,224,224,4,224,1002,223,8,223,1001,224,4,224,1,223,224,223,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,1007,226,226,224,1002,223,2,223,1006,224,329,1001,223,1,223,1007,226,677,224,102,2,223,223,1006,224,344,1001,223,1,223,108,677,677,224,102,2,223,223,1005,224,359,1001,223,1,223,1007,677,677,224,1002,223,2,223,1006,224,374,101,1,223,223,8,677,226,224,1002,223,2,223,1006,224,389,101,1,223,223,7,226,226,224,1002,223,2,223,1005,224,404,101,1,223,223,7,677,226,224,102,2,223,223,1005,224,419,1001,223,1,223,8,226,677,224,1002,223,2,223,1005,224,434,101,1,223,223,1008,226,677,224,102,2,223,223,1006,224,449,1001,223,1,223,7,226,677,224,1002,223,2,223,1006,224,464,1001,223,1,223,108,677,226,224,102,2,223,223,1005,224,479,101,1,223,223,108,226,226,224,1002,223,2,223,1006,224,494,101,1,223,223,8,226,226,224,1002,223,2,223,1005,224,509,1001,223,1,223,1107,677,226,224,102,2,223,223,1005,224,524,1001,223,1,223,1107,226,226,224,102,2,223,223,1005,224,539,1001,223,1,223,1108,677,677,224,1002,223,2,223,1006,224,554,101,1,223,223,107,226,677,224,102,2,223,223,1005,224,569,1001,223,1,223,1108,226,677,224,1002,223,2,223,1005,224,584,1001,223,1,223,1107,226,677,224,1002,223,2,223,1005,224,599,1001,223,1,223,1008,226,226,224,1002,223,2,223,1005,224,614,101,1,223,223,107,226,226,224,102,2,223,223,1006,224,629,1001,223,1,223,1008,677,677,224,1002,223,2,223,1006,224,644,101,1,223,223,107,677,677,224,1002,223,2,223,1005,224,659,101,1,223,223,1108,677,226,224,1002,223,2,223,1006,224,674,1001,223,1,223,4,223,99,226] 178 | -------------------------------------------------------------------------------- /day05/intcode.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple 6 | from enum import Enum 7 | 8 | class Opcode(Enum): 9 | ADD = 1 10 | MULTIPLY = 2 11 | STORE_INPUT = 3 12 | SEND_TO_OUTPUT = 4 13 | JUMP_IF_TRUE = 5 14 | JUMP_IF_FALSE = 6 15 | LESS_THAN = 7 16 | EQUALS = 8 17 | END_PROGRAM = 99 18 | 19 | Modes = List[int] 20 | 21 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 22 | opcode_part = opcode % 100 23 | 24 | modes: List[int] = [] 25 | opcode = opcode // 100 26 | 27 | for _ in range(num_modes): 28 | modes.append(opcode % 10) 29 | opcode = opcode // 10 30 | 31 | return Opcode(opcode_part), modes 32 | 33 | 34 | Program = List[int] 35 | 36 | def run(program: Program, input: List[int]) -> List[int]: 37 | program = program[:] 38 | output = [] 39 | 40 | pos = 0 41 | 42 | def get_value(pos: int, mode: int) -> int: 43 | if mode == 0: 44 | # pointer mode 45 | return program[program[pos]] 46 | elif mode == 1: 47 | # immediate mode 48 | return program[pos] 49 | else: 50 | raise ValueError(f"unknown mode: {mode}") 51 | 52 | while True: 53 | opcode, modes = parse_opcode(program[pos]) 54 | 55 | if opcode == Opcode.END_PROGRAM: 56 | break 57 | elif opcode == Opcode.ADD: 58 | value1 = get_value(pos + 1, modes[0]) 59 | value2 = get_value(pos + 2, modes[1]) 60 | program[program[pos + 3]] = value1 + value2 61 | pos += 4 62 | elif opcode == Opcode.MULTIPLY: 63 | value1 = get_value(pos + 1, modes[0]) 64 | value2 = get_value(pos + 2, modes[1]) 65 | program[program[pos + 3]] = value1 * value2 66 | pos += 4 67 | elif opcode == Opcode.STORE_INPUT: 68 | # Get input and store at location 69 | loc = program[pos + 1] 70 | input_value = input[0] 71 | input = input[1:] 72 | program[loc] = input_value 73 | pos += 2 74 | elif opcode == Opcode.SEND_TO_OUTPUT: 75 | # Get output from location 76 | value = get_value(pos + 1, modes[0]) 77 | output.append(value) 78 | pos += 2 79 | 80 | elif opcode == Opcode.JUMP_IF_TRUE: 81 | # jump if true 82 | value1 = get_value(pos + 1, modes[0]) 83 | value2 = get_value(pos + 2, modes[1]) 84 | 85 | if value1 != 0: 86 | pos = value2 87 | else: 88 | pos += 3 89 | 90 | elif opcode == Opcode.JUMP_IF_FALSE: 91 | value1 = get_value(pos + 1, modes[0]) 92 | value2 = get_value(pos + 2, modes[1]) 93 | 94 | if value1 == 0: 95 | pos = value2 96 | else: 97 | pos += 3 98 | 99 | elif opcode == Opcode.LESS_THAN: 100 | value1 = get_value(pos + 1, modes[0]) 101 | value2 = get_value(pos + 2, modes[1]) 102 | 103 | if value1 < value2: 104 | program[program[pos + 3]] = 1 105 | else: 106 | program[program[pos + 3]] = 0 107 | pos += 4 108 | 109 | elif opcode == Opcode.EQUALS: 110 | value1 = get_value(pos + 1, modes[0]) 111 | value2 = get_value(pos + 2, modes[1]) 112 | 113 | if value1 == value2: 114 | program[program[pos + 3]] = 1 115 | else: 116 | program[program[pos + 3]] = 0 117 | pos += 4 118 | 119 | else: 120 | raise RuntimeError(f"invalid opcode: {opcode}") 121 | 122 | return output 123 | 124 | PROGRAM = [3,225,1,225,6,6,1100,1,238,225,104,0,1002,43,69,224,101,-483,224,224,4,224,1002,223,8,223,1001,224,5,224,1,224,223,223,1101,67,60,225,1102,5,59,225,1101,7,16,225,1102,49,72,225,101,93,39,224,101,-98,224,224,4,224,102,8,223,223,1001,224,6,224,1,224,223,223,1102,35,82,225,2,166,36,224,101,-4260,224,224,4,224,102,8,223,223,101,5,224,224,1,223,224,223,102,66,48,224,1001,224,-4752,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1001,73,20,224,1001,224,-55,224,4,224,102,8,223,223,101,7,224,224,1,223,224,223,1102,18,41,224,1001,224,-738,224,4,224,102,8,223,223,101,6,224,224,1,224,223,223,1101,68,71,225,1102,5,66,225,1101,27,5,225,1101,54,63,224,1001,224,-117,224,4,224,102,8,223,223,1001,224,2,224,1,223,224,223,1,170,174,224,101,-71,224,224,4,224,1002,223,8,223,1001,224,4,224,1,223,224,223,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,1007,226,226,224,1002,223,2,223,1006,224,329,1001,223,1,223,1007,226,677,224,102,2,223,223,1006,224,344,1001,223,1,223,108,677,677,224,102,2,223,223,1005,224,359,1001,223,1,223,1007,677,677,224,1002,223,2,223,1006,224,374,101,1,223,223,8,677,226,224,1002,223,2,223,1006,224,389,101,1,223,223,7,226,226,224,1002,223,2,223,1005,224,404,101,1,223,223,7,677,226,224,102,2,223,223,1005,224,419,1001,223,1,223,8,226,677,224,1002,223,2,223,1005,224,434,101,1,223,223,1008,226,677,224,102,2,223,223,1006,224,449,1001,223,1,223,7,226,677,224,1002,223,2,223,1006,224,464,1001,223,1,223,108,677,226,224,102,2,223,223,1005,224,479,101,1,223,223,108,226,226,224,1002,223,2,223,1006,224,494,101,1,223,223,8,226,226,224,1002,223,2,223,1005,224,509,1001,223,1,223,1107,677,226,224,102,2,223,223,1005,224,524,1001,223,1,223,1107,226,226,224,102,2,223,223,1005,224,539,1001,223,1,223,1108,677,677,224,1002,223,2,223,1006,224,554,101,1,223,223,107,226,677,224,102,2,223,223,1005,224,569,1001,223,1,223,1108,226,677,224,1002,223,2,223,1005,224,584,1001,223,1,223,1107,226,677,224,1002,223,2,223,1005,224,599,1001,223,1,223,1008,226,226,224,1002,223,2,223,1005,224,614,101,1,223,223,107,226,226,224,102,2,223,223,1006,224,629,1001,223,1,223,1008,677,677,224,1002,223,2,223,1006,224,644,101,1,223,223,107,677,677,224,1002,223,2,223,1005,224,659,101,1,223,223,1108,677,226,224,1002,223,2,223,1006,224,674,1001,223,1,223,4,223,99,226] 125 | -------------------------------------------------------------------------------- /day06/day06.py: -------------------------------------------------------------------------------- 1 | """ 2 | If we build a tree, each thing orbits its ancestors, 3 | so we need to count the total number of ancestors 4 | """ 5 | 6 | from typing import NamedTuple, List, Dict 7 | from collections import defaultdict 8 | 9 | RAW = """COM)B 10 | B)C 11 | C)D 12 | D)E 13 | E)F 14 | B)G 15 | G)H 16 | D)I 17 | E)J 18 | J)K 19 | K)L""" 20 | 21 | class Orbit(NamedTuple): 22 | parent: str 23 | child: str 24 | 25 | @staticmethod 26 | def from_string(s: str) -> 'Orbit': 27 | parent, child = s.strip().split(")") 28 | return Orbit(parent, child) 29 | 30 | ORBITS = [Orbit.from_string(s) for s in RAW.strip().split("\n")] 31 | 32 | def make_tree(orbits: List[Orbit]) -> Dict[str, str]: 33 | parents = {} 34 | for parent, child in orbits: 35 | parents[child] = parent 36 | return parents 37 | 38 | def count_ancestors(child: str, parents: Dict[str, str]) -> int: 39 | count = 0 40 | while child != "COM": 41 | count += 1 42 | child = parents[child] 43 | return count 44 | 45 | PARENTS = make_tree(ORBITS) 46 | 47 | assert count_ancestors('D', PARENTS) == 3 48 | assert count_ancestors('L', PARENTS) == 7 49 | assert count_ancestors('COM', PARENTS) == 0 50 | 51 | def total_ancestors(orbits: List[Orbit]) -> int: 52 | parents = make_tree(orbits) 53 | 54 | return sum(count_ancestors(child, parents) for child in parents) 55 | 56 | assert total_ancestors(ORBITS) == 42 57 | 58 | with open('input.txt') as f: 59 | orbits = [Orbit.from_string(line) for line in f] 60 | 61 | # print(total_ancestors(orbits)) 62 | 63 | 64 | def path_to_com(child: str, parents: Dict[str, str]) -> List[str]: 65 | path = [child] 66 | 67 | while child != "COM": 68 | child = parents[child] 69 | path.append(child) 70 | 71 | return path 72 | 73 | assert path_to_com('I', PARENTS) == ['I', 'D', 'C', 'B', 'COM'] 74 | 75 | def shortest_path(child1: str, child2: str, parents: Dict[str, str]) -> int: 76 | path1 = path_to_com(child1, parents) 77 | path2 = path_to_com(child2, parents) 78 | 79 | # J, E, D, C, B, COM 80 | # I, D, C, B, COM 81 | 82 | while path1 and path2 and path1[-1] == path2[-1]: 83 | path1.pop() 84 | path2.pop() 85 | 86 | return len(path1) + len(path2) 87 | 88 | assert shortest_path('I', 'K', PARENTS) == 4 89 | assert shortest_path('H', 'F', PARENTS) == 6 90 | 91 | parents = make_tree(orbits) 92 | print(shortest_path(parents['YOU'], parents['SAN'], parents)) -------------------------------------------------------------------------------- /day07/amplifier.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple 6 | from enum import Enum 7 | import itertools 8 | from collections import deque 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | END_PROGRAM = 99 25 | 26 | 27 | Modes = List[int] 28 | Program = List[int] 29 | 30 | 31 | class EndProgram(Exception): pass 32 | 33 | 34 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 35 | opcode_part = opcode % 100 36 | 37 | modes: List[int] = [] 38 | opcode = opcode // 100 39 | 40 | for _ in range(num_modes): 41 | modes.append(opcode % 10) 42 | opcode = opcode // 10 43 | 44 | return Opcode(opcode_part), modes 45 | 46 | 47 | class Amplifier: 48 | def __init__(self, program: List[int], phase: int) -> None: 49 | self.program = program[:] 50 | self.inputs = deque([phase]) 51 | self.pos = 0 52 | 53 | def _get_value(self, pos: int, mode: int) -> int: 54 | if mode == 0: 55 | # pointer mode 56 | return self.program[self.program[pos]] 57 | elif mode == 1: 58 | # immediate mode 59 | return self.program[pos] 60 | else: 61 | raise ValueError(f"unknown mode: {mode}") 62 | 63 | def __call__(self, input_value: int) -> int: 64 | self.inputs.append(input_value) 65 | 66 | while True: 67 | opcode, modes = parse_opcode(self.program[self.pos]) 68 | 69 | logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, " 70 | f"opcode: {opcode}, modes: {modes}") 71 | 72 | if opcode == Opcode.END_PROGRAM: 73 | raise EndProgram 74 | 75 | elif opcode == Opcode.ADD: 76 | value1 = self._get_value(self.pos + 1, modes[0]) 77 | value2 = self._get_value(self.pos + 2, modes[1]) 78 | self.program[self.program[self.pos + 3]] = value1 + value2 79 | self.pos += 4 80 | 81 | elif opcode == Opcode.MULTIPLY: 82 | value1 = self._get_value(self.pos + 1, modes[0]) 83 | value2 = self._get_value(self.pos + 2, modes[1]) 84 | self.program[self.program[self.pos + 3]] = value1 * value2 85 | self.pos += 4 86 | 87 | elif opcode == Opcode.STORE_INPUT: 88 | # Get input and store at location 89 | loc = self.program[self.pos + 1] 90 | input_value = self.inputs.popleft() 91 | self.program[loc] = input_value 92 | self.pos += 2 93 | 94 | elif opcode == Opcode.SEND_TO_OUTPUT: 95 | # Get output from location 96 | value = self._get_value(self.pos + 1, modes[0]) 97 | self.pos += 2 98 | return value 99 | 100 | elif opcode == Opcode.JUMP_IF_TRUE: 101 | # jump if true 102 | value1 = self._get_value(self.pos + 1, modes[0]) 103 | value2 = self._get_value(self.pos + 2, modes[1]) 104 | 105 | if value1 != 0: 106 | self.pos = value2 107 | else: 108 | self.pos += 3 109 | 110 | elif opcode == Opcode.JUMP_IF_FALSE: 111 | value1 = self._get_value(self.pos + 1, modes[0]) 112 | value2 = self._get_value(self.pos + 2, modes[1]) 113 | 114 | if value1 == 0: 115 | self.pos = value2 116 | else: 117 | self.pos += 3 118 | 119 | elif opcode == Opcode.LESS_THAN: 120 | value1 = self._get_value(self.pos + 1, modes[0]) 121 | value2 = self._get_value(self.pos + 2, modes[1]) 122 | 123 | if value1 < value2: 124 | self.program[self.program[self.pos + 3]] = 1 125 | else: 126 | self.program[self.program[self.pos + 3]] = 0 127 | self.pos += 4 128 | 129 | elif opcode == Opcode.EQUALS: 130 | value1 = self._get_value(self.pos + 1, modes[0]) 131 | value2 = self._get_value(self.pos + 2, modes[1]) 132 | 133 | if value1 == value2: 134 | self.program[self.program[self.pos + 3]] = 1 135 | else: 136 | self.program[self.program[self.pos + 3]] = 0 137 | self.pos += 4 138 | 139 | else: 140 | raise ValueError(f"invalid opcode: {opcode}") 141 | 142 | 143 | def run_amplifiers(program: List[int], phases: List[int]) -> int: 144 | amplifiers = deque([Amplifier(program, phase) for phase in phases]) 145 | output = 0 146 | 147 | while amplifiers: 148 | amplifier = amplifiers.popleft() 149 | 150 | try: 151 | output = amplifier(output) 152 | amplifiers.append(amplifier) 153 | except EndProgram: 154 | pass 155 | 156 | return output 157 | 158 | 159 | PROG1 = [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] 160 | PHASES1 = [9,8,7,6,5] 161 | assert run_amplifiers(PROG1, PHASES1) == 139629729 162 | 163 | PROG2 = [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] 164 | PHASES2 = [9,7,8,5,6] 165 | assert run_amplifiers(PROG2, PHASES2) == 18216 166 | 167 | def best_output(program: List[int]) -> int: 168 | return max(run_amplifiers(program, phases) 169 | for phases in itertools.permutations([5, 6, 7, 8, 9])) 170 | 171 | assert best_output(PROG1) == 139629729 172 | assert best_output(PROG2) == 18216 173 | 174 | PROGRAM = [3,8,1001,8,10,8,105,1,0,0,21,42,67,84,109,126,207,288,369,450,99999,3,9,102,4,9,9,1001,9,4,9,102,2,9,9,101,2,9,9,4,9,99,3,9,1001,9,5,9,1002,9,5,9,1001,9,5,9,1002,9,5,9,101,5,9,9,4,9,99,3,9,101,5,9,9,1002,9,3,9,1001,9,2,9,4,9,99,3,9,1001,9,2,9,102,4,9,9,101,2,9,9,102,4,9,9,1001,9,2,9,4,9,99,3,9,102,2,9,9,101,5,9,9,1002,9,2,9,4,9,99,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,99] 175 | 176 | # print(best_output(PROGRAM)) 177 | -------------------------------------------------------------------------------- /day07/day07.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple 6 | from enum import Enum 7 | import itertools 8 | 9 | class Opcode(Enum): 10 | ADD = 1 11 | MULTIPLY = 2 12 | STORE_INPUT = 3 13 | SEND_TO_OUTPUT = 4 14 | JUMP_IF_TRUE = 5 15 | JUMP_IF_FALSE = 6 16 | LESS_THAN = 7 17 | EQUALS = 8 18 | END_PROGRAM = 99 19 | 20 | Modes = List[int] 21 | 22 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 23 | opcode_part = opcode % 100 24 | 25 | modes: List[int] = [] 26 | opcode = opcode // 100 27 | 28 | for _ in range(num_modes): 29 | modes.append(opcode % 10) 30 | opcode = opcode // 10 31 | 32 | return Opcode(opcode_part), modes 33 | 34 | 35 | Program = List[int] 36 | 37 | def run_program(program: Program, input: List[int]) -> List[int]: 38 | program = program[:] 39 | output = [] 40 | 41 | pos = 0 42 | 43 | def get_value(pos: int, mode: int) -> int: 44 | if mode == 0: 45 | # pointer mode 46 | return program[program[pos]] 47 | elif mode == 1: 48 | # immediate mode 49 | return program[pos] 50 | else: 51 | raise ValueError(f"unknown mode: {mode}") 52 | 53 | while True: 54 | opcode, modes = parse_opcode(program[pos]) 55 | 56 | if opcode == Opcode.END_PROGRAM: 57 | break 58 | elif opcode == Opcode.ADD: 59 | value1 = get_value(pos + 1, modes[0]) 60 | value2 = get_value(pos + 2, modes[1]) 61 | program[program[pos + 3]] = value1 + value2 62 | pos += 4 63 | elif opcode == Opcode.MULTIPLY: 64 | value1 = get_value(pos + 1, modes[0]) 65 | value2 = get_value(pos + 2, modes[1]) 66 | program[program[pos + 3]] = value1 * value2 67 | pos += 4 68 | elif opcode == Opcode.STORE_INPUT: 69 | # Get input and store at location 70 | loc = program[pos + 1] 71 | input_value = input[0] 72 | input = input[1:] 73 | program[loc] = input_value 74 | pos += 2 75 | elif opcode == Opcode.SEND_TO_OUTPUT: 76 | # Get output from location 77 | value = get_value(pos + 1, modes[0]) 78 | output.append(value) 79 | pos += 2 80 | 81 | elif opcode == Opcode.JUMP_IF_TRUE: 82 | # jump if true 83 | value1 = get_value(pos + 1, modes[0]) 84 | value2 = get_value(pos + 2, modes[1]) 85 | 86 | if value1 != 0: 87 | pos = value2 88 | else: 89 | pos += 3 90 | 91 | elif opcode == Opcode.JUMP_IF_FALSE: 92 | value1 = get_value(pos + 1, modes[0]) 93 | value2 = get_value(pos + 2, modes[1]) 94 | 95 | if value1 == 0: 96 | pos = value2 97 | else: 98 | pos += 3 99 | 100 | elif opcode == Opcode.LESS_THAN: 101 | value1 = get_value(pos + 1, modes[0]) 102 | value2 = get_value(pos + 2, modes[1]) 103 | 104 | if value1 < value2: 105 | program[program[pos + 3]] = 1 106 | else: 107 | program[program[pos + 3]] = 0 108 | pos += 4 109 | 110 | elif opcode == Opcode.EQUALS: 111 | value1 = get_value(pos + 1, modes[0]) 112 | value2 = get_value(pos + 2, modes[1]) 113 | 114 | if value1 == value2: 115 | program[program[pos + 3]] = 1 116 | else: 117 | program[program[pos + 3]] = 0 118 | pos += 4 119 | 120 | else: 121 | raise RuntimeError(f"invalid opcode: {opcode}") 122 | 123 | return output 124 | 125 | 126 | def run_amplifier(program: List[int], input_signal: int, phase: int) -> int: 127 | inputs = [phase, input_signal] 128 | output, = run_program(program, inputs) 129 | 130 | return output 131 | 132 | 133 | def run(program: List[int], phases: List[int]) -> int: 134 | last_output = 0 135 | 136 | for phase in phases: 137 | last_output = run_amplifier(program, last_output, phase) 138 | 139 | return last_output 140 | 141 | assert run([3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0], 142 | [4,3,2,1,0]) == 43210 143 | 144 | assert run([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], 145 | [0,1,2,3,4]) == 54321 146 | 147 | def best_output(program: List[int]) -> int: 148 | return max(run(program, phases) 149 | for phases in itertools.permutations(range(5))) 150 | 151 | assert best_output([3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0]) == 43210 152 | 153 | assert best_output([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]) \ 154 | == 54321 155 | 156 | PROGRAM = [3,8,1001,8,10,8,105,1,0,0,21,42,67,84,109,126,207,288,369,450,99999,3,9,102,4,9,9,1001,9,4,9,102,2,9,9,101,2,9,9,4,9,99,3,9,1001,9,5,9,1002,9,5,9,1001,9,5,9,1002,9,5,9,101,5,9,9,4,9,99,3,9,101,5,9,9,1002,9,3,9,1001,9,2,9,4,9,99,3,9,1001,9,2,9,102,4,9,9,101,2,9,9,102,4,9,9,1001,9,2,9,4,9,99,3,9,102,2,9,9,101,5,9,9,1002,9,2,9,4,9,99,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,99] 157 | 158 | print(best_output(PROGRAM)) -------------------------------------------------------------------------------- /day07/day07_part2.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple 6 | from enum import Enum 7 | import itertools 8 | from collections import deque 9 | 10 | class Opcode(Enum): 11 | ADD = 1 12 | MULTIPLY = 2 13 | STORE_INPUT = 3 14 | SEND_TO_OUTPUT = 4 15 | JUMP_IF_TRUE = 5 16 | JUMP_IF_FALSE = 6 17 | LESS_THAN = 7 18 | EQUALS = 8 19 | END_PROGRAM = 99 20 | 21 | Modes = List[int] 22 | 23 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 24 | opcode_part = opcode % 100 25 | 26 | modes: List[int] = [] 27 | opcode = opcode // 100 28 | 29 | for _ in range(num_modes): 30 | modes.append(opcode % 10) 31 | opcode = opcode // 10 32 | 33 | return Opcode(opcode_part), modes 34 | 35 | Program = List[int] 36 | 37 | 38 | class Amplifier: 39 | def __init__(self, program: List[int], phase: int) -> None: 40 | self.program = program[:] 41 | self.inputs = deque([phase]) 42 | self.pos = 0 43 | 44 | def get_value(self, pos: int, mode: int) -> int: 45 | if mode == 0: 46 | # pointer mode 47 | return self.program[self.program[pos]] 48 | elif mode == 1: 49 | # immediate mode 50 | return self.program[pos] 51 | else: 52 | raise ValueError(f"unknown mode: {mode}") 53 | 54 | def step(self, input_value: int) -> int: 55 | self.inputs.append(input_value) 56 | 57 | while True: 58 | opcode, modes = parse_opcode(self.program[self.pos]) 59 | 60 | # print(self.pos, self.inputs, opcode, modes) 61 | 62 | if opcode == Opcode.END_PROGRAM: 63 | return None 64 | elif opcode == Opcode.ADD: 65 | value1 = self.get_value(self.pos + 1, modes[0]) 66 | value2 = self.get_value(self.pos + 2, modes[1]) 67 | self.program[self.program[self.pos + 3]] = value1 + value2 68 | self.pos += 4 69 | elif opcode == Opcode.MULTIPLY: 70 | value1 = self.get_value(self.pos + 1, modes[0]) 71 | value2 = self.get_value(self.pos + 2, modes[1]) 72 | self.program[self.program[self.pos + 3]] = value1 * value2 73 | self.pos += 4 74 | elif opcode == Opcode.STORE_INPUT: 75 | # Get input and store at location 76 | loc = self.program[self.pos + 1] 77 | input_value = self.inputs.popleft() 78 | self.program[loc] = input_value 79 | self.pos += 2 80 | elif opcode == Opcode.SEND_TO_OUTPUT: 81 | # Get output from location 82 | value = self.get_value(self.pos + 1, modes[0]) 83 | self.pos += 2 84 | return value 85 | 86 | elif opcode == Opcode.JUMP_IF_TRUE: 87 | # jump if true 88 | value1 = self.get_value(self.pos + 1, modes[0]) 89 | value2 = self.get_value(self.pos + 2, modes[1]) 90 | 91 | if value1 != 0: 92 | self.pos = value2 93 | else: 94 | self.pos += 3 95 | 96 | elif opcode == Opcode.JUMP_IF_FALSE: 97 | value1 = self.get_value(self.pos + 1, modes[0]) 98 | value2 = self.get_value(self.pos + 2, modes[1]) 99 | 100 | if value1 == 0: 101 | self.pos = value2 102 | else: 103 | self.pos += 3 104 | 105 | elif opcode == Opcode.LESS_THAN: 106 | value1 = self.get_value(self.pos + 1, modes[0]) 107 | value2 = self.get_value(self.pos + 2, modes[1]) 108 | 109 | if value1 < value2: 110 | self.program[self.program[self.pos + 3]] = 1 111 | else: 112 | self.program[self.program[self.pos + 3]] = 0 113 | self.pos += 4 114 | 115 | elif opcode == Opcode.EQUALS: 116 | value1 = self.get_value(self.pos + 1, modes[0]) 117 | value2 = self.get_value(self.pos + 2, modes[1]) 118 | 119 | if value1 == value2: 120 | self.program[self.program[self.pos + 3]] = 1 121 | else: 122 | self.program[self.program[self.pos + 3]] = 0 123 | self.pos += 4 124 | 125 | else: 126 | raise RuntimeError(f"invalid opcode: {opcode}") 127 | 128 | 129 | def run_amplifiers(program: List[int], phases: List[int]) -> int: 130 | amplifiers = [Amplifier(program, phase) for phase in phases] 131 | n = len(amplifiers) 132 | num_finished = 0 133 | 134 | last_output = 0 135 | last_non_none_output = None 136 | aid = 0 137 | 138 | while num_finished < n: 139 | # print(aid, last_output, num_finished) 140 | last_output = amplifiers[aid].step(last_output) 141 | if last_output is None: 142 | num_finished += 1 143 | else: 144 | last_non_none_output = last_output 145 | aid = (aid + 1) % n 146 | 147 | return last_non_none_output 148 | 149 | 150 | PROG1 = [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] 151 | PHASES1 = [9,8,7,6,5] 152 | assert run_amplifiers(PROG1, PHASES1) == 139629729 153 | 154 | PROG2 = [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] 155 | PHASES2 = [9,7,8,5,6] 156 | assert run_amplifiers(PROG2, PHASES2) == 18216 157 | 158 | def best_output(program: List[int]) -> int: 159 | return max(run_amplifiers(program, phases) 160 | for phases in itertools.permutations([5, 6, 7, 8, 9])) 161 | 162 | assert best_output(PROG1) == 139629729 163 | assert best_output(PROG2) == 18216 164 | 165 | PROGRAM = [3,8,1001,8,10,8,105,1,0,0,21,42,67,84,109,126,207,288,369,450,99999,3,9,102,4,9,9,1001,9,4,9,102,2,9,9,101,2,9,9,4,9,99,3,9,1001,9,5,9,1002,9,5,9,1001,9,5,9,1002,9,5,9,101,5,9,9,4,9,99,3,9,101,5,9,9,1002,9,3,9,1001,9,2,9,4,9,99,3,9,1001,9,2,9,102,4,9,9,101,2,9,9,102,4,9,9,1001,9,2,9,4,9,99,3,9,102,2,9,9,101,5,9,9,1002,9,2,9,4,9,99,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,1,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1002,9,2,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,101,2,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,99,3,9,1001,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,101,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1001,9,2,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,102,2,9,9,4,9,3,9,101,1,9,9,4,9,99,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,101,2,9,9,4,9,3,9,1002,9,2,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,1,9,4,9,3,9,1002,9,2,9,4,9,3,9,101,1,9,9,4,9,3,9,102,2,9,9,4,9,3,9,1001,9,2,9,4,9,99] 166 | 167 | print(best_output(PROGRAM)) 168 | -------------------------------------------------------------------------------- /day08/day08.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | from collections import Counter 3 | import copy 4 | 5 | Image = List[List[List[int]]] 6 | 7 | 8 | def parse_image(raw: str, width: int, height: int) -> Image: 9 | pixels = [int(c) for c in raw] 10 | 11 | num_layers = len(pixels) // width // height 12 | 13 | image = [ 14 | [ 15 | [None for _ in range(width)] 16 | for _ in range(height) 17 | ] 18 | for _ in range(num_layers) 19 | ] 20 | 21 | layer = i = j = 0 22 | 23 | for pixel in pixels: 24 | image[layer][i][j] = pixel 25 | 26 | j += 1 27 | 28 | if j == width: 29 | j = 0 30 | i += 1 31 | 32 | if i == height: 33 | i = 0 34 | layer += 1 35 | 36 | return image 37 | 38 | 39 | RAW = "123456789012" 40 | IMAGE = parse_image(RAW, 3, 2) 41 | assert IMAGE == [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [0, 1, 2]]] 42 | 43 | 44 | def count_colors(image: Image) -> List[Dict[int, int]]: 45 | return [ 46 | Counter(pixel for row in layer for pixel in row) 47 | for layer in image 48 | ] 49 | 50 | def one_times_two(image: Image) -> int: 51 | color_counts = count_colors(image) 52 | layer_counts = min(color_counts, key=lambda cc: cc[0]) 53 | return layer_counts[1] * layer_counts[2] 54 | 55 | 56 | assert one_times_two(IMAGE) == 1 57 | 58 | with open('day08.txt') as f: 59 | raw = f.read().strip() 60 | 61 | image = parse_image(raw, width=25, height=6) 62 | 63 | # print(one_times_two(image)) 64 | 65 | def show(image: Image) -> None: 66 | consolidated = copy.deepcopy(image[0]) 67 | num_layers = len(image) 68 | height = len(image[0]) 69 | width = len(image[0][0]) 70 | 71 | for i in range(height): 72 | for j in range(width): 73 | for layer in range(num_layers): 74 | color = image[layer][i][j] 75 | if color == 0: 76 | consolidated[i][j] = ' ' 77 | break 78 | elif color == 1: 79 | consolidated[i][j] = '*' 80 | break 81 | 82 | for row in consolidated: 83 | print("".join(row)) 84 | 85 | RAW2 = "0222112222120000" 86 | IMAGE2 = parse_image(RAW2, 2, 2) 87 | 88 | # show(IMAGE2) 89 | 90 | show(image) -------------------------------------------------------------------------------- /day09/day09.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple, Iterable 6 | from enum import Enum 7 | import itertools 8 | from collections import deque, defaultdict 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int]) -> None: 53 | self.program = defaultdict(int) 54 | self.program.update({i: value for i, value in enumerate(program)}) 55 | self.inputs = deque() 56 | self.pos = 0 57 | self.relative_base = 0 58 | 59 | def _get_value(self, pos: int, mode: int) -> int: 60 | if mode == 0: 61 | # pointer mode 62 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 63 | return self.program[self.program[pos]] 64 | elif mode == 1: 65 | # immediate mode 66 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 67 | return self.program[pos] 68 | elif mode == 2: 69 | # relative mode 70 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 71 | return self.program[self.program[pos] + self.relative_base] 72 | else: 73 | raise ValueError(f"unknown mode: {mode}") 74 | 75 | def _loc(self, pos: int, mode: int) -> int: 76 | if mode == 0: 77 | # pointer mode 78 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 79 | return self.program[pos] 80 | elif mode == 2: 81 | # relative mode 82 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 83 | return self.program[pos] + self.relative_base 84 | 85 | def __call__(self, input_values: Iterable[int] = ()) -> int: 86 | self.inputs.extend(input_values) 87 | 88 | while True: 89 | logging.debug(f"program: {self.program}") 90 | logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 91 | 92 | opcode, modes = parse_opcode(self.program[self.pos]) 93 | 94 | logging.debug(f"opcode: {opcode}, modes: {modes}") 95 | 96 | if opcode == Opcode.END_PROGRAM: 97 | raise EndProgram 98 | 99 | elif opcode == Opcode.ADD: 100 | value1 = self._get_value(self.pos + 1, modes[0]) 101 | value2 = self._get_value(self.pos + 2, modes[1]) 102 | loc = self._loc(self.pos + 3, modes[2]) 103 | 104 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 105 | 106 | self.program[loc] = value1 + value2 107 | self.pos += 4 108 | 109 | elif opcode == Opcode.MULTIPLY: 110 | value1 = self._get_value(self.pos + 1, modes[0]) 111 | value2 = self._get_value(self.pos + 2, modes[1]) 112 | loc = self._loc(self.pos + 3, modes[2]) 113 | 114 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 115 | 116 | self.program[loc] = value1 * value2 117 | self.pos += 4 118 | 119 | elif opcode == Opcode.STORE_INPUT: 120 | # Get input and store at location 121 | loc = self._loc(self.pos + 1, modes[0]) 122 | input_value = self.inputs.popleft() 123 | self.program[loc] = input_value 124 | self.pos += 2 125 | 126 | elif opcode == Opcode.SEND_TO_OUTPUT: 127 | # Get output from location 128 | value = self._get_value(self.pos + 1, modes[0]) 129 | self.pos += 2 130 | logging.debug(f"output: {value}") 131 | return value 132 | 133 | elif opcode == Opcode.JUMP_IF_TRUE: 134 | # jump if true 135 | value1 = self._get_value(self.pos + 1, modes[0]) 136 | value2 = self._get_value(self.pos + 2, modes[1]) 137 | 138 | logging.debug(f"value1: {value1}, value2: {value2}") 139 | 140 | if value1 != 0: 141 | self.pos = value2 142 | else: 143 | self.pos += 3 144 | 145 | elif opcode == Opcode.JUMP_IF_FALSE: 146 | value1 = self._get_value(self.pos + 1, modes[0]) 147 | value2 = self._get_value(self.pos + 2, modes[1]) 148 | 149 | logging.debug(f"value1: {value1}, value2: {value2}") 150 | 151 | if value1 == 0: 152 | self.pos = value2 153 | else: 154 | self.pos += 3 155 | 156 | elif opcode == Opcode.LESS_THAN: 157 | value1 = self._get_value(self.pos + 1, modes[0]) 158 | value2 = self._get_value(self.pos + 2, modes[1]) 159 | loc = self._loc(self.pos + 3, modes[2]) 160 | 161 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 162 | 163 | if value1 < value2: 164 | self.program[loc] = 1 165 | else: 166 | self.program[loc] = 0 167 | self.pos += 4 168 | 169 | elif opcode == Opcode.EQUALS: 170 | value1 = self._get_value(self.pos + 1, modes[0]) 171 | value2 = self._get_value(self.pos + 2, modes[1]) 172 | loc = self._loc(self.pos + 3, modes[2]) 173 | 174 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 175 | 176 | if value1 == value2: 177 | self.program[loc] = 1 178 | else: 179 | self.program[loc] = 0 180 | self.pos += 4 181 | 182 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 183 | value = self._get_value(self.pos + 1, modes[0]) 184 | 185 | logging.debug(f"value: {value}") 186 | 187 | self.relative_base += value 188 | self.pos += 2 189 | 190 | else: 191 | raise ValueError(f"invalid opcode: {opcode}") 192 | 193 | 194 | def run(program: Program, inputs: List[int]) -> List[int]: 195 | outputs = [] 196 | computer = IntcodeComputer(program) 197 | 198 | try: 199 | while True: 200 | outputs.append(computer(inputs)) 201 | inputs = () 202 | except EndProgram: 203 | return outputs 204 | 205 | PROG1 = [109,1,204,-1,1001,100,1,100,1008,100,16,101,1006,101,0,99] 206 | # print(run(PROG1, ())) 207 | 208 | PROG2 = [1102,34915192,34915192,7,4,7,99,0] 209 | # print(run(PROG2, ())) 210 | 211 | PROG3 = [104,1125899906842624,99] 212 | # print(run(PROG3, ())) 213 | 214 | BOOST = [1102,34463338,34463338,63,1007,63,34463338,63,1005,63,53,1102,3,1,1000,109,988,209,12,9,1000,209,6,209,3,203,0,1008,1000,1,63,1005,63,65,1008,1000,2,63,1005,63,904,1008,1000,0,63,1005,63,58,4,25,104,0,99,4,0,104,0,99,4,17,104,0,99,0,0,1101,37,0,1012,1101,26,0,1008,1101,0,39,1016,1101,0,36,1007,1101,669,0,1024,1102,1,29,1009,1102,0,1,1020,1102,24,1,1003,1102,22,1,1013,1101,0,30,1019,1101,260,0,1027,1101,38,0,1018,1101,642,0,1029,1102,25,1,1004,1101,23,0,1017,1101,21,0,1001,1102,20,1,1010,1102,33,1,1015,1102,35,1,1002,1102,1,1,1021,1102,31,1,1014,1101,895,0,1022,1101,0,32,1011,1102,1,28,1005,1101,0,892,1023,1101,263,0,1026,1102,1,27,1000,1101,647,0,1028,1101,0,34,1006,1102,1,660,1025,109,9,1208,-7,38,63,1005,63,201,1001,64,1,64,1106,0,203,4,187,1002,64,2,64,109,4,2101,0,-5,63,1008,63,24,63,1005,63,227,1001,64,1,64,1106,0,229,4,209,1002,64,2,64,109,5,21107,40,41,0,1005,1018,251,4,235,1001,64,1,64,1105,1,251,1002,64,2,64,109,18,2106,0,-9,1105,1,269,4,257,1001,64,1,64,1002,64,2,64,109,-40,1208,6,35,63,1005,63,287,4,275,1105,1,291,1001,64,1,64,1002,64,2,64,109,11,2102,1,0,63,1008,63,35,63,1005,63,315,1001,64,1,64,1106,0,317,4,297,1002,64,2,64,109,6,21107,41,40,-3,1005,1010,337,1001,64,1,64,1106,0,339,4,323,1002,64,2,64,109,-2,2101,0,-8,63,1008,63,24,63,1005,63,365,4,345,1001,64,1,64,1105,1,365,1002,64,2,64,109,9,21102,42,1,-3,1008,1017,43,63,1005,63,385,1105,1,391,4,371,1001,64,1,64,1002,64,2,64,109,-4,1206,5,407,1001,64,1,64,1105,1,409,4,397,1002,64,2,64,109,13,1206,-9,427,4,415,1001,64,1,64,1106,0,427,1002,64,2,64,109,-25,2107,27,1,63,1005,63,449,4,433,1001,64,1,64,1106,0,449,1002,64,2,64,109,-3,1202,-1,1,63,1008,63,27,63,1005,63,475,4,455,1001,64,1,64,1105,1,475,1002,64,2,64,109,6,21108,43,41,8,1005,1015,491,1106,0,497,4,481,1001,64,1,64,1002,64,2,64,109,6,1205,8,515,4,503,1001,64,1,64,1105,1,515,1002,64,2,64,109,-11,1207,1,23,63,1005,63,531,1105,1,537,4,521,1001,64,1,64,1002,64,2,64,109,1,2108,24,0,63,1005,63,559,4,543,1001,64,1,64,1105,1,559,1002,64,2,64,109,12,21101,44,0,1,1008,1016,44,63,1005,63,585,4,565,1001,64,1,64,1105,1,585,1002,64,2,64,109,-23,2102,1,8,63,1008,63,27,63,1005,63,607,4,591,1105,1,611,1001,64,1,64,1002,64,2,64,109,18,21108,45,45,3,1005,1013,633,4,617,1001,64,1,64,1105,1,633,1002,64,2,64,109,11,2106,0,7,4,639,1106,0,651,1001,64,1,64,1002,64,2,64,109,-1,2105,1,4,4,657,1001,64,1,64,1105,1,669,1002,64,2,64,109,-10,2107,26,-6,63,1005,63,685,1105,1,691,4,675,1001,64,1,64,1002,64,2,64,109,9,1205,1,703,1106,0,709,4,697,1001,64,1,64,1002,64,2,64,109,-12,2108,22,-3,63,1005,63,729,1001,64,1,64,1106,0,731,4,715,1002,64,2,64,109,-11,1207,10,35,63,1005,63,753,4,737,1001,64,1,64,1106,0,753,1002,64,2,64,109,9,21101,46,0,5,1008,1010,43,63,1005,63,773,1105,1,779,4,759,1001,64,1,64,1002,64,2,64,109,-1,1201,4,0,63,1008,63,26,63,1005,63,801,4,785,1105,1,805,1001,64,1,64,1002,64,2,64,109,7,1201,-8,0,63,1008,63,22,63,1005,63,825,1106,0,831,4,811,1001,64,1,64,1002,64,2,64,109,-1,1202,-6,1,63,1008,63,23,63,1005,63,855,1001,64,1,64,1106,0,857,4,837,1002,64,2,64,109,7,21102,47,1,0,1008,1017,47,63,1005,63,883,4,863,1001,64,1,64,1106,0,883,1002,64,2,64,109,8,2105,1,-2,1106,0,901,4,889,1001,64,1,64,4,64,99,21101,0,27,1,21101,915,0,0,1105,1,922,21201,1,20897,1,204,1,99,109,3,1207,-2,3,63,1005,63,964,21201,-2,-1,1,21101,0,942,0,1106,0,922,22101,0,1,-1,21201,-2,-3,1,21102,957,1,0,1106,0,922,22201,1,-1,-2,1106,0,968,22102,1,-2,-2,109,-3,2105,1,0] 215 | 216 | # print(run(BOOST, [2])) 217 | -------------------------------------------------------------------------------- /day10/day10.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, NamedTuple, Tuple, Iterator 2 | import math 3 | from collections import defaultdict 4 | 5 | class Asteroid(NamedTuple): 6 | x: int 7 | y: int 8 | 9 | Asteroids = List[Asteroid] 10 | 11 | def parse(raw: str) -> Asteroids: 12 | return [ 13 | Asteroid(x, y) 14 | for y, line in enumerate(raw.strip().split("\n")) 15 | for x, c in enumerate(line) 16 | if c == '#' 17 | ] 18 | 19 | 20 | def count_visible(asteroids: Asteroids, station: Asteroid) -> int: 21 | # recenter 22 | slopes = set() 23 | for x, y in asteroids: 24 | dx = x - station.x 25 | dy = y - station.y 26 | 27 | gcd = math.gcd(dx, dy) 28 | 29 | if dx == dy == 0: 30 | pass 31 | else: 32 | slopes.add((dx / gcd, dy / gcd)) 33 | return len(slopes) 34 | 35 | def best_station(asteroids: Asteroids) -> Tuple[Asteroid, int]: 36 | results = [(a, count_visible(asteroids, a)) for a in asteroids] 37 | return max(results, key=lambda pair: pair[1]) 38 | 39 | RAW = """.#..# 40 | ..... 41 | ##### 42 | ....# 43 | ...##""" 44 | 45 | ASTEROIDS = parse(RAW) 46 | 47 | assert best_station(ASTEROIDS) == (Asteroid(3, 4), 8) 48 | 49 | A2 = parse("""......#.#. 50 | #..#.#.... 51 | ..#######. 52 | .#.#.###.. 53 | .#..#..... 54 | ..#....#.# 55 | #..#....#. 56 | .##.#..### 57 | ##...#..#. 58 | .#....####""") 59 | 60 | assert best_station(A2) == (Asteroid(5, 8), 33) 61 | 62 | A3 = parse(""".#..##.###...####### 63 | ##.############..##. 64 | .#.######.########.# 65 | .###.#######.####.#. 66 | #####.##.#.##.###.## 67 | ..#####..#.######### 68 | #################### 69 | #.####....###.#.#.## 70 | ##.################# 71 | #####.##.###..####.. 72 | ..######..##.####### 73 | ####.##.####...##..# 74 | .#####..#.######.### 75 | ##...#.##########... 76 | #.##########.####### 77 | .####.#.###.###.#.## 78 | ....##.##.###..##### 79 | .#.#.###########.### 80 | #.#.#.#####.####.### 81 | ###.##.####.##.#..##""") 82 | 83 | assert best_station(A3) == (Asteroid(11, 13), 210) 84 | 85 | 86 | asteroids = parse("""##.###.#.......#.#....#....#..........#. 87 | ....#..#..#.....#.##.............#...... 88 | ...#.#..###..#..#.....#........#......#. 89 | #......#.....#.##.#.##.##...#...#......# 90 | .............#....#.....#.#......#.#.... 91 | ..##.....#..#..#.#.#....##.......#.....# 92 | .#........#...#...#.#.....#.....#.#..#.# 93 | ...#...........#....#..#.#..#...##.#.#.. 94 | #.##.#.#...#..#...........#..........#.. 95 | ........#.#..#..##.#.##......##......... 96 | ................#.##.#....##.......#.... 97 | #............#.........###...#...#.....# 98 | #....#..#....##.#....#...#.....#......#. 99 | .........#...#.#....#.#.....#...#...#... 100 | .............###.....#.#...##........... 101 | ...#...#.......#....#.#...#....#...#.... 102 | .....#..#...#.#.........##....#...#..... 103 | ....##.........#......#...#...#....#..#. 104 | #...#..#..#.#...##.#..#.............#.## 105 | .....#...##..#....#.#.##..##.....#....#. 106 | ..#....#..#........#.#.......#.##..###.. 107 | ...#....#..#.#.#........##..#..#..##.... 108 | .......#.##.....#.#.....#...#........... 109 | ........#.......#.#...........#..###..## 110 | ...#.....#..#.#.......##.###.###...#.... 111 | ...............#..#....#.#....#....#.#.. 112 | #......#...#.....#.#........##.##.#..... 113 | ###.......#............#....#..#.#...... 114 | ..###.#.#....##..#.......#.............# 115 | ##.#.#...#.#..........##.#..#...##...... 116 | ..#......#..........#.#..#....##........ 117 | ......##.##.#....#....#..........#...#.. 118 | #.#..#..#.#...........#..#.......#..#.#. 119 | #.....#.#.........#............#.#..##.# 120 | .....##....#.##....#.....#..##....#..#.. 121 | .#.......#......#.......#....#....#..#.. 122 | ...#........#.#.##..#.#..#..#........#.. 123 | #........#.#......#..###....##..#......# 124 | ...#....#...#.....#.....#.##.#..#...#... 125 | #.#.....##....#...........#.....#...#...""") 126 | 127 | # print(best_station(asteroids)) 128 | 129 | def faux_angle(asteroid): 130 | dx, dy = asteroid 131 | if dx == 0 and dy < 0: 132 | # e.g. (0, -1), straight up 133 | return (0, 0) 134 | elif dx > 0 and dy < 0: 135 | # e.g. (0.1, -0.9) or (0.9, -0.1) 136 | return (1, dx / abs(dy)) 137 | elif dx > 0 and dy == 0: 138 | return (2, 0) 139 | elif dx > 0 and dy > 0: 140 | # e.g. (0.9, 0.1) or (0.1, 0.9) 141 | return (3, dy / dx) 142 | elif dx == 0 and dy > 0: 143 | return (4, 0) 144 | elif dx < 0 and dy > 0: 145 | # e.g. (-0.1, 0.9) or (-0.9, 0.1) 146 | return (5, abs(dx) / dy) 147 | elif dx < 0 and dy == 0: 148 | return (6, 0) 149 | elif dx < 0 and dy < 0: 150 | # e.g. (-0.9, -0.1) or (-0.1, -0.9) 151 | return (7, dy / dx) 152 | 153 | def iterate(asteroids: Asteroids, station: Asteroid) -> Iterator[Asteroid]: 154 | asteroids_by_angle = defaultdict(list) 155 | 156 | for x, y in asteroids: 157 | dx = x - station.x 158 | dy = y - station.y 159 | 160 | gcd = math.gcd(dx, dy) 161 | 162 | if dx == dy == 0: 163 | pass 164 | else: 165 | angle = (dx / gcd, dy / gcd) 166 | asteroids_by_angle[angle].append(Asteroid(x, y)) 167 | 168 | # sort by length descending for each angle 169 | for angle_asteroids in asteroids_by_angle.values(): 170 | angle_asteroids.sort(key=lambda a: abs(a.x - station.x) + abs(a.y - station.y), reverse=True) 171 | 172 | while asteroids_by_angle: 173 | keys = asteroids_by_angle.keys() # (dx, dy) 174 | keys = sorted(keys, key=faux_angle) 175 | 176 | for key in keys: 177 | angle_asteroids = asteroids_by_angle[key] 178 | yield angle_asteroids.pop() 179 | if not angle_asteroids: 180 | del asteroids_by_angle[key] 181 | 182 | NEW_ASTEROIDS = parse(""".#....#####...#.. 183 | ##...##.#####..## 184 | ##...#...#.#####. 185 | ..#.....#...###.. 186 | ..#.#.....#....##""") 187 | 188 | NEW_STATION = Asteroid(8, 3) 189 | 190 | station = best_station(asteroids)[0] 191 | vaporizations = list(iterate(asteroids, station)) 192 | 193 | print(vaporizations[199]) 194 | -------------------------------------------------------------------------------- /day11/day11.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple, Iterable, Set 6 | from enum import Enum 7 | import itertools 8 | from collections import deque, defaultdict 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int]) -> None: 53 | self.program = defaultdict(int) 54 | self.program.update({i: value for i, value in enumerate(program)}) 55 | self.inputs = deque() 56 | self.pos = 0 57 | self.relative_base = 0 58 | 59 | def _get_value(self, pos: int, mode: int) -> int: 60 | if mode == 0: 61 | # pointer mode 62 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 63 | return self.program[self.program[pos]] 64 | elif mode == 1: 65 | # immediate mode 66 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 67 | return self.program[pos] 68 | elif mode == 2: 69 | # relative mode 70 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 71 | return self.program[self.program[pos] + self.relative_base] 72 | else: 73 | raise ValueError(f"unknown mode: {mode}") 74 | 75 | def _loc(self, pos: int, mode: int) -> int: 76 | if mode == 0: 77 | # pointer mode 78 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 79 | return self.program[pos] 80 | elif mode == 2: 81 | # relative mode 82 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 83 | return self.program[pos] + self.relative_base 84 | 85 | def __call__(self, input_values: Iterable[int] = ()) -> int: 86 | self.inputs.extend(input_values) 87 | 88 | while True: 89 | logging.debug(f"program: {self.program}") 90 | logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 91 | 92 | opcode, modes = parse_opcode(self.program[self.pos]) 93 | 94 | logging.debug(f"opcode: {opcode}, modes: {modes}") 95 | 96 | if opcode == Opcode.END_PROGRAM: 97 | raise EndProgram 98 | 99 | elif opcode == Opcode.ADD: 100 | value1 = self._get_value(self.pos + 1, modes[0]) 101 | value2 = self._get_value(self.pos + 2, modes[1]) 102 | loc = self._loc(self.pos + 3, modes[2]) 103 | 104 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 105 | 106 | self.program[loc] = value1 + value2 107 | self.pos += 4 108 | 109 | elif opcode == Opcode.MULTIPLY: 110 | value1 = self._get_value(self.pos + 1, modes[0]) 111 | value2 = self._get_value(self.pos + 2, modes[1]) 112 | loc = self._loc(self.pos + 3, modes[2]) 113 | 114 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 115 | 116 | self.program[loc] = value1 * value2 117 | self.pos += 4 118 | 119 | elif opcode == Opcode.STORE_INPUT: 120 | # Get input and store at location 121 | loc = self._loc(self.pos + 1, modes[0]) 122 | input_value = self.inputs.popleft() 123 | self.program[loc] = input_value 124 | self.pos += 2 125 | 126 | elif opcode == Opcode.SEND_TO_OUTPUT: 127 | # Get output from location 128 | value = self._get_value(self.pos + 1, modes[0]) 129 | self.pos += 2 130 | logging.debug(f"output: {value}") 131 | return value 132 | 133 | elif opcode == Opcode.JUMP_IF_TRUE: 134 | # jump if true 135 | value1 = self._get_value(self.pos + 1, modes[0]) 136 | value2 = self._get_value(self.pos + 2, modes[1]) 137 | 138 | logging.debug(f"value1: {value1}, value2: {value2}") 139 | 140 | if value1 != 0: 141 | self.pos = value2 142 | else: 143 | self.pos += 3 144 | 145 | elif opcode == Opcode.JUMP_IF_FALSE: 146 | value1 = self._get_value(self.pos + 1, modes[0]) 147 | value2 = self._get_value(self.pos + 2, modes[1]) 148 | 149 | logging.debug(f"value1: {value1}, value2: {value2}") 150 | 151 | if value1 == 0: 152 | self.pos = value2 153 | else: 154 | self.pos += 3 155 | 156 | elif opcode == Opcode.LESS_THAN: 157 | value1 = self._get_value(self.pos + 1, modes[0]) 158 | value2 = self._get_value(self.pos + 2, modes[1]) 159 | loc = self._loc(self.pos + 3, modes[2]) 160 | 161 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 162 | 163 | if value1 < value2: 164 | self.program[loc] = 1 165 | else: 166 | self.program[loc] = 0 167 | self.pos += 4 168 | 169 | elif opcode == Opcode.EQUALS: 170 | value1 = self._get_value(self.pos + 1, modes[0]) 171 | value2 = self._get_value(self.pos + 2, modes[1]) 172 | loc = self._loc(self.pos + 3, modes[2]) 173 | 174 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 175 | 176 | if value1 == value2: 177 | self.program[loc] = 1 178 | else: 179 | self.program[loc] = 0 180 | self.pos += 4 181 | 182 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 183 | value = self._get_value(self.pos + 1, modes[0]) 184 | 185 | logging.debug(f"value: {value}") 186 | 187 | self.relative_base += value 188 | self.pos += 2 189 | 190 | else: 191 | raise ValueError(f"invalid opcode: {opcode}") 192 | 193 | 194 | def run(program: Program, inputs: List[int]) -> List[int]: 195 | outputs = [] 196 | computer = IntcodeComputer(program) 197 | 198 | try: 199 | while True: 200 | outputs.append(computer(inputs)) 201 | inputs = () 202 | except EndProgram: 203 | return outputs 204 | 205 | PROGRAM = [3,8,1005,8,319,1106,0,11,0,0,0,104,1,104,0,3,8,1002,8,-1,10,101,1,10,10,4,10,108,1,8,10,4,10,1001,8,0,28,2,1008,7,10,2,4,17,10,3,8,102,-1,8,10,101,1,10,10,4,10,1008,8,0,10,4,10,1002,8,1,59,3,8,1002,8,-1,10,101,1,10,10,4,10,1008,8,0,10,4,10,1001,8,0,81,1006,0,24,3,8,1002,8,-1,10,101,1,10,10,4,10,108,0,8,10,4,10,102,1,8,105,2,6,13,10,1006,0,5,3,8,1002,8,-1,10,101,1,10,10,4,10,108,0,8,10,4,10,1002,8,1,134,2,1007,0,10,2,1102,20,10,2,1106,4,10,1,3,1,10,3,8,102,-1,8,10,101,1,10,10,4,10,108,1,8,10,4,10,1002,8,1,172,3,8,1002,8,-1,10,1001,10,1,10,4,10,108,1,8,10,4,10,101,0,8,194,1,103,7,10,1006,0,3,1,4,0,10,3,8,1002,8,-1,10,1001,10,1,10,4,10,1008,8,1,10,4,10,101,0,8,228,2,109,0,10,1,101,17,10,1006,0,79,3,8,1002,8,-1,10,1001,10,1,10,4,10,108,0,8,10,4,10,1002,8,1,260,2,1008,16,10,1,1105,20,10,1,3,17,10,3,8,1002,8,-1,10,1001,10,1,10,4,10,1008,8,1,10,4,10,1002,8,1,295,1,1002,16,10,101,1,9,9,1007,9,1081,10,1005,10,15,99,109,641,104,0,104,1,21101,387365733012,0,1,21102,1,336,0,1105,1,440,21102,937263735552,1,1,21101,0,347,0,1106,0,440,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,3,10,104,0,104,1,3,10,104,0,104,0,3,10,104,0,104,1,21102,3451034715,1,1,21101,0,394,0,1105,1,440,21102,3224595675,1,1,21101,0,405,0,1106,0,440,3,10,104,0,104,0,3,10,104,0,104,0,21101,0,838337454440,1,21102,428,1,0,1105,1,440,21101,0,825460798308,1,21101,439,0,0,1105,1,440,99,109,2,22101,0,-1,1,21102,1,40,2,21101,0,471,3,21101,461,0,0,1106,0,504,109,-2,2106,0,0,0,1,0,0,1,109,2,3,10,204,-1,1001,466,467,482,4,0,1001,466,1,466,108,4,466,10,1006,10,498,1102,1,0,466,109,-2,2105,1,0,0,109,4,2101,0,-1,503,1207,-3,0,10,1006,10,521,21101,0,0,-3,21202,-3,1,1,22102,1,-2,2,21101,1,0,3,21102,540,1,0,1105,1,545,109,-4,2105,1,0,109,5,1207,-3,1,10,1006,10,568,2207,-4,-2,10,1006,10,568,22102,1,-4,-4,1106,0,636,22102,1,-4,1,21201,-3,-1,2,21202,-2,2,3,21102,587,1,0,1105,1,545,21201,1,0,-4,21101,0,1,-1,2207,-4,-2,10,1006,10,606,21102,0,1,-1,22202,-2,-1,-2,2107,0,-3,10,1006,10,628,22102,1,-1,1,21102,1,628,0,105,1,503,21202,-2,-1,-2,22201,-4,-2,-4,109,-5,2106,0,0] 206 | 207 | class Direction(Enum): 208 | UP = 0 209 | RIGHT = 1 210 | DOWN = 2 211 | LEFT = 3 212 | 213 | def turn(current_direction: Direction, should_turn: int) -> Direction: 214 | if current_direction == Direction.UP: 215 | return Direction.LEFT if should_turn == 0 else Direction.RIGHT 216 | elif current_direction == Direction.RIGHT: 217 | return Direction.UP if should_turn == 0 else Direction.DOWN 218 | elif current_direction == Direction.DOWN: 219 | return Direction.RIGHT if should_turn == 0 else Direction.LEFT 220 | elif current_direction == Direction.LEFT: 221 | return Direction.DOWN if should_turn == 0 else Direction.UP 222 | 223 | def move_forward(x: int, y: int, direction: Direction) -> Tuple[int, int]: 224 | if direction == Direction.UP: 225 | return x, y + 1 226 | elif direction == Direction.RIGHT: 227 | return x + 1, y 228 | elif direction == Direction.DOWN: 229 | return x, y - 1 230 | elif direction == Direction.LEFT: 231 | return x - 1, y 232 | 233 | def robot(program: Program) -> int: 234 | computer = IntcodeComputer(program) 235 | grid = defaultdict(int) 236 | grid[(0, 0)] = 1 237 | x = y = 0 238 | direction = Direction.UP 239 | painted = set() 240 | 241 | step = 0 242 | 243 | try: 244 | while True: 245 | step += 1 246 | print(step, len(painted)) 247 | # print(f"grid: {grid}") 248 | # print(f"x, y: {x} {y}") 249 | # print(f"direction: {direction}") 250 | current_color = grid[(x, y)] 251 | color_to_paint = computer([current_color]) 252 | # print(f"color to paint: {color_to_paint}") 253 | should_turn = computer() 254 | # print(f"should turn: {should_turn}") 255 | painted.add((x, y)) 256 | grid[(x, y)] = color_to_paint 257 | direction = turn(direction, should_turn) 258 | x, y = move_forward(x, y, direction) 259 | 260 | except EndProgram: 261 | return {pos for pos, color in grid.items() if color == 1} 262 | 263 | painted = robot(PROGRAM) 264 | 265 | def show(positions: Set[Tuple[int, int]]): 266 | x_min = min(x for (x, y) in positions) 267 | x_max = max(x for (x, y) in positions) 268 | y_min = min(y for x, y in positions) 269 | y_max = max(y for x, y in positions) 270 | 271 | for y in reversed(range(y_min, y_max + 1)): 272 | row = ["*" if (x, y) in positions else " " for x in range(x_min, x_max + 1)] 273 | print("".join(row)) 274 | 275 | show(painted) 276 | -------------------------------------------------------------------------------- /day12/day12.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, List 2 | from dataclasses import dataclass 3 | import copy 4 | 5 | @dataclass 6 | class XYZ: 7 | x: int 8 | y: int 9 | z: int 10 | 11 | def energy(self) -> int: 12 | return abs(self.x) + abs(self.y) + abs(self.z) 13 | 14 | 15 | class Moon: 16 | def __init__(self, position: XYZ, velocity: XYZ = None) -> None: 17 | self.position = position 18 | self.velocity = velocity or XYZ(0, 0, 0) 19 | 20 | def __repr__(self) -> str: 21 | return f"Moon(position={self.position}, velocity={self.velocity})" 22 | 23 | def potential_energy(self) -> int: 24 | return self.position.energy() 25 | 26 | def kinetic_energy(self) -> int: 27 | return self.velocity.energy() 28 | 29 | def total_energy(self) -> int: 30 | return self.potential_energy() * self.kinetic_energy() 31 | 32 | def apply_velocity(self) -> None: 33 | self.position.x += self.velocity.x 34 | self.position.y += self.velocity.y 35 | self.position.z += self.velocity.z 36 | 37 | def sig_x(self): 38 | return (self.position.x, self.velocity.x) 39 | 40 | def sig_y(self): 41 | return (self.position.y, self.velocity.y) 42 | 43 | def sig_z(self): 44 | return (self.position.z, self.velocity.z) 45 | 46 | 47 | def step(moons: List[Moon]) -> None: 48 | # first apply acceleration 49 | n = len(moons) 50 | for moon1 in moons: 51 | for moon2 in moons: 52 | if moon1 != moon2: 53 | # adjust the velocity of moon1 54 | if moon1.position.x < moon2.position.x: 55 | moon1.velocity.x += 1 56 | elif moon1.position.x > moon2.position.x: 57 | moon1.velocity.x -= 1 58 | 59 | if moon1.position.y < moon2.position.y: 60 | moon1.velocity.y += 1 61 | elif moon1.position.y > moon2.position.y: 62 | moon1.velocity.y -= 1 63 | 64 | if moon1.position.z < moon2.position.z: 65 | moon1.velocity.z += 1 66 | elif moon1.position.z > moon2.position.z: 67 | moon1.velocity.z -= 1 68 | 69 | # next apply velocity 70 | for moon in moons: 71 | moon.apply_velocity() 72 | 73 | MOONS = [ 74 | Moon(XYZ(-1, 0, 2)), 75 | Moon(XYZ(2, -10, -7)), 76 | Moon(XYZ(4, -8, 8)), 77 | Moon(XYZ(3, 5, -1)) 78 | ] 79 | 80 | 81 | moons = [ 82 | Moon(XYZ(0, 4, 0)), 83 | Moon(XYZ(-10, -6, -14)), 84 | Moon(XYZ(9, -16, -3)), 85 | Moon(XYZ(6, -1, 2)) 86 | ] 87 | 88 | def sig_x(moons: List[Moon]): 89 | return tuple(moon.sig_x() for moon in moons) 90 | 91 | def sig_y(moons: List[Moon]): 92 | return tuple(moon.sig_y() for moon in moons) 93 | 94 | def sig_z(moons: List[Moon]): 95 | return tuple(moon.sig_z() for moon in moons) 96 | 97 | 98 | def steps_to_repeat(moons: List[Moon], sig_fn) -> int: 99 | moons = copy.deepcopy(moons) 100 | 101 | seen = set() 102 | seen.add(sig_fn(moons)) 103 | 104 | num_steps = 0 105 | 106 | while True: 107 | num_steps += 1 108 | step(moons) 109 | sig = sig_fn(moons) 110 | if sig in seen: 111 | return num_steps 112 | else: 113 | seen.add(sig) 114 | 115 | print(steps_to_repeat(moons, sig_x)) 116 | print(steps_to_repeat(moons, sig_y)) 117 | print(steps_to_repeat(moons, sig_z)) 118 | -------------------------------------------------------------------------------- /day13/day13.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple, Iterable, Set, Dict 6 | from enum import Enum 7 | import itertools 8 | from collections import deque, defaultdict 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int]) -> None: 53 | self.program = defaultdict(int) 54 | self.program.update({i: value for i, value in enumerate(program)}) 55 | self.inputs = deque() 56 | self.pos = 0 57 | self.relative_base = 0 58 | 59 | def _get_value(self, pos: int, mode: int) -> int: 60 | if mode == 0: 61 | # pointer mode 62 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 63 | return self.program[self.program[pos]] 64 | elif mode == 1: 65 | # immediate mode 66 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 67 | return self.program[pos] 68 | elif mode == 2: 69 | # relative mode 70 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 71 | return self.program[self.program[pos] + self.relative_base] 72 | else: 73 | raise ValueError(f"unknown mode: {mode}") 74 | 75 | def _loc(self, pos: int, mode: int) -> int: 76 | if mode == 0: 77 | # pointer mode 78 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 79 | return self.program[pos] 80 | elif mode == 2: 81 | # relative mode 82 | logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 83 | return self.program[pos] + self.relative_base 84 | 85 | def __call__(self, input_values: Iterable[int] = ()) -> int: 86 | self.inputs.extend(input_values) 87 | 88 | while True: 89 | logging.debug(f"program: {self.program}") 90 | logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 91 | 92 | opcode, modes = parse_opcode(self.program[self.pos]) 93 | 94 | logging.debug(f"opcode: {opcode}, modes: {modes}") 95 | 96 | if opcode == Opcode.END_PROGRAM: 97 | raise EndProgram 98 | 99 | elif opcode == Opcode.ADD: 100 | value1 = self._get_value(self.pos + 1, modes[0]) 101 | value2 = self._get_value(self.pos + 2, modes[1]) 102 | loc = self._loc(self.pos + 3, modes[2]) 103 | 104 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 105 | 106 | self.program[loc] = value1 + value2 107 | self.pos += 4 108 | 109 | elif opcode == Opcode.MULTIPLY: 110 | value1 = self._get_value(self.pos + 1, modes[0]) 111 | value2 = self._get_value(self.pos + 2, modes[1]) 112 | loc = self._loc(self.pos + 3, modes[2]) 113 | 114 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 115 | 116 | self.program[loc] = value1 * value2 117 | self.pos += 4 118 | 119 | elif opcode == Opcode.STORE_INPUT: 120 | # Get input and store at location 121 | loc = self._loc(self.pos + 1, modes[0]) 122 | input_value = self.inputs.popleft() 123 | self.program[loc] = input_value 124 | self.pos += 2 125 | 126 | elif opcode == Opcode.SEND_TO_OUTPUT: 127 | # Get output from location 128 | value = self._get_value(self.pos + 1, modes[0]) 129 | self.pos += 2 130 | logging.debug(f"output: {value}") 131 | 132 | #### 133 | self.inputs.clear() 134 | #### 135 | 136 | return value 137 | 138 | elif opcode == Opcode.JUMP_IF_TRUE: 139 | # jump if true 140 | value1 = self._get_value(self.pos + 1, modes[0]) 141 | value2 = self._get_value(self.pos + 2, modes[1]) 142 | 143 | logging.debug(f"value1: {value1}, value2: {value2}") 144 | 145 | if value1 != 0: 146 | self.pos = value2 147 | else: 148 | self.pos += 3 149 | 150 | elif opcode == Opcode.JUMP_IF_FALSE: 151 | value1 = self._get_value(self.pos + 1, modes[0]) 152 | value2 = self._get_value(self.pos + 2, modes[1]) 153 | 154 | logging.debug(f"value1: {value1}, value2: {value2}") 155 | 156 | if value1 == 0: 157 | self.pos = value2 158 | else: 159 | self.pos += 3 160 | 161 | elif opcode == Opcode.LESS_THAN: 162 | value1 = self._get_value(self.pos + 1, modes[0]) 163 | value2 = self._get_value(self.pos + 2, modes[1]) 164 | loc = self._loc(self.pos + 3, modes[2]) 165 | 166 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 167 | 168 | if value1 < value2: 169 | self.program[loc] = 1 170 | else: 171 | self.program[loc] = 0 172 | self.pos += 4 173 | 174 | elif opcode == Opcode.EQUALS: 175 | value1 = self._get_value(self.pos + 1, modes[0]) 176 | value2 = self._get_value(self.pos + 2, modes[1]) 177 | loc = self._loc(self.pos + 3, modes[2]) 178 | 179 | logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 180 | 181 | if value1 == value2: 182 | self.program[loc] = 1 183 | else: 184 | self.program[loc] = 0 185 | self.pos += 4 186 | 187 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 188 | value = self._get_value(self.pos + 1, modes[0]) 189 | 190 | logging.debug(f"value: {value}") 191 | 192 | self.relative_base += value 193 | self.pos += 2 194 | 195 | else: 196 | raise ValueError(f"invalid opcode: {opcode}") 197 | 198 | class Tile(Enum): 199 | EMPTY = 0 200 | WALL = 1 201 | BLOCK = 2 202 | PADDLE = 3 203 | BALL = 4 204 | 205 | 206 | with open('day13.txt') as f: 207 | program = [int(n) for n in f.read().strip().split(",")] 208 | 209 | 210 | def count_blocks(program: Program) -> int: 211 | screen: Dict[Tuple[int, int], Tile] = {} 212 | computer = IntcodeComputer(program) 213 | 214 | try: 215 | while True: 216 | x = computer() 217 | y = computer() 218 | tile = Tile(computer()) 219 | screen[(x, y)] = tile 220 | except EndProgram: 221 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 222 | return screen 223 | # print(count_blocks(program)) 224 | 225 | # screen = count_blocks(program) 226 | 227 | def show(tile: Tile) -> str: 228 | if tile == Tile.BALL: 229 | return 'o' 230 | elif tile == Tile.BLOCK: 231 | return '#' 232 | elif tile == Tile.EMPTY: 233 | return ' ' 234 | elif tile == Tile.PADDLE: 235 | return '-' 236 | elif tile == Tile.WALL: 237 | return 'X' 238 | else: 239 | raise RuntimeError(f"bad tile: {tile}") 240 | 241 | def draw(screen: Dict[Tuple[int, int], Tile]) -> None: 242 | if not screen: 243 | return 244 | x_min = min(x for x, y in screen) 245 | x_max = max(x for x, y in screen) 246 | y_min = min(y for x, y in screen) 247 | y_max = max(y for x, y in screen) 248 | 249 | for y in range(y_min, y_max + 1): 250 | row = [show(screen.get((x, y), Tile.EMPTY)) for x in range(x_min, x_max + 1)] 251 | print("".join(row)) 252 | 253 | 254 | def play_breakout(program: Program) -> int: 255 | screen: Dict[Tuple[int, int], Tile] = {} 256 | score = 0 257 | program[0] = 2 258 | computer = IntcodeComputer(program) 259 | 260 | ball_x = None 261 | paddle_x = None 262 | num_blocks = 0 263 | 264 | step = 0 265 | 266 | try: 267 | while True: 268 | if ball_x is None or paddle_x is None: 269 | input_value = [] 270 | elif paddle_x < ball_x: 271 | input_value = [1] 272 | elif ball_x < paddle_x: 273 | input_value = [-1] 274 | else: 275 | input_value = [0] 276 | 277 | x = computer(input_value) 278 | y = computer() 279 | 280 | if x == -1 and y == 0: 281 | score = computer() 282 | print("score", score) 283 | print("ball x", ball_x) 284 | print("paddle x", paddle_x) 285 | print("input value", input_value) 286 | print("num_blocks", num_blocks) 287 | 288 | else: 289 | tile = Tile(computer()) 290 | previous_tile = screen.get((x, y), Tile.EMPTY) 291 | 292 | if tile == Tile.BLOCK and previous_tile != Tile.BLOCK: 293 | num_blocks += 1 294 | elif previous_tile == Tile.BLOCK and tile != Tile.BLOCK: 295 | num_blocks -= 1 296 | 297 | screen[(x, y)] = tile 298 | 299 | if tile == Tile.BALL: 300 | ball_x = x 301 | elif tile == Tile.PADDLE: 302 | paddle_x = x 303 | 304 | # if tile in (Tile.BALL, Tile.PADDLE): 305 | 306 | except EndProgram: 307 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 308 | return score 309 | # print(count_blocks(program)) 310 | 311 | print(play_breakout(program)) 312 | -------------------------------------------------------------------------------- /day13/day13.txt: -------------------------------------------------------------------------------- 1 | 1,380,379,385,1008,2571,639943,381,1005,381,12,99,109,2572,1102,0,1,383,1102,1,0,382,20101,0,382,1,20102,1,383,2,21102,37,1,0,1105,1,578,4,382,4,383,204,1,1001,382,1,382,1007,382,42,381,1005,381,22,1001,383,1,383,1007,383,23,381,1005,381,18,1006,385,69,99,104,-1,104,0,4,386,3,384,1007,384,0,381,1005,381,94,107,0,384,381,1005,381,108,1105,1,161,107,1,392,381,1006,381,161,1102,-1,1,384,1106,0,119,1007,392,40,381,1006,381,161,1101,0,1,384,21002,392,1,1,21102,21,1,2,21101,0,0,3,21102,138,1,0,1105,1,549,1,392,384,392,21002,392,1,1,21102,21,1,2,21101,3,0,3,21101,161,0,0,1105,1,549,1102,1,0,384,20001,388,390,1,20102,1,389,2,21102,180,1,0,1106,0,578,1206,1,213,1208,1,2,381,1006,381,205,20001,388,390,1,21002,389,1,2,21101,205,0,0,1105,1,393,1002,390,-1,390,1102,1,1,384,20101,0,388,1,20001,389,391,2,21101,0,228,0,1105,1,578,1206,1,261,1208,1,2,381,1006,381,253,21002,388,1,1,20001,389,391,2,21101,253,0,0,1105,1,393,1002,391,-1,391,1101,0,1,384,1005,384,161,20001,388,390,1,20001,389,391,2,21101,0,279,0,1106,0,578,1206,1,316,1208,1,2,381,1006,381,304,20001,388,390,1,20001,389,391,2,21102,304,1,0,1106,0,393,1002,390,-1,390,1002,391,-1,391,1101,1,0,384,1005,384,161,20102,1,388,1,20102,1,389,2,21102,1,0,3,21101,338,0,0,1106,0,549,1,388,390,388,1,389,391,389,21002,388,1,1,21001,389,0,2,21101,0,4,3,21101,0,365,0,1105,1,549,1007,389,22,381,1005,381,75,104,-1,104,0,104,0,99,0,1,0,0,0,0,0,0,233,19,18,1,1,21,109,3,21202,-2,1,1,22102,1,-1,2,21101,0,0,3,21101,414,0,0,1106,0,549,21202,-2,1,1,21201,-1,0,2,21101,0,429,0,1106,0,601,2101,0,1,435,1,386,0,386,104,-1,104,0,4,386,1001,387,-1,387,1005,387,451,99,109,-3,2106,0,0,109,8,22202,-7,-6,-3,22201,-3,-5,-3,21202,-4,64,-2,2207,-3,-2,381,1005,381,492,21202,-2,-1,-1,22201,-3,-1,-3,2207,-3,-2,381,1006,381,481,21202,-4,8,-2,2207,-3,-2,381,1005,381,518,21202,-2,-1,-1,22201,-3,-1,-3,2207,-3,-2,381,1006,381,507,2207,-3,-4,381,1005,381,540,21202,-4,-1,-1,22201,-3,-1,-3,2207,-3,-4,381,1006,381,529,21201,-3,0,-7,109,-8,2105,1,0,109,4,1202,-2,42,566,201,-3,566,566,101,639,566,566,2101,0,-1,0,204,-3,204,-2,204,-1,109,-4,2105,1,0,109,3,1202,-1,42,593,201,-2,593,593,101,639,593,593,21001,0,0,-2,109,-3,2105,1,0,109,3,22102,23,-2,1,22201,1,-1,1,21102,487,1,2,21101,326,0,3,21102,1,966,4,21101,630,0,0,1106,0,456,21201,1,1605,-2,109,-3,2106,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,2,2,2,0,0,0,2,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,2,0,2,0,2,0,0,0,0,2,2,2,0,1,1,0,2,0,2,0,2,0,0,2,2,0,2,2,2,2,0,0,0,2,0,0,2,2,2,0,2,2,0,2,0,0,0,0,2,0,0,0,0,0,0,1,1,0,0,0,0,2,0,0,0,2,0,0,2,0,0,0,0,2,0,0,0,2,2,0,2,0,2,2,0,2,2,0,2,0,2,2,2,0,0,2,0,1,1,0,0,2,2,2,2,2,2,2,0,0,2,0,2,0,0,0,2,0,2,0,0,2,0,0,0,0,2,0,0,2,2,0,2,0,0,2,2,0,0,1,1,0,2,2,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,2,0,0,1,1,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,2,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,2,0,2,2,2,0,2,2,0,0,0,0,0,0,2,0,2,0,0,2,2,0,0,0,2,0,2,2,0,2,0,0,0,2,0,1,1,0,0,0,0,2,0,2,2,2,2,2,0,2,0,2,2,0,0,2,2,0,2,2,2,0,2,0,2,2,0,2,2,0,2,2,2,2,2,0,0,1,1,0,0,2,0,0,0,2,0,2,2,2,2,2,0,2,0,0,2,2,0,0,2,0,2,2,0,0,2,0,2,0,2,2,2,0,2,2,2,2,0,1,1,0,2,0,0,0,2,0,0,2,0,0,0,2,0,2,0,2,2,0,0,0,0,2,2,2,2,2,2,0,0,2,2,0,0,2,2,0,2,0,0,1,1,0,2,0,0,0,2,0,0,0,0,2,2,0,0,2,2,2,0,0,0,0,0,2,0,0,0,2,0,2,0,0,2,0,2,2,2,2,0,0,0,1,1,0,0,0,0,0,0,2,2,0,2,0,2,2,0,2,0,2,2,0,2,0,0,0,0,2,0,0,0,2,2,0,2,2,0,0,0,2,2,0,0,1,1,0,0,2,0,2,2,0,2,2,2,0,0,2,2,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,1,1,0,0,2,0,0,2,0,2,0,2,0,0,2,2,2,2,2,0,2,0,0,2,2,0,0,0,2,0,0,0,2,2,0,2,0,0,2,0,0,0,1,1,0,2,2,0,0,2,0,0,0,0,0,0,2,0,2,0,0,2,0,0,0,2,0,2,0,2,2,0,0,2,0,0,0,0,0,2,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,85,93,24,97,81,73,90,65,8,89,80,17,22,56,24,44,93,59,34,85,16,26,8,85,58,86,32,75,36,78,65,80,67,83,36,22,47,30,4,17,49,44,80,79,66,53,32,98,63,63,38,69,39,8,74,77,35,93,58,2,40,74,19,95,85,34,13,26,46,94,7,77,1,80,44,80,57,25,40,36,5,66,24,4,26,69,28,34,96,28,14,47,60,2,76,36,90,20,84,9,72,34,80,30,81,91,19,50,38,65,78,80,91,43,52,59,32,98,13,11,74,84,5,64,4,92,11,51,11,36,12,94,80,7,36,15,19,78,76,81,21,88,8,31,50,23,22,80,54,72,6,6,12,81,79,80,85,33,60,67,53,8,98,35,34,11,10,34,18,42,65,14,30,89,21,85,32,50,73,4,28,81,10,94,65,65,5,14,25,47,62,4,35,62,70,66,71,88,94,83,13,28,92,93,75,86,4,7,94,15,43,74,47,3,46,82,71,56,37,95,45,29,90,48,51,19,16,65,10,7,81,98,13,96,49,90,64,94,14,68,73,35,19,34,75,37,7,5,87,44,71,52,12,27,92,27,81,63,95,38,49,61,7,92,65,37,47,63,13,2,62,70,65,24,17,59,31,7,1,15,9,48,52,74,38,27,8,53,80,66,6,72,35,39,79,11,89,80,90,53,84,7,84,64,75,15,40,48,82,30,28,98,23,90,88,92,5,34,7,23,41,87,93,65,46,90,29,24,38,56,15,62,6,89,68,81,93,43,31,60,51,45,25,15,88,25,11,88,93,60,87,4,3,93,8,50,62,27,54,7,6,18,44,65,33,69,23,45,44,69,49,11,73,81,64,18,6,74,7,77,53,18,6,98,62,35,81,48,33,64,26,16,55,17,71,75,72,39,6,54,67,97,68,59,65,3,70,75,91,98,87,41,94,16,1,14,27,11,19,52,53,18,47,27,29,59,6,37,86,71,12,62,84,26,36,22,74,95,77,59,66,70,95,91,2,59,39,50,81,51,75,87,30,22,30,64,54,53,64,33,37,88,24,90,24,32,36,95,39,54,6,85,10,15,53,82,13,41,50,10,67,87,51,87,15,80,27,10,32,97,77,90,41,34,45,14,11,95,79,58,31,49,59,86,50,89,22,92,68,51,74,28,38,16,68,29,6,23,37,19,55,71,5,73,38,66,12,21,45,51,69,89,51,2,9,61,62,97,92,74,23,8,23,82,56,74,9,9,73,69,3,25,81,18,67,64,5,86,96,49,22,86,93,36,15,28,31,55,62,81,78,66,65,55,68,71,92,66,65,48,64,75,16,42,26,12,49,27,33,40,66,43,37,51,90,32,96,63,96,42,61,70,97,96,79,67,59,12,17,81,12,39,64,80,1,28,2,83,35,20,54,69,97,11,96,96,22,60,92,79,28,35,32,24,19,96,85,26,20,38,72,60,41,62,14,51,54,60,45,35,37,17,85,53,27,1,87,55,25,48,46,10,70,16,13,16,77,10,73,70,65,58,57,62,16,59,51,70,93,55,42,79,46,12,88,28,96,53,82,61,75,67,58,13,42,11,64,3,76,15,60,40,6,57,71,68,26,48,57,80,61,28,88,44,92,49,49,38,46,37,40,34,1,18,97,72,49,53,49,35,88,44,5,29,81,6,21,87,77,25,36,91,33,22,74,22,85,55,39,98,21,6,43,26,2,60,36,34,72,39,25,40,51,37,65,69,48,4,18,42,49,13,52,80,39,65,94,92,27,34,58,83,93,5,34,2,11,90,37,73,77,84,68,63,78,63,34,32,47,26,90,72,61,89,5,78,26,80,92,29,36,76,48,53,61,53,14,42,1,2,34,37,36,16,70,27,1,50,26,29,92,53,58,19,90,47,27,35,30,72,76,54,85,33,26,65,77,39,7,3,8,67,87,25,12,43,74,95,97,12,23,47,62,70,62,22,98,76,17,14,77,50,91,20,15,42,34,20,65,79,87,27,44,12,95,2,97,97,43,62,76,65,48,71,83,72,33,44,17,65,40,86,12,89,97,54,36,62,78,65,15,7,37,25,31,3,16,33,91,56,67,66,29,61,40,91,32,27,13,8,55,89,54,69,76,16,67,66,54,7,75,84,6,64,15,29,56,88,57,49,26,57,70,19,3,62,79,61,62,55,49,16,94,94,53,79,639943 2 | -------------------------------------------------------------------------------- /day13/day13_optimized.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple, Iterable, Set, Dict, Callable 6 | from enum import Enum 7 | import itertools 8 | from collections import deque, defaultdict 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | # logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int], get_input: Callable[[], int]) -> None: 53 | self.program = program[:] 54 | self.get_input = get_input 55 | self.pos = 0 56 | self.relative_base = 0 57 | 58 | def _expand(self, pos: int) -> None: 59 | while len(self.program) <= pos: 60 | self.program.append(0) 61 | 62 | def _get_value(self, pos: int, mode: int) -> int: 63 | self._expand(pos) 64 | 65 | if mode == 0: 66 | # pointer mode 67 | self._expand(self.program[pos]) 68 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 69 | return self.program[self.program[pos]] 70 | elif mode == 1: 71 | # immediate mode 72 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 73 | return self.program[pos] 74 | elif mode == 2: 75 | # relative mode 76 | self._expand(self.program[pos] + self.relative_base) 77 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 78 | return self.program[self.program[pos] + self.relative_base] 79 | else: 80 | raise ValueError(f"unknown mode: {mode}") 81 | 82 | def _loc(self, pos: int, mode: int) -> int: 83 | self._expand(pos) 84 | 85 | if mode == 0: 86 | # pointer mode 87 | self._expand(self.program[pos]) 88 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 89 | return self.program[pos] 90 | elif mode == 2: 91 | # relative mode 92 | self._expand(self.program[pos] + self.relative_base) 93 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 94 | return self.program[pos] + self.relative_base 95 | 96 | def __call__(self, input_values: Iterable[int] = ()) -> int: 97 | self.inputs.extend(input_values) 98 | 99 | while True: 100 | # logging.debug(f"program: {self.program}") 101 | # logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 102 | 103 | opcode, modes = parse_opcode(self.program[self.pos]) 104 | 105 | # logging.debug(f"opcode: {opcode}, modes: {modes}") 106 | 107 | if opcode == Opcode.END_PROGRAM: 108 | raise EndProgram 109 | 110 | elif opcode == Opcode.ADD: 111 | value1 = self._get_value(self.pos + 1, modes[0]) 112 | value2 = self._get_value(self.pos + 2, modes[1]) 113 | loc = self._loc(self.pos + 3, modes[2]) 114 | 115 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 116 | 117 | self.program[loc] = value1 + value2 118 | self.pos += 4 119 | 120 | elif opcode == Opcode.MULTIPLY: 121 | value1 = self._get_value(self.pos + 1, modes[0]) 122 | value2 = self._get_value(self.pos + 2, modes[1]) 123 | loc = self._loc(self.pos + 3, modes[2]) 124 | 125 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 126 | 127 | self.program[loc] = value1 * value2 128 | self.pos += 4 129 | 130 | elif opcode == Opcode.STORE_INPUT: 131 | # Get input and store at location 132 | loc = self._loc(self.pos + 1, modes[0]) 133 | input_value = self.inputs.popleft() 134 | self.program[loc] = input_value 135 | self.pos += 2 136 | 137 | elif opcode == Opcode.SEND_TO_OUTPUT: 138 | # Get output from location 139 | value = self._get_value(self.pos + 1, modes[0]) 140 | self.pos += 2 141 | # logging.debug(f"output: {value}") 142 | 143 | #### 144 | self.inputs.clear() 145 | #### 146 | 147 | return value 148 | 149 | elif opcode == Opcode.JUMP_IF_TRUE: 150 | # jump if true 151 | value1 = self._get_value(self.pos + 1, modes[0]) 152 | value2 = self._get_value(self.pos + 2, modes[1]) 153 | 154 | # logging.debug(f"value1: {value1}, value2: {value2}") 155 | 156 | if value1 != 0: 157 | self.pos = value2 158 | else: 159 | self.pos += 3 160 | 161 | elif opcode == Opcode.JUMP_IF_FALSE: 162 | value1 = self._get_value(self.pos + 1, modes[0]) 163 | value2 = self._get_value(self.pos + 2, modes[1]) 164 | 165 | # logging.debug(f"value1: {value1}, value2: {value2}") 166 | 167 | if value1 == 0: 168 | self.pos = value2 169 | else: 170 | self.pos += 3 171 | 172 | elif opcode == Opcode.LESS_THAN: 173 | value1 = self._get_value(self.pos + 1, modes[0]) 174 | value2 = self._get_value(self.pos + 2, modes[1]) 175 | loc = self._loc(self.pos + 3, modes[2]) 176 | 177 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 178 | 179 | if value1 < value2: 180 | self.program[loc] = 1 181 | else: 182 | self.program[loc] = 0 183 | self.pos += 4 184 | 185 | elif opcode == Opcode.EQUALS: 186 | value1 = self._get_value(self.pos + 1, modes[0]) 187 | value2 = self._get_value(self.pos + 2, modes[1]) 188 | loc = self._loc(self.pos + 3, modes[2]) 189 | 190 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 191 | 192 | if value1 == value2: 193 | self.program[loc] = 1 194 | else: 195 | self.program[loc] = 0 196 | self.pos += 4 197 | 198 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 199 | value = self._get_value(self.pos + 1, modes[0]) 200 | 201 | # logging.debug(f"value: {value}") 202 | 203 | self.relative_base += value 204 | self.pos += 2 205 | 206 | else: 207 | raise ValueError(f"invalid opcode: {opcode}") 208 | 209 | class Tile(Enum): 210 | EMPTY = 0 211 | WALL = 1 212 | BLOCK = 2 213 | PADDLE = 3 214 | BALL = 4 215 | 216 | 217 | with open('day13.txt') as f: 218 | program = [int(n) for n in f.read().strip().split(",")] 219 | 220 | 221 | def count_blocks(program: Program) -> int: 222 | screen: Dict[Tuple[int, int], Tile] = {} 223 | computer = IntcodeComputer(program) 224 | 225 | try: 226 | while True: 227 | x = computer() 228 | y = computer() 229 | tile = Tile(computer()) 230 | screen[(x, y)] = tile 231 | except EndProgram: 232 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 233 | return screen 234 | # print(count_blocks(program)) 235 | 236 | # screen = count_blocks(program) 237 | 238 | def show(tile: Tile) -> str: 239 | if tile == Tile.BALL: 240 | return 'o' 241 | elif tile == Tile.BLOCK: 242 | return '#' 243 | elif tile == Tile.EMPTY: 244 | return ' ' 245 | elif tile == Tile.PADDLE: 246 | return '-' 247 | elif tile == Tile.WALL: 248 | return 'X' 249 | else: 250 | raise RuntimeError(f"bad tile: {tile}") 251 | 252 | def draw(screen: Dict[Tuple[int, int], Tile]) -> None: 253 | if not screen: 254 | return 255 | x_min = min(x for x, y in screen) 256 | x_max = max(x for x, y in screen) 257 | y_min = min(y for x, y in screen) 258 | y_max = max(y for x, y in screen) 259 | 260 | for y in range(y_min, y_max + 1): 261 | row = [show(screen.get((x, y), Tile.EMPTY)) for x in range(x_min, x_max + 1)] 262 | print("".join(row)) 263 | 264 | 265 | def play_breakout(program: Program) -> int: 266 | screen: Dict[Tuple[int, int], Tile] = {} 267 | score = 0 268 | program[0] = 2 269 | computer = IntcodeComputer(program) 270 | 271 | ball_x = None 272 | paddle_x = None 273 | num_blocks = 0 274 | 275 | step = 0 276 | 277 | try: 278 | while True: 279 | if ball_x is None or paddle_x is None: 280 | input_value = [] 281 | elif paddle_x < ball_x: 282 | input_value = [1] 283 | elif ball_x < paddle_x: 284 | input_value = [-1] 285 | else: 286 | input_value = [0] 287 | 288 | x = computer(input_value) 289 | y = computer() 290 | 291 | if x == -1 and y == 0: 292 | score = computer() 293 | print("score", score) 294 | print("ball x", ball_x) 295 | print("paddle x", paddle_x) 296 | print("input value", input_value) 297 | print("num_blocks", num_blocks) 298 | 299 | else: 300 | tile = Tile(computer()) 301 | previous_tile = screen.get((x, y), Tile.EMPTY) 302 | 303 | if tile == Tile.BLOCK and previous_tile != Tile.BLOCK: 304 | num_blocks += 1 305 | elif previous_tile == Tile.BLOCK and tile != Tile.BLOCK: 306 | num_blocks -= 1 307 | 308 | screen[(x, y)] = tile 309 | 310 | if tile == Tile.BALL: 311 | ball_x = x 312 | elif tile == Tile.PADDLE: 313 | paddle_x = x 314 | 315 | # if tile in (Tile.BALL, Tile.PADDLE): 316 | 317 | except EndProgram: 318 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 319 | return score 320 | # print(count_blocks(program)) 321 | 322 | print(play_breakout(program)) 323 | -------------------------------------------------------------------------------- /day13/intcode.py: -------------------------------------------------------------------------------- 1 | """ 2 | After solving this problem I went back and refactored this code again, 3 | under the assumption that I'd probably have to use it again on a later day. 4 | """ 5 | from typing import List, NamedTuple, Tuple, Iterable, Set, Dict, Callable 6 | from enum import Enum 7 | import itertools 8 | from collections import deque, defaultdict 9 | import logging 10 | 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | # logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int], get_input: Callable[[], int]) -> None: 53 | self.program = defaultdict(int) 54 | self.program.update({i: value for i, value in enumerate(program)}) 55 | self.get_input = get_input 56 | self.pos = 0 57 | self.relative_base = 0 58 | 59 | def _get_value(self, pos: int, mode: int) -> int: 60 | if mode == 0: 61 | # pointer mode 62 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 63 | return self.program[self.program[pos]] 64 | elif mode == 1: 65 | # immediate mode 66 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 67 | return self.program[pos] 68 | elif mode == 2: 69 | # relative mode 70 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 71 | return self.program[self.program[pos] + self.relative_base] 72 | else: 73 | raise ValueError(f"unknown mode: {mode}") 74 | 75 | def _loc(self, pos: int, mode: int) -> int: 76 | if mode == 0: 77 | # pointer mode 78 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 79 | return self.program[pos] 80 | elif mode == 2: 81 | # relative mode 82 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 83 | return self.program[pos] + self.relative_base 84 | 85 | def go(self) -> int: 86 | 87 | while True: 88 | # logging.debug(f"program: {self.program}") 89 | # logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 90 | 91 | opcode, modes = parse_opcode(self.program[self.pos]) 92 | 93 | # logging.debug(f"opcode: {opcode}, modes: {modes}") 94 | 95 | if opcode == Opcode.END_PROGRAM: 96 | raise EndProgram 97 | 98 | elif opcode == Opcode.ADD: 99 | value1 = self._get_value(self.pos + 1, modes[0]) 100 | value2 = self._get_value(self.pos + 2, modes[1]) 101 | loc = self._loc(self.pos + 3, modes[2]) 102 | 103 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 104 | 105 | self.program[loc] = value1 + value2 106 | self.pos += 4 107 | 108 | elif opcode == Opcode.MULTIPLY: 109 | value1 = self._get_value(self.pos + 1, modes[0]) 110 | value2 = self._get_value(self.pos + 2, modes[1]) 111 | loc = self._loc(self.pos + 3, modes[2]) 112 | 113 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 114 | 115 | self.program[loc] = value1 * value2 116 | self.pos += 4 117 | 118 | elif opcode == Opcode.STORE_INPUT: 119 | # Get input and store at location 120 | loc = self._loc(self.pos + 1, modes[0]) 121 | input_value = self.get_input() 122 | self.program[loc] = input_value 123 | self.pos += 2 124 | 125 | elif opcode == Opcode.SEND_TO_OUTPUT: 126 | # Get output from location 127 | value = self._get_value(self.pos + 1, modes[0]) 128 | self.pos += 2 129 | # logging.debug(f"output: {value}") 130 | 131 | #### 132 | #### 133 | 134 | return value 135 | 136 | elif opcode == Opcode.JUMP_IF_TRUE: 137 | # jump if true 138 | value1 = self._get_value(self.pos + 1, modes[0]) 139 | value2 = self._get_value(self.pos + 2, modes[1]) 140 | 141 | # logging.debug(f"value1: {value1}, value2: {value2}") 142 | 143 | if value1 != 0: 144 | self.pos = value2 145 | else: 146 | self.pos += 3 147 | 148 | elif opcode == Opcode.JUMP_IF_FALSE: 149 | value1 = self._get_value(self.pos + 1, modes[0]) 150 | value2 = self._get_value(self.pos + 2, modes[1]) 151 | 152 | # logging.debug(f"value1: {value1}, value2: {value2}") 153 | 154 | if value1 == 0: 155 | self.pos = value2 156 | else: 157 | self.pos += 3 158 | 159 | elif opcode == Opcode.LESS_THAN: 160 | value1 = self._get_value(self.pos + 1, modes[0]) 161 | value2 = self._get_value(self.pos + 2, modes[1]) 162 | loc = self._loc(self.pos + 3, modes[2]) 163 | 164 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 165 | 166 | if value1 < value2: 167 | self.program[loc] = 1 168 | else: 169 | self.program[loc] = 0 170 | self.pos += 4 171 | 172 | elif opcode == Opcode.EQUALS: 173 | value1 = self._get_value(self.pos + 1, modes[0]) 174 | value2 = self._get_value(self.pos + 2, modes[1]) 175 | loc = self._loc(self.pos + 3, modes[2]) 176 | 177 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 178 | 179 | if value1 == value2: 180 | self.program[loc] = 1 181 | else: 182 | self.program[loc] = 0 183 | self.pos += 4 184 | 185 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 186 | value = self._get_value(self.pos + 1, modes[0]) 187 | 188 | # logging.debug(f"value: {value}") 189 | 190 | self.relative_base += value 191 | self.pos += 2 192 | 193 | else: 194 | raise ValueError(f"invalid opcode: {opcode}") 195 | 196 | class Tile(Enum): 197 | EMPTY = 0 198 | WALL = 1 199 | BLOCK = 2 200 | PADDLE = 3 201 | BALL = 4 202 | 203 | 204 | with open('day13.txt') as f: 205 | program = [int(n) for n in f.read().strip().split(",")] 206 | 207 | 208 | def count_blocks(program: Program) -> int: 209 | screen: Dict[Tuple[int, int], Tile] = {} 210 | computer = IntcodeComputer(program) 211 | 212 | try: 213 | while True: 214 | x = computer.go() 215 | y = computer.go() 216 | tile = Tile(computer.go()) 217 | screen[(x, y)] = tile 218 | except EndProgram: 219 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 220 | return screen 221 | # print(count_blocks(program)) 222 | 223 | # screen = count_blocks(program) 224 | 225 | def show(tile: Tile) -> str: 226 | if tile == Tile.BALL: 227 | return 'o' 228 | elif tile == Tile.BLOCK: 229 | return '#' 230 | elif tile == Tile.EMPTY: 231 | return ' ' 232 | elif tile == Tile.PADDLE: 233 | return '-' 234 | elif tile == Tile.WALL: 235 | return 'X' 236 | else: 237 | raise RuntimeError(f"bad tile: {tile}") 238 | 239 | def draw(screen: Dict[Tuple[int, int], Tile]) -> None: 240 | if not screen: 241 | return 242 | x_min = min(x for x, y in screen) 243 | x_max = max(x for x, y in screen) 244 | y_min = min(y for x, y in screen) 245 | y_max = max(y for x, y in screen) 246 | 247 | for y in range(y_min, y_max + 1): 248 | row = [show(screen.get((x, y), Tile.EMPTY)) for x in range(x_min, x_max + 1)] 249 | print("".join(row)) 250 | 251 | 252 | def play_breakout(program: Program) -> int: 253 | screen: Dict[Tuple[int, int], Tile] = {} 254 | score = 0 255 | program[0] = 2 256 | 257 | ball_x = None 258 | paddle_x = None 259 | 260 | def get_input() -> int: 261 | if ball_x is None or paddle_x is None: 262 | raise RuntimeError("too early for input") 263 | elif paddle_x < ball_x: 264 | return 1 265 | elif ball_x < paddle_x: 266 | return -1 267 | else: 268 | return 0 269 | 270 | 271 | computer = IntcodeComputer(program, get_input) 272 | 273 | num_blocks = 0 274 | 275 | step = 0 276 | 277 | try: 278 | while True: 279 | x = computer.go() 280 | y = computer.go() 281 | 282 | if x == -1 and y == 0: 283 | score = computer.go() 284 | print("score", score) 285 | print("ball x", ball_x) 286 | print("paddle x", paddle_x) 287 | print("input value", get_input()) 288 | print("num_blocks", num_blocks) 289 | 290 | else: 291 | tile = Tile(computer.go()) 292 | previous_tile = screen.get((x, y), Tile.EMPTY) 293 | 294 | if tile == Tile.BLOCK and previous_tile != Tile.BLOCK: 295 | num_blocks += 1 296 | elif previous_tile == Tile.BLOCK and tile != Tile.BLOCK: 297 | num_blocks -= 1 298 | 299 | screen[(x, y)] = tile 300 | 301 | if tile == Tile.BALL: 302 | ball_x = x 303 | elif tile == Tile.PADDLE: 304 | paddle_x = x 305 | 306 | # if tile in (Tile.BALL, Tile.PADDLE): 307 | 308 | except EndProgram: 309 | # return len([tile for tile in screen.values() if tile == Tile.BLOCK]) 310 | return score 311 | # print(count_blocks(program)) 312 | 313 | print(play_breakout(program)) 314 | -------------------------------------------------------------------------------- /day14/day14.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, List 2 | import math 3 | 4 | class Amount(NamedTuple): 5 | chemical: str 6 | quantity: int 7 | 8 | @staticmethod 9 | def from_string(raw: str) -> 'Amount': 10 | qty, chemical = raw.strip().split(" ") 11 | return Amount(chemical, int(qty)) 12 | 13 | class Rule(NamedTuple): 14 | inputs: List[Amount] 15 | output: Amount 16 | 17 | # 18 | 19 | def parse_rule(raw: str) -> Rule: 20 | lhs, rhs = raw.split(" => ") 21 | inputs = lhs.split(", ") 22 | input_amounts = [Amount.from_string(inp) for inp in inputs] 23 | output_amount = Amount.from_string(rhs) 24 | return Rule(input_amounts, output_amount) 25 | 26 | assert parse_rule("7 A, 1 D => 1 E") == Rule([Amount("A", 7), Amount("D", 1)], Amount("E", 1)) 27 | 28 | RAW = """10 ORE => 10 A 29 | 1 ORE => 1 B 30 | 7 A, 1 B => 1 C 31 | 7 A, 1 C => 1 D 32 | 7 A, 1 D => 1 E 33 | 7 A, 1 E => 1 FUEL""" 34 | 35 | RULES = [parse_rule(raw) for raw in RAW.split("\n")] 36 | 37 | 38 | with open('day14.txt') as f: 39 | raw = f.read() 40 | rules = [parse_rule(line) for line in raw.split("\n")] 41 | 42 | def least_ore(rules: List[Rule], fuel_needed: int = 1): 43 | rules_by_product = {rule.output.chemical: rule for rule in rules} 44 | 45 | requirements = {"FUEL": fuel_needed} 46 | ore_needed = 0 47 | 48 | def done() -> bool: 49 | return all(qty <= 0 for qty in requirements.values()) 50 | 51 | 52 | while not done(): 53 | key = next(iter(chem for chem, qty in requirements.items() if qty > 0)) 54 | qty_needed = requirements[key] 55 | 56 | rule = rules_by_product[key] 57 | # let's say I need 5 but the rule produces 3, then I need to run the rule twice 58 | num_times = math.ceil(qty_needed / rule.output.quantity) 59 | requirements[key] -= num_times * rule.output.quantity 60 | 61 | for amount in rule.inputs: 62 | if amount.chemical == "ORE": 63 | ore_needed += amount.quantity * num_times 64 | else: 65 | requirements[amount.chemical] = requirements.get(amount.chemical, 0) + num_times * amount.quantity 66 | 67 | return ore_needed 68 | 69 | RAW2 = """157 ORE => 5 NZVS 70 | 165 ORE => 6 DCFZ 71 | 44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL 72 | 12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ 73 | 179 ORE => 7 PSHF 74 | 177 ORE => 5 HKGWZ 75 | 7 DCFZ, 7 PSHF => 2 XJWVT 76 | 165 ORE => 2 GPVTF 77 | 3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT""" 78 | 79 | RULES2 = [parse_rule(raw) for raw in RAW2.split("\n")] 80 | 81 | 82 | print(least_ore(rules)) 83 | 84 | lo = 1000000 85 | hi = 10000000 86 | ore_lo = least_ore(rules, lo) 87 | ore_hi = least_ore(rules, hi) 88 | 89 | t = 1_000_000_000_000 90 | 91 | while lo < hi: 92 | mid = (lo + hi) // 2 93 | 94 | ore_mid = least_ore(rules,mid) 95 | 96 | print(lo, mid, hi) 97 | print(ore_lo, ore_mid, ore_hi) 98 | 99 | if ore_mid <= t: 100 | lo, ore_lo = mid, ore_mid 101 | else: 102 | hi, ore_hi = mid, ore_mid 103 | 104 | -------------------------------------------------------------------------------- /day14/day14.txt: -------------------------------------------------------------------------------- 1 | 1 HKCVW, 2 DFCT => 5 ZJZRN 2 | 8 TCPN, 7 XHTJF, 3 DFCT => 8 ZKCXK 3 | 1 ZJZRN, 4 NZVL, 1 NJFXK, 7 RHJCQ, 32 MCQS, 1 XFNPT => 5 ZWQX 4 | 10 DRWB, 16 JBHKV => 6 TCPN 5 | 3 MBFK => 7 DRWB 6 | 9 RHJCQ => 6 MBMKZ 7 | 1 BVFPF => 2 KRTGD 8 | 1 QNXC, 7 BKNQT, 1 XFNPT => 4 VNFJQ 9 | 2 TCPN, 1 WFSV => 2 TGJP 10 | 35 DFCT => 2 RHJCQ 11 | 1 SKBV, 7 CTRH => 8 QGDSV 12 | 8 VSRMJ, 1 BVFPF => 4 CTRH 13 | 1 WMCD => 3 FPZLF 14 | 13 CVJQG, 8 DXBZJ => 9 QBDQ 15 | 1 XSRWM => 5 GDJGV 16 | 132 ORE => 3 MBFK 17 | 2 BQGP => 9 LZKJZ 18 | 5 GZLHP => 7 WFSV 19 | 2 RXSZS, 10 MBFK, 1 BPNVK => 2 GZLHP 20 | 13 BZFH => 8 XSRWM 21 | 3 QLSVN => 3 SKBV 22 | 8 QBDQ => 4 VSRMJ 23 | 1 RXSZS => 9 CVJQG 24 | 3 MBFK => 3 BVFPF 25 | 7 GZLHP, 4 MBFK, 5 CVJQG => 8 XHTJF 26 | 1 GZLHP => 2 DFCT 27 | 4 SZDWB, 4 RHJCQ, 1 WMCD => 3 RGZDK 28 | 2 BRXLV => 8 DXBZJ 29 | 192 ORE => 7 RXSZS 30 | 1 PRMR, 6 DFCT => 5 SZDWB 31 | 104 ORE => 9 BPNVK 32 | 6 VLJWQ, 8 ZKCXK, 6 BKNQT, 26 JRXQ, 7 FPZLF, 6 HKCVW, 18 KRTGD => 4 RBFX 33 | 7 XFNPT, 1 GDJGV => 2 HJDB 34 | 15 SKBV, 8 DRWB, 12 RXSZS => 3 GHQPH 35 | 1 BZFH => 5 GCBR 36 | 1 TGJP, 6 SKBV => 1 BZFH 37 | 4 KRTGD, 1 ZJHKP, 1 LZKJZ, 1 VNFJQ, 6 QBDQ, 1 PRMR, 1 NJFXK, 1 HJDB => 8 TFQH 38 | 10 BVFPF, 1 RGZDK => 8 QNXC 39 | 1 XHTJF => 5 JRXQ 40 | 3 XKTMK, 4 QGDSV => 3 ZJHKP 41 | 2 BZFH => 7 PRMR 42 | 1 BPNVK, 1 RXSZS => 5 JBHKV 43 | 10 XHTJF => 9 BKNQT 44 | 1 JBHKV, 2 XHTJF => 8 QLSVN 45 | 24 VNFJQ, 42 TFQH, 39 RBFX, 1 ZWQX, 7 VBHVQ, 26 DRWB, 21 NJFXK => 1 FUEL 46 | 26 WBKQ, 14 XHTJF => 5 BQGP 47 | 5 WBKQ, 7 MBMKZ => 3 LQGC 48 | 6 LQGC => 5 NZVL 49 | 13 KRTGD, 5 GHQPH => 9 VLJWQ 50 | 117 ORE => 4 BRXLV 51 | 3 XKTMK, 1 PRMR => 2 MCQS 52 | 3 DRWB, 7 BVFPF, 4 TCPN => 7 NJFXK 53 | 10 VHFCR, 13 JZQJ => 5 XKTMK 54 | 17 CVJQG, 4 GCBR => 9 HKCVW 55 | 22 DFCT, 17 TGJP => 2 WBKQ 56 | 2 JZQJ, 12 XFNPT, 1 BQGP => 2 VBHVQ 57 | 12 HKCVW => 1 JZQJ 58 | 1 XSRWM => 3 WMCD 59 | 12 BZFH, 14 SKBV, 1 CTRH => 4 XFNPT 60 | 7 ZKCXK => 6 VHFCR -------------------------------------------------------------------------------- /day16/day16.py: -------------------------------------------------------------------------------- 1 | from typing import List, Iterator 2 | import itertools 3 | 4 | def pattern(output_element: int) -> Iterator[int]: 5 | while True: 6 | for _ in range(output_element): 7 | yield 0 8 | for _ in range(output_element): 9 | yield 1 10 | for _ in range(output_element): 11 | yield 0 12 | for _ in range(output_element): 13 | yield -1 14 | 15 | def ones_digit(n: int) -> int: 16 | if n > 0: 17 | return n % 10 18 | else: 19 | return (-n) % 10 20 | 21 | def fft_phase(numbers: List[int]) -> List[int]: 22 | output = [] 23 | n = len(numbers) 24 | for i in range(n): 25 | pat = pattern(i + 1) 26 | next(pat) 27 | 28 | values = list(zip(pat, numbers)) 29 | #print(values) 30 | total = sum(p * n for p, n in values) 31 | #print(total) 32 | output.append(ones_digit(total)) 33 | return output 34 | 35 | offset = 5971981 36 | raw = "59719811742386712072322509550573967421647565332667367184388997335292349852954113343804787102604664096288440135472284308373326245877593956199225516071210882728614292871131765110416999817460140955856338830118060988497097324334962543389288979535054141495171461720836525090700092901849537843081841755954360811618153200442803197286399570023355821961989595705705045742262477597293974158696594795118783767300148414702347570064139665680516053143032825288231685962359393267461932384683218413483205671636464298057303588424278653449749781937014234119757220011471950196190313903906218080178644004164122665292870495547666700781057929319060171363468213087408071790" 37 | numbers = [int(c) for c in raw] 38 | mega = numbers * 10_000 39 | 40 | 41 | # for _ in range(100): 42 | # numbers = fft_phase(numbers) 43 | # print(numbers[:8]) 44 | 45 | 46 | # now repeated 10000 times 47 | # so 6 million numbers long 48 | # each phase is 6 million * 6 million, which is too much 49 | 50 | # we only want 8 specific digits 51 | 52 | # 0, -1, 0, 1, 0, -1, 0, 1 53 | # s[3] + s[7] + s[11] + ... - s[1] - s[5] - ... 54 | 55 | # 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0, -1, -1 56 | # s[0] + s[7] + s[8] + s[15] + s[16] + .... 57 | # - s[3] - s[4] - s[10] - s[11] - .... 58 | 59 | ### part 2 60 | 61 | def part2(raw: str) -> List[int]: 62 | offset = int(raw[:7]) 63 | numbers = [int(c) for c in raw] * 10_000 64 | 65 | assert offset > len(numbers) // 2 66 | 67 | # pattern is tail([0] * n, [1] * n, [0] * n, [-1] * n) 68 | # in particular, pattern is 0 up until place n 69 | # in particular, if n >= len(numbers) // 2, pattern is just 1 starting at n until the end 70 | 71 | # that means we only need to sum up until the end 72 | for _ in range(100): 73 | 74 | # last position 75 | pos = len(numbers) - 1 76 | total = 0 77 | 78 | while pos >= offset: 79 | total += numbers[pos] 80 | numbers[pos] = ones_digit(total) 81 | pos -= 1 82 | 83 | 84 | return numbers[offset:offset+8] 85 | 86 | -------------------------------------------------------------------------------- /day17/Untitled-1: -------------------------------------------------------------------------------- 1 | L8,R12,R12,R10,R10,R12,R10,L8,R12,R12,R10,R10,R12,R10,L10,R10,L6,L10,R10,L6,R10,R12,R10,L8,R10,R12,R10,R10,R12,R10,L10,R10,L6 -------------------------------------------------------------------------------- /day17/day17.txt: -------------------------------------------------------------------------------- 1 | ............>###########>...................................... 2 | ............#...........#...................................... 3 | ............#.^###########>.................................... 4 | ............#.#.........#.#.................................... 5 | ............#.#.......######^.................................. 6 | ............#.#.........#.#.#.................................. 7 | ............#.#.........#.#.#.......................^#########> 8 | ............#.#.........#.#.#.......................#.........# 9 | ..^#########>.#.........#.#.#.......................#.........# 10 | ..#...........#.........#.#.#.......................#.........# 11 | ^###########>.#.........#.#.#.......................#.........# 12 | #.#.........#.#.........#.#.#.......................#.........# 13 | #.#.........#.<#########v.v#########>.....^#########>.........# 14 | #.#.........#...............#.......#.....#...................# 15 | #.#.........#...............<#########^.^#########>...........# 16 | #.#.........#.......................#.#.#.#.......#...........# 17 | #.#.........#.......................#.#.#.#.......#...........# 18 | #.#.........#.......................#.#.#.#.......#...........# 19 | #.#.........#.......................#.#.#.#.......#.<#########v 20 | #.#.........#.......................#.#.#.#.......#.#.......... 21 | #.<##########.......................#.#.#.#.......#.#.......... 22 | #...................................#.#.#.#.......#.#.......... 23 | ########^...........................v#####>.......#.#.......... 24 | ......................................#.#.........#.#.......... 25 | ......................................<###########v.#.......... 26 | ........................................#...........#.......... 27 | ........................................<###########v.......... 28 | 29 | -------------------------------------------------------------------------------- /day18/day18.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple, Dict, Set, Tuple 2 | from collections import deque 3 | import heapq 4 | 5 | class XY(NamedTuple): 6 | x: int 7 | y: int 8 | 9 | 10 | class Grid(NamedTuple): 11 | walls: Set[XY] 12 | keys: Dict[XY, str] 13 | doors: Dict[XY, str] 14 | start: XY 15 | 16 | @staticmethod 17 | def parse(raw: str) -> 'Grid': 18 | walls = set() 19 | keys = {} 20 | doors = {} 21 | 22 | lines = raw.strip().split("\n") 23 | 24 | for i, line in enumerate(lines): 25 | for j, c in enumerate(line): 26 | loc = XY(i, j) 27 | if c == '#': 28 | walls.add(loc) 29 | elif c == '@': 30 | start = loc 31 | elif 'a' <= c <= 'z': 32 | keys[loc] = c 33 | elif 'A' <= c <= 'Z': 34 | doors[loc] = c 35 | 36 | return Grid(walls, keys, doors, start) 37 | 38 | deltas = [(0, 1), (0, -1), (1, 0), (-1, 0)] 39 | 40 | 41 | def one_source_shortest_path(grid: Grid, start: XY): 42 | # key is key_name, value is pair (distance, doors_passed_through) 43 | results = {} 44 | visited = {start} 45 | 46 | frontier = deque([(0, start, [])]) 47 | 48 | while frontier: 49 | num_steps, (x, y), doors = frontier.popleft() 50 | for dx, dy in deltas: 51 | new_pos = XY(x + dx, y + dy) 52 | 53 | if new_pos in visited or new_pos in grid.walls: 54 | continue 55 | 56 | visited.add(new_pos) 57 | 58 | if new_pos in grid.keys: 59 | key = grid.keys[new_pos] 60 | results[key] = (num_steps + 1, doors) 61 | frontier.append((num_steps + 1, new_pos, doors)) 62 | elif new_pos in grid.doors: 63 | new_doors = doors + [grid.doors[new_pos]] 64 | frontier.append((num_steps + 1, new_pos, new_doors)) 65 | else: 66 | frontier.append((num_steps + 1, new_pos, doors)) 67 | 68 | return results 69 | 70 | 71 | def all_source_shortest_path(grid: Grid): 72 | results = {} 73 | 74 | results['@'] = one_source_shortest_path(grid, grid.start) 75 | for key_loc, key in grid.keys.items(): 76 | results[key] = one_source_shortest_path(grid, key_loc) 77 | 78 | return results 79 | 80 | 81 | 82 | RAW = """######### 83 | #b.A.@.a# 84 | #########""" 85 | 86 | GRID = Grid.parse(RAW) 87 | 88 | GRID2 = Grid.parse("""################# 89 | #i.G..c...e..H.p# 90 | ########.######## 91 | #j.A..b...f..D.o# 92 | ########@######## 93 | #k.E..a...g..B.n# 94 | ########.######## 95 | #l.F..d...h..C.m# 96 | #################""") 97 | 98 | GRID3 = Grid.parse("""######################## 99 | #@..............ac.GI.b# 100 | ###d#e#f################ 101 | ###A#B#C################ 102 | ###g#h#i################ 103 | ########################""") 104 | 105 | def signature(prev_keys: Set[str], loc: str) -> str: 106 | return f"{loc}:{''.join(sorted(prev_keys))}" 107 | 108 | def shortest_path(grid: Grid) -> int: 109 | assp = all_source_shortest_path(grid) 110 | seen_signatures = set() 111 | 112 | num_keys = len(grid.keys) 113 | # maintain priority queue of num_steps, key at, keys had 114 | pq = [(0, '@', set())] 115 | 116 | while pq: 117 | num_steps, source_key, keys_had = heapq.heappop(pq) 118 | sig = signature(keys_had, source_key) 119 | if sig in seen_signatures: 120 | continue 121 | seen_signatures.add(sig) 122 | 123 | print(num_steps, source_key, keys_had) 124 | 125 | if len(keys_had) == num_keys: 126 | return num_steps 127 | 128 | ossp = assp[source_key] 129 | for dest_key, (steps_to_key, doors) in ossp.items(): 130 | if dest_key in keys_had: 131 | continue 132 | if any(door.lower() not in keys_had for door in doors): 133 | continue 134 | 135 | new_keys = keys_had | {dest_key} 136 | heapq.heappush(pq, (num_steps + steps_to_key, dest_key, new_keys)) 137 | 138 | 139 | 140 | with open('day18.txt') as f: 141 | grid = Grid.parse(f.read()) 142 | 143 | # print(shortest_path(grid)) -------------------------------------------------------------------------------- /day18/day18.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | #...............#.......#...............#.........#.......#.......#.....#.......# 3 | #.#########.#.###.#####.#.#.#######.#####.#######.#.#######.#.###.###.#.#.#####.# 4 | #.#.#.....#.#.#...#...#..f#...#.....#...#...#.....#.........#...#.....#...#m....# 5 | #.#.#.###.###.#.#####.#######.#######.#.#.#.#.#######.#########.###########.##### 6 | #...#...#.#...#.......#.....#.#.......#.#.#.#...#...#...#.......#..h......#.....# 7 | ###.###.#.#.#########.#.###.#T#.#######.###.###.#B#.#.###.#######.#######.#####.# 8 | #...#...#...#.....#...#...#.#...#.....#.#...#.#...#.#.#...#.........#...#.......# 9 | #####.#######.###.#.###.#.#######.###.#.#.###.#####.###.#.###########.#.#######.# 10 | #...#...#.....#.#...#.#.#.....#...#.....#.#.....#...#...#.#...........#.#.....#.# 11 | #.#.###.#.#####.#####.#.#####.#.###.#####.#.###.#.###.#####.###########.#.###.#.# 12 | #.#.....#.#.....#.........#...#...#.#...#.#...#..x#...W.....#.....#.....#.#...#.# 13 | #.#######.###.#.#########.#.#####.###.#.#.###.#######.#######.###.#.#####.#.##### 14 | #.........#...#.......#.#.#.#...#.....#.#.#.#.#.....#...#.....#...#.#.....#.....# 15 | #.#########O#########.#.#.#.#.#.#.#####.#.#.#.###.#.#.###.#####.###.#.#########.# 16 | #.#.........#.........#...#.#.#.#...#.#.#.#.......#.#.#...#...#.#.......#.....#.# 17 | #.#####.#########.#########.#.#.###.#.#.#.#########.###.###.###.#####.###.###.#.# 18 | #.....#...#.....#.#.........#.#.#.....#.#.....#.....#...#...........#.#...#e..#.# 19 | #####.###.#K###.#.#####.#####.#.#######.#####.#####.#.#.###########.###.###.###.# 20 | #...#.....#.#...#.......#.....#.#.......#.....#...#...#.#.........#.....#.#.#...# 21 | #.#####.###.#.#########.#.#####.#.#####.#.#####.#.#######.#####.#.#######.#.#.#.# 22 | #.#j..#.#...#a..#.....#.#.#.......#.....#.......#...........#.#.#.......#.#...#.# 23 | #.#.#.#.#.#####.#.#####.#.#############.#.###################.#########.#.#####.# 24 | #...#.#.#.#...#...#.....#.#..z..#.....#.#.....#.......#.....#...#...#...#...#...# 25 | #.###.#.#.###.#####.#####.#.###.#.###.#######.#.#.#####.###.###.#.#.#.###.###.### 26 | #.#...#.#.....#.#...#...#.#...#.#.#.#...#.....#.#.....#...#.....#.#...#.#...#...# 27 | ###.###.#####.#.#.###.#.#.###.#.#.#.###.#.###########.###.#######.#####.#.#.###.# 28 | #...#...#...#.#.......#.#.....#.#.....#.#.#.............#...#...#...#.....#.#...# 29 | #.#.#.#####.#.#############.###.###.###.#.#.#########.#####.###.#.#.#####.#.#.### 30 | #.#.#.#.....#.............#...#.....#...#.#.....#...#.#.....#...#.#.#...#.#.#.#.# 31 | #.###.#.#.###############.###.#####.#.#.#.#####.#.#.#.#.#####.###.#.#.#.#.#.#.#.# 32 | #.#...#.#.......#.........#...#...#.#.#.#.#.....#.#...#.#.........#.#.#.#.#...#.# 33 | #.#.#######.###.#.#########.###.#.###.#.#.#.###########.#.#########.#.#.#####.#.# 34 | #.#.......#...#.#.#.......#...#.#.#...#.#.#...#.......#.#.#.......#...#.#...#c..# 35 | #.#######.#.#.###.###.###.#####.#.#.###.#.###.#.#####.#.###.#####.#.###.#.#.###.# 36 | #.L..q..#.#.#...#...#.#.#.....#.#...#...#...#.....#...#.....#.....#...#...#.#...# 37 | #.#######.#####.###.#.#.#####.#.#####.#####.#######S#########.#############.#.### 38 | #...#...#.#.......#...#.#.....#.#...#...#...#.....#.....#...#.#.....#.....#k#...# 39 | #.#.#.#.#.#.#####.#####.#.#####.#.#.###.#.###.###.###.#.###.#.#.###.#.###.#.###.# 40 | #.#...#.....#...........#.......#.#...........#.......#.....#...#.....#.....#...# 41 | #######################################.@.####################################### 42 | #.#...#...........#u....#...#.......#...............#.....#...................#.# 43 | #.#.#.#.#####.###.#.#.#.#.#.#.#.#####.#.#####.#####.#.###.#.###.#############.#.# 44 | #.#.#...#...#...#.#.#.#.#.#...#.#s....#.#.....#...#.#.#.#..p#.#.#.....#.....#...# 45 | #.#.#######.###.###.#.###.#####.#.#####.#.#######.#.#.#.#####.#.#.###.#.###.###.# 46 | #...#.........#.#...#.#...#...#...#...#.#...#.....#...#.......#.#.#.#.....#.#.#.# 47 | #.###.#.#####.#.#.###.#.###.#######.#.#.#.#.#.###.#####.#######.#.#.#######.#.#.# 48 | #...#.#.#.....#...#...#.#...#.....#.#...#.#.#.#...#.....#.......#.#...#.V...#...# 49 | ###.###.#.#########.###.#.#.#.###.#.###.###.#.#####.#.###.#######.###.#.#####.### 50 | #.#.....#.#.....#...#.....#.#...#...#...#...#.#.....#.......#...#.#...#.#.....#.# 51 | #.#######.#N#.#.#.#.#.#########.#.#######.###.#.#############.#.#.#.#.#.#.#####.# 52 | #.......#.#.#.#.#.#.#.#...#.....#.#.....#.#.........#.........#.#...#.#.#.....U.# 53 | #.#######.###.#.#.###.#.#P#.#######.###.#.###.#######.#########.#####.#.#######.# 54 | #.#.....#...#.#.#.....#.#...#.......#...#...#...#.....#.......#.......#.#...#...# 55 | #.#.###.###.#.#########.###.#.#######.#####.###.#.#####.###.###########.#.#.#.### 56 | #...#...#.#.#.#.....#...#...#.#.....#...#...#...#.......#...#.........#.#.#.#.#.# 57 | #.###.#.#.#.#.#.#.###.#######.#.###.###.#.#####.#####.#####.#.#######.#.#.#Q#.#.# 58 | #.#...#...#.#...#...#.....#...#.#...#...#.....#.....#.#.Yi#.#.#...#...#.#.#.#...# 59 | #.#.#######.###.###.#####.#.###.#.#.#.###.###.#######.#.#.#.#.###.#.###.#.#.###.# 60 | #.#.......#...#.#.....#...#.#...#.#.#...#.#...#.......#.#.#.#...#.....#.#.#.#...# 61 | #.#######.###.###.#.###.###.#####.#####.#.#.#.#.#######.#.#.###.#####.#.#.#.##### 62 | #.#.....#.....#...#.#.#.........#.....#.#.#.#.#.#...#...#.#.#.#.#...#...#.#.....# 63 | #.#.###.#######.#####.#########.#.#.###.#.#.#.#.#.###.###.#.#.#.#.#.#####.#####.# 64 | #.#.#.....#.........#.#.....#.#.#.#b#...#.#.#.#.#.#..y#.#.#.#.#.#.#...#...#.....# 65 | #.###.#####.#######.#.#.###.#.#.#.#.#.#####.###.#.#.###.#.###.#.#.###.#.###.###.# 66 | #.E.#....d..#.......#...#.#.#.#.#.#.#...#...#...#.#...#.#...I.#...#..l#.#...#...# 67 | ###.#.#######.###########F#.#.#.###.###.#.#.#.###.###.#.#########.#.###.#.###J### 68 | #...#...#...#...........#...#.......#...#.#.#.#.....#.G.#.......#.#.....#.#.#.#.# 69 | #.#####.#.#########.#.#.#.#########.#.#.#.###.#####.###.#.###.#.#####.###.#.#.#.# 70 | #..v..#...#.......#.#.#.#.#...#.....#.#.#...#.......#.#.#...#.#.....#.#...#.#...# 71 | #####.#####.#####.###.###.#.#.#.#####.#.#Z#.#######X#.#.#####.#####.#.#.###.###.# 72 | #...#.....#...#.....#...#...#.#.#...#.#.#.#.......#...#.....#.....#.#.#.#...#...# 73 | #.#######.#.#.#####.###.#####.#.#.###.#.###.#.#####.#######.#.###.#.###.#.###.### 74 | #.........#.#.#...#...#...#...#...#...#.#...#.#...#.#.R....g#.#...#.....#.#...#.# 75 | #.###########.#.#.###.###.#.###.###.###.#.#####.#.#.#.#########.#########.#.###.# 76 | #...#.#.....#...#.#.#.#...#..t#.#...#...#.#.....#...#...#.....#.#..n....#.M.#...# 77 | ###.#.#.#.#.#####.#.#.#.#####.###.#####.#.#.###########.#.###.#.#.#######.###.#.# 78 | #...#...#.#.#..w#.#.#.#.....#...#.....#.#.#.......#.....#.#...#.#.#.D...#.....#.# 79 | #C#######.#.#.#.#.#.#.###.#.###.#####.#.#.#######.#.#####.#.###.#.#.###.#######H# 80 | #......o..#...#...#.......#...#.A.....#.#.........#r......#.....#.....#.........# 81 | ################################################################################# -------------------------------------------------------------------------------- /day18/day18_part2.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple, Dict, Set, Tuple 2 | from collections import deque 3 | import heapq 4 | 5 | class XY(NamedTuple): 6 | x: int 7 | y: int 8 | 9 | 10 | class Grid(NamedTuple): 11 | walls: Set[XY] 12 | keys: Dict[XY, str] 13 | doors: Dict[XY, str] 14 | starts: List[XY] 15 | 16 | @staticmethod 17 | def parse(raw: str) -> 'Grid': 18 | walls = set() 19 | keys = {} 20 | doors = {} 21 | starts = [] 22 | 23 | lines = raw.strip().split("\n") 24 | 25 | for i, line in enumerate(lines): 26 | for j, c in enumerate(line): 27 | loc = XY(i, j) 28 | if c == '#': 29 | walls.add(loc) 30 | elif c == '@': 31 | starts.append(loc) 32 | elif 'a' <= c <= 'z': 33 | keys[loc] = c 34 | elif 'A' <= c <= 'Z': 35 | doors[loc] = c 36 | 37 | return Grid(walls, keys, doors, starts) 38 | 39 | deltas = [(0, 1), (0, -1), (1, 0), (-1, 0)] 40 | 41 | 42 | def one_source_shortest_path(grid: Grid, start: XY): 43 | # key is key_name, value is pair (distance, doors_passed_through) 44 | results = {} 45 | visited = {start} 46 | 47 | frontier = deque([(0, start, [])]) 48 | 49 | while frontier: 50 | num_steps, (x, y), doors = frontier.popleft() 51 | for dx, dy in deltas: 52 | new_pos = XY(x + dx, y + dy) 53 | 54 | if new_pos in visited or new_pos in grid.walls: 55 | continue 56 | 57 | visited.add(new_pos) 58 | 59 | if new_pos in grid.keys: 60 | key = grid.keys[new_pos] 61 | results[key] = (num_steps + 1, doors) 62 | frontier.append((num_steps + 1, new_pos, doors)) 63 | elif new_pos in grid.doors: 64 | new_doors = doors + [grid.doors[new_pos]] 65 | frontier.append((num_steps + 1, new_pos, new_doors)) 66 | else: 67 | frontier.append((num_steps + 1, new_pos, doors)) 68 | 69 | return results 70 | 71 | 72 | def all_source_shortest_path(grid: Grid): 73 | results = {} 74 | 75 | for i, start in enumerate(grid.starts): 76 | results[str(i)] = one_source_shortest_path(grid, start) 77 | for key_loc, key in grid.keys.items(): 78 | results[key] = one_source_shortest_path(grid, key_loc) 79 | 80 | return results 81 | 82 | 83 | 84 | RAW = """######### 85 | #b.A.@.a# 86 | #########""" 87 | 88 | GRID = Grid.parse(RAW) 89 | 90 | GRID2 = Grid.parse("""################# 91 | #i.G..c...e..H.p# 92 | ########.######## 93 | #j.A..b...f..D.o# 94 | ########@######## 95 | #k.E..a...g..B.n# 96 | ########.######## 97 | #l.F..d...h..C.m# 98 | #################""") 99 | 100 | GRID3 = Grid.parse("""######################## 101 | #@..............ac.GI.b# 102 | ###d#e#f################ 103 | ###A#B#C################ 104 | ###g#h#i################ 105 | ########################""") 106 | 107 | def signature(prev_keys: Set[str], curr_locs: List[str]) -> str: 108 | loc = ''.join(curr_locs) 109 | return f"{loc}:{''.join(sorted(prev_keys))}" 110 | 111 | def shortest_path(grid: Grid) -> int: 112 | assp = all_source_shortest_path(grid) 113 | seen_signatures = set() 114 | 115 | num_keys = len(grid.keys) 116 | # maintain priority queue of num_steps, key at, keys had 117 | pq = [(0, ['0', '1', '2', '3'], set())] 118 | 119 | while pq: 120 | num_steps, source_keys, keys_had = heapq.heappop(pq) 121 | sig = signature(keys_had, source_keys) 122 | if sig in seen_signatures: 123 | continue 124 | seen_signatures.add(sig) 125 | 126 | print(num_steps, source_keys, keys_had) 127 | 128 | if len(keys_had) == num_keys: 129 | return num_steps 130 | 131 | for i, source_key in enumerate(source_keys): 132 | ossp = assp[source_key] 133 | for dest_key, (steps_to_key, doors) in ossp.items(): 134 | if dest_key in keys_had: 135 | continue 136 | if any(door.lower() not in keys_had for door in doors): 137 | continue 138 | 139 | new_source_keys = source_keys[:] 140 | new_source_keys[i] = dest_key 141 | 142 | new_keys = keys_had | {dest_key} 143 | heapq.heappush(pq, (num_steps + steps_to_key, new_source_keys, new_keys)) 144 | 145 | 146 | GRID = Grid.parse("""############### 147 | #d.ABC.#.....a# 148 | ######@#@###### 149 | ############### 150 | ######@#@###### 151 | #b.....#.....c# 152 | ###############""") 153 | 154 | GRID2 = Grid.parse("""############# 155 | #g#f.D#..h#l# 156 | #F###e#E###.# 157 | #dCba@#@BcIJ# 158 | ############# 159 | #nK.L@#@G...# 160 | #M###N#H###.# 161 | #o#m..#i#jk.# 162 | #############""") 163 | 164 | 165 | 166 | 167 | with open('day18_part2.txt') as f: 168 | grid = Grid.parse(f.read()) 169 | 170 | print(shortest_path(grid)) -------------------------------------------------------------------------------- /day18/day18_part2.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | #...............#.......#...............#.........#.......#.......#.....#.......# 3 | #.#########.#.###.#####.#.#.#######.#####.#######.#.#######.#.###.###.#.#.#####.# 4 | #.#.#.....#.#.#...#...#..f#...#.....#...#...#.....#.........#...#.....#...#m....# 5 | #.#.#.###.###.#.#####.#######.#######.#.#.#.#.#######.#########.###########.##### 6 | #...#...#.#...#.......#.....#.#.......#.#.#.#...#...#...#.......#..h......#.....# 7 | ###.###.#.#.#########.#.###.#T#.#######.###.###.#B#.#.###.#######.#######.#####.# 8 | #...#...#...#.....#...#...#.#...#.....#.#...#.#...#.#.#...#.........#...#.......# 9 | #####.#######.###.#.###.#.#######.###.#.#.###.#####.###.#.###########.#.#######.# 10 | #...#...#.....#.#...#.#.#.....#...#.....#.#.....#...#...#.#...........#.#.....#.# 11 | #.#.###.#.#####.#####.#.#####.#.###.#####.#.###.#.###.#####.###########.#.###.#.# 12 | #.#.....#.#.....#.........#...#...#.#...#.#...#..x#...W.....#.....#.....#.#...#.# 13 | #.#######.###.#.#########.#.#####.###.#.#.###.#######.#######.###.#.#####.#.##### 14 | #.........#...#.......#.#.#.#...#.....#.#.#.#.#.....#...#.....#...#.#.....#.....# 15 | #.#########O#########.#.#.#.#.#.#.#####.#.#.#.###.#.#.###.#####.###.#.#########.# 16 | #.#.........#.........#...#.#.#.#...#.#.#.#.......#.#.#...#...#.#.......#.....#.# 17 | #.#####.#########.#########.#.#.###.#.#.#.#########.###.###.###.#####.###.###.#.# 18 | #.....#...#.....#.#.........#.#.#.....#.#.....#.....#...#...........#.#...#e..#.# 19 | #####.###.#K###.#.#####.#####.#.#######.#####.#####.#.#.###########.###.###.###.# 20 | #...#.....#.#...#.......#.....#.#.......#.....#...#...#.#.........#.....#.#.#...# 21 | #.#####.###.#.#########.#.#####.#.#####.#.#####.#.#######.#####.#.#######.#.#.#.# 22 | #.#j..#.#...#a..#.....#.#.#.......#.....#.......#...........#.#.#.......#.#...#.# 23 | #.#.#.#.#.#####.#.#####.#.#############.#.###################.#########.#.#####.# 24 | #...#.#.#.#...#...#.....#.#..z..#.....#.#.....#.......#.....#...#...#...#...#...# 25 | #.###.#.#.###.#####.#####.#.###.#.###.#######.#.#.#####.###.###.#.#.#.###.###.### 26 | #.#...#.#.....#.#...#...#.#...#.#.#.#...#.....#.#.....#...#.....#.#...#.#...#...# 27 | ###.###.#####.#.#.###.#.#.###.#.#.#.###.#.###########.###.#######.#####.#.#.###.# 28 | #...#...#...#.#.......#.#.....#.#.....#.#.#.............#...#...#...#.....#.#...# 29 | #.#.#.#####.#.#############.###.###.###.#.#.#########.#####.###.#.#.#####.#.#.### 30 | #.#.#.#.....#.............#...#.....#...#.#.....#...#.#.....#...#.#.#...#.#.#.#.# 31 | #.###.#.#.###############.###.#####.#.#.#.#####.#.#.#.#.#####.###.#.#.#.#.#.#.#.# 32 | #.#...#.#.......#.........#...#...#.#.#.#.#.....#.#...#.#.........#.#.#.#.#...#.# 33 | #.#.#######.###.#.#########.###.#.###.#.#.#.###########.#.#########.#.#.#####.#.# 34 | #.#.......#...#.#.#.......#...#.#.#...#.#.#...#.......#.#.#.......#...#.#...#c..# 35 | #.#######.#.#.###.###.###.#####.#.#.###.#.###.#.#####.#.###.#####.#.###.#.#.###.# 36 | #.L..q..#.#.#...#...#.#.#.....#.#...#...#...#.....#...#.....#.....#...#...#.#...# 37 | #.#######.#####.###.#.#.#####.#.#####.#####.#######S#########.#############.#.### 38 | #...#...#.#.......#...#.#.....#.#...#...#...#.....#.....#...#.#.....#.....#k#...# 39 | #.#.#.#.#.#.#####.#####.#.#####.#.#.###.#.###.###.###.#.###.#.#.###.#.###.#.###.# 40 | #.#...#.....#...........#.......#.#....@#@....#.......#.....#...#.....#.....#...# 41 | ################################################################################# 42 | #.#...#...........#u....#...#.......#..@#@..........#.....#...................#.# 43 | #.#.#.#.#####.###.#.#.#.#.#.#.#.#####.#.#####.#####.#.###.#.###.#############.#.# 44 | #.#.#...#...#...#.#.#.#.#.#...#.#s....#.#.....#...#.#.#.#..p#.#.#.....#.....#...# 45 | #.#.#######.###.###.#.###.#####.#.#####.#.#######.#.#.#.#####.#.#.###.#.###.###.# 46 | #...#.........#.#...#.#...#...#...#...#.#...#.....#...#.......#.#.#.#.....#.#.#.# 47 | #.###.#.#####.#.#.###.#.###.#######.#.#.#.#.#.###.#####.#######.#.#.#######.#.#.# 48 | #...#.#.#.....#...#...#.#...#.....#.#...#.#.#.#...#.....#.......#.#...#.V...#...# 49 | ###.###.#.#########.###.#.#.#.###.#.###.###.#.#####.#.###.#######.###.#.#####.### 50 | #.#.....#.#.....#...#.....#.#...#...#...#...#.#.....#.......#...#.#...#.#.....#.# 51 | #.#######.#N#.#.#.#.#.#########.#.#######.###.#.#############.#.#.#.#.#.#.#####.# 52 | #.......#.#.#.#.#.#.#.#...#.....#.#.....#.#.........#.........#.#...#.#.#.....U.# 53 | #.#######.###.#.#.###.#.#P#.#######.###.#.###.#######.#########.#####.#.#######.# 54 | #.#.....#...#.#.#.....#.#...#.......#...#...#...#.....#.......#.......#.#...#...# 55 | #.#.###.###.#.#########.###.#.#######.#####.###.#.#####.###.###########.#.#.#.### 56 | #...#...#.#.#.#.....#...#...#.#.....#...#...#...#.......#...#.........#.#.#.#.#.# 57 | #.###.#.#.#.#.#.#.###.#######.#.###.###.#.#####.#####.#####.#.#######.#.#.#Q#.#.# 58 | #.#...#...#.#...#...#.....#...#.#...#...#.....#.....#.#.Yi#.#.#...#...#.#.#.#...# 59 | #.#.#######.###.###.#####.#.###.#.#.#.###.###.#######.#.#.#.#.###.#.###.#.#.###.# 60 | #.#.......#...#.#.....#...#.#...#.#.#...#.#...#.......#.#.#.#...#.....#.#.#.#...# 61 | #.#######.###.###.#.###.###.#####.#####.#.#.#.#.#######.#.#.###.#####.#.#.#.##### 62 | #.#.....#.....#...#.#.#.........#.....#.#.#.#.#.#...#...#.#.#.#.#...#...#.#.....# 63 | #.#.###.#######.#####.#########.#.#.###.#.#.#.#.#.###.###.#.#.#.#.#.#####.#####.# 64 | #.#.#.....#.........#.#.....#.#.#.#b#...#.#.#.#.#.#..y#.#.#.#.#.#.#...#...#.....# 65 | #.###.#####.#######.#.#.###.#.#.#.#.#.#####.###.#.#.###.#.###.#.#.###.#.###.###.# 66 | #.E.#....d..#.......#...#.#.#.#.#.#.#...#...#...#.#...#.#...I.#...#..l#.#...#...# 67 | ###.#.#######.###########F#.#.#.###.###.#.#.#.###.###.#.#########.#.###.#.###J### 68 | #...#...#...#...........#...#.......#...#.#.#.#.....#.G.#.......#.#.....#.#.#.#.# 69 | #.#####.#.#########.#.#.#.#########.#.#.#.###.#####.###.#.###.#.#####.###.#.#.#.# 70 | #..v..#...#.......#.#.#.#.#...#.....#.#.#...#.......#.#.#...#.#.....#.#...#.#...# 71 | #####.#####.#####.###.###.#.#.#.#####.#.#Z#.#######X#.#.#####.#####.#.#.###.###.# 72 | #...#.....#...#.....#...#...#.#.#...#.#.#.#.......#...#.....#.....#.#.#.#...#...# 73 | #.#######.#.#.#####.###.#####.#.#.###.#.###.#.#####.#######.#.###.#.###.#.###.### 74 | #.........#.#.#...#...#...#...#...#...#.#...#.#...#.#.R....g#.#...#.....#.#...#.# 75 | #.###########.#.#.###.###.#.###.###.###.#.#####.#.#.#.#########.#########.#.###.# 76 | #...#.#.....#...#.#.#.#...#..t#.#...#...#.#.....#...#...#.....#.#..n....#.M.#...# 77 | ###.#.#.#.#.#####.#.#.#.#####.###.#####.#.#.###########.#.###.#.#.#######.###.#.# 78 | #...#...#.#.#..w#.#.#.#.....#...#.....#.#.#.......#.....#.#...#.#.#.D...#.....#.# 79 | #C#######.#.#.#.#.#.#.###.#.###.#####.#.#.#######.#.#####.#.###.#.#.###.#######H# 80 | #......o..#...#...#.......#...#.A.....#.#.........#r......#.....#.....#.........# 81 | ################################################################################# -------------------------------------------------------------------------------- /day19/day19.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List, NamedTuple, Tuple, Iterable, Set, Dict, Callable 3 | from enum import Enum 4 | import itertools 5 | from collections import deque, defaultdict 6 | import logging 7 | import copy 8 | 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | 13 | PROGRAM = [109,424,203,1,21101,11,0,0,1106,0,282,21102,1,18,0,1105,1,259,1202,1,1,221,203,1,21101,0,31,0,1105,1,282,21102,38,1,0,1106,0,259,21002,23,1,2,21202,1,1,3,21102,1,1,1,21102,57,1,0,1106,0,303,1202,1,1,222,20102,1,221,3,20102,1,221,2,21102,259,1,1,21102,80,1,0,1105,1,225,21101,0,145,2,21102,91,1,0,1105,1,303,2101,0,1,223,20101,0,222,4,21102,259,1,3,21101,225,0,2,21102,1,225,1,21102,1,118,0,1105,1,225,20101,0,222,3,21101,0,197,2,21101,133,0,0,1106,0,303,21202,1,-1,1,22001,223,1,1,21101,0,148,0,1105,1,259,1202,1,1,223,21001,221,0,4,21001,222,0,3,21102,1,19,2,1001,132,-2,224,1002,224,2,224,1001,224,3,224,1002,132,-1,132,1,224,132,224,21001,224,1,1,21102,195,1,0,105,1,109,20207,1,223,2,21002,23,1,1,21102,-1,1,3,21101,0,214,0,1105,1,303,22101,1,1,1,204,1,99,0,0,0,0,109,5,1201,-4,0,249,22101,0,-3,1,22101,0,-2,2,21202,-1,1,3,21102,250,1,0,1106,0,225,22101,0,1,-4,109,-5,2105,1,0,109,3,22107,0,-2,-1,21202,-1,2,-1,21201,-1,-1,-1,22202,-1,-2,-2,109,-3,2106,0,0,109,3,21207,-2,0,-1,1206,-1,294,104,0,99,22102,1,-2,-2,109,-3,2105,1,0,109,5,22207,-3,-4,-1,1206,-1,346,22201,-4,-3,-4,21202,-3,-1,-1,22201,-4,-1,2,21202,2,-1,-1,22201,-4,-1,1,21201,-2,0,3,21101,343,0,0,1105,1,303,1105,1,415,22207,-2,-3,-1,1206,-1,387,22201,-3,-2,-3,21202,-2,-1,-1,22201,-3,-1,3,21202,3,-1,-1,22201,-3,-1,2,22101,0,-4,1,21102,384,1,0,1106,0,303,1106,0,415,21202,-4,-1,-4,22201,-4,-3,-4,22202,-3,-2,-2,22202,-2,-4,-4,22202,-3,-2,-3,21202,-4,-1,-2,22201,-3,-2,1,22102,1,1,-4,109,-5,2105,1,0] 14 | 15 | class Opcode(Enum): 16 | ADD = 1 17 | MULTIPLY = 2 18 | STORE_INPUT = 3 19 | SEND_TO_OUTPUT = 4 20 | JUMP_IF_TRUE = 5 21 | JUMP_IF_FALSE = 6 22 | LESS_THAN = 7 23 | EQUALS = 8 24 | ADJUST_RELATIVE_BASE = 9 25 | 26 | END_PROGRAM = 99 27 | 28 | 29 | Modes = List[int] 30 | Program = List[int] 31 | 32 | 33 | class EndProgram(Exception): pass 34 | 35 | 36 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 37 | # logging.debug(f"parsing {opcode}") 38 | 39 | opcode_part = opcode % 100 40 | 41 | modes: List[int] = [] 42 | opcode = opcode // 100 43 | 44 | for _ in range(num_modes): 45 | modes.append(opcode % 10) 46 | opcode = opcode // 10 47 | 48 | return Opcode(opcode_part), modes 49 | 50 | 51 | class IntcodeComputer: 52 | def __init__(self, program: List[int], get_input: Callable[[], int]) -> None: 53 | self.program = defaultdict(int) 54 | self.program.update({i: value for i, value in enumerate(program)}) 55 | self.get_input = get_input 56 | self.pos = 0 57 | self.relative_base = 0 58 | 59 | def save(self): 60 | return [ 61 | copy.deepcopy(self.program), 62 | self.get_input, 63 | self.pos, 64 | self.relative_base 65 | ] 66 | 67 | @staticmethod 68 | def load(program, get_input, pos, relative_base): 69 | computer = IntcodeComputer([], get_input) 70 | computer.program = program 71 | computer.pos = pos 72 | computer.relative_base = relative_base 73 | return computer 74 | 75 | def _get_value(self, pos: int, mode: int) -> int: 76 | if mode == 0: 77 | # pointer mode 78 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 79 | return self.program[self.program[pos]] 80 | elif mode == 1: 81 | # immediate mode 82 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 83 | return self.program[pos] 84 | elif mode == 2: 85 | # relative mode 86 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 87 | return self.program[self.program[pos] + self.relative_base] 88 | else: 89 | raise ValueError(f"unknown mode: {mode}") 90 | 91 | def _loc(self, pos: int, mode: int) -> int: 92 | if mode == 0: 93 | # pointer mode 94 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 95 | return self.program[pos] 96 | elif mode == 2: 97 | # relative mode 98 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 99 | return self.program[pos] + self.relative_base 100 | 101 | def go(self) -> int: 102 | 103 | while True: 104 | # logging.debug(f"program: {self.program}") 105 | # logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 106 | 107 | opcode, modes = parse_opcode(self.program[self.pos]) 108 | 109 | # logging.debug(f"opcode: {opcode}, modes: {modes}") 110 | 111 | if opcode == Opcode.END_PROGRAM: 112 | raise EndProgram 113 | 114 | elif opcode == Opcode.ADD: 115 | value1 = self._get_value(self.pos + 1, modes[0]) 116 | value2 = self._get_value(self.pos + 2, modes[1]) 117 | loc = self._loc(self.pos + 3, modes[2]) 118 | 119 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 120 | 121 | self.program[loc] = value1 + value2 122 | self.pos += 4 123 | 124 | elif opcode == Opcode.MULTIPLY: 125 | value1 = self._get_value(self.pos + 1, modes[0]) 126 | value2 = self._get_value(self.pos + 2, modes[1]) 127 | loc = self._loc(self.pos + 3, modes[2]) 128 | 129 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 130 | 131 | self.program[loc] = value1 * value2 132 | self.pos += 4 133 | 134 | elif opcode == Opcode.STORE_INPUT: 135 | # Get input and store at location 136 | loc = self._loc(self.pos + 1, modes[0]) 137 | input_value = self.get_input() 138 | self.program[loc] = input_value 139 | self.pos += 2 140 | 141 | elif opcode == Opcode.SEND_TO_OUTPUT: 142 | # Get output from location 143 | value = self._get_value(self.pos + 1, modes[0]) 144 | self.pos += 2 145 | # logging.debug(f"output: {value}") 146 | 147 | #### 148 | #### 149 | 150 | return value 151 | 152 | elif opcode == Opcode.JUMP_IF_TRUE: 153 | # jump if true 154 | value1 = self._get_value(self.pos + 1, modes[0]) 155 | value2 = self._get_value(self.pos + 2, modes[1]) 156 | 157 | # logging.debug(f"value1: {value1}, value2: {value2}") 158 | 159 | if value1 != 0: 160 | self.pos = value2 161 | else: 162 | self.pos += 3 163 | 164 | elif opcode == Opcode.JUMP_IF_FALSE: 165 | value1 = self._get_value(self.pos + 1, modes[0]) 166 | value2 = self._get_value(self.pos + 2, modes[1]) 167 | 168 | # logging.debug(f"value1: {value1}, value2: {value2}") 169 | 170 | if value1 == 0: 171 | self.pos = value2 172 | else: 173 | self.pos += 3 174 | 175 | elif opcode == Opcode.LESS_THAN: 176 | value1 = self._get_value(self.pos + 1, modes[0]) 177 | value2 = self._get_value(self.pos + 2, modes[1]) 178 | loc = self._loc(self.pos + 3, modes[2]) 179 | 180 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 181 | 182 | if value1 < value2: 183 | self.program[loc] = 1 184 | else: 185 | self.program[loc] = 0 186 | self.pos += 4 187 | 188 | elif opcode == Opcode.EQUALS: 189 | value1 = self._get_value(self.pos + 1, modes[0]) 190 | value2 = self._get_value(self.pos + 2, modes[1]) 191 | loc = self._loc(self.pos + 3, modes[2]) 192 | 193 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 194 | 195 | if value1 == value2: 196 | self.program[loc] = 1 197 | else: 198 | self.program[loc] = 0 199 | self.pos += 4 200 | 201 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 202 | value = self._get_value(self.pos + 1, modes[0]) 203 | 204 | # logging.debug(f"value: {value}") 205 | 206 | self.relative_base += value 207 | self.pos += 2 208 | 209 | else: 210 | raise ValueError(f"invalid opcode: {opcode}") 211 | 212 | 213 | # total = 0 214 | 215 | # for i in range(50): 216 | # for j in range(50): 217 | # inputs = [i, j] 218 | # it = iter(inputs) 219 | # get_input = lambda: next(it) 220 | # computer = IntcodeComputer(PROGRAM, lambda: next(it)) 221 | 222 | # total += computer.go() 223 | 224 | 225 | def check(i: int, j: int) -> int: 226 | inputs = [i, j] 227 | it = iter(inputs) 228 | get_input = lambda: next(it) 229 | computer = IntcodeComputer(PROGRAM, get_input) 230 | return computer.go() 231 | 232 | 233 | hilo = {} 234 | 235 | i = 6 236 | jlo = 5 237 | jhi = 5 238 | 239 | # for i in range(6, 100): 240 | # row = [] 241 | # for j in range(40): 242 | # result = check(i, j) 243 | # row.append(str(result)) 244 | # print(''.join(row)) 245 | 246 | results = {} 247 | 248 | for i in range(6, 2000): 249 | while not check(i, jlo): 250 | jlo += 1 251 | jhi = max(jhi, jlo) 252 | while check(i, jhi): 253 | jhi += 1 254 | jhi -= 1 255 | 256 | results[i] = (jlo, jhi) 257 | 258 | if (i - 99) in results: 259 | klo, khi = results[i - 99] 260 | if khi >= jlo + 99: 261 | top_left = (i - 99, jlo) 262 | 263 | print(top_left[1] * 10_000 + top_left[0]) 264 | break 265 | 266 | print(i, jlo, jhi) 267 | 268 | 269 | -------------------------------------------------------------------------------- /day20/day20.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Set, Iterator, NamedTuple 2 | from collections import defaultdict, deque 3 | 4 | deltas1 = [(0, 1), (1, 0)] 5 | deltas2 = [(0, 1), (1, 0), (0, -1), (-1, 0)] 6 | 7 | class XY(NamedTuple): 8 | x: int 9 | y: int 10 | 11 | 12 | class Maze(NamedTuple): 13 | start: XY 14 | end: XY 15 | neighbors: Dict[XY, List[XY]] 16 | 17 | 18 | def parse(raw: str) -> Maze: 19 | lines = raw.strip("\n").split("\n") 20 | nr = len(lines) 21 | nc = len(lines[0]) 22 | 23 | def neighbors(loc: XY, deltas) -> Iterator[XY]: 24 | x, y = loc 25 | for dx, dy in deltas: 26 | neighbor = XY(x + dx, y + dy) 27 | if 0 <= neighbor.x < nr and 0 <= neighbor.y < nc: 28 | yield neighbor 29 | 30 | # First let's find all the portals 31 | portals: Dict[str, List[XY]] = defaultdict(list) 32 | 33 | for i, line in enumerate(lines): 34 | for j, c in enumerate(line): 35 | if 'A' <= c <= 'Z': 36 | for i2, j2 in neighbors(XY(i, j), deltas1): 37 | c2 = lines[i2][j2] 38 | if 'A' <= c2 <= 'Z': 39 | portals[f"{c}{c2}"].extend([XY(i, j), XY(i2, j2)]) 40 | 41 | portal_neighbors = defaultdict(list) 42 | neighbor_portals = defaultdict(list) 43 | 44 | # find start and end 45 | for i, line in enumerate(lines): 46 | for j, c in enumerate(line): 47 | if c == '.': 48 | for loc in neighbors(XY(i, j), deltas2): 49 | for portal, locs in portals.items(): 50 | if loc in locs: 51 | portal_neighbors[portal].append(XY(i, j)) 52 | neighbor_portals[XY(i, j)].append(portal) 53 | 54 | neighbor_dict = defaultdict(list) 55 | 56 | # find neighbors 57 | for i, line in enumerate(lines): 58 | for j, c in enumerate(line): 59 | if c == '.': 60 | loc = XY(i, j) 61 | # get direct neighbors 62 | for loc2 in neighbors(loc, deltas2): 63 | i2, j2 = loc2 64 | c2 = lines[i2][j2] 65 | if c2 == '.': 66 | neighbor_dict[loc].append(loc2) 67 | # get portal neighbors 68 | for portal in neighbor_portals.get(loc, []): 69 | for nbor in portal_neighbors[portal]: 70 | if nbor != loc: 71 | neighbor_dict[loc].append(nbor) 72 | 73 | return Maze(start=portal_neighbors['AA'][0], 74 | end=portal_neighbors['ZZ'][0], 75 | neighbors=neighbor_dict) 76 | 77 | RAW = """ A 78 | A 79 | #######.######### 80 | #######.........# 81 | #######.#######.# 82 | #######.#######.# 83 | #######.#######.# 84 | ##### B ###.# 85 | BC...## C ###.# 86 | ##.## ###.# 87 | ##...DE F ###.# 88 | ##### G ###.# 89 | #########.#####.# 90 | DE..#######...###.# 91 | #.#########.###.# 92 | FG..#########.....# 93 | ###########.##### 94 | Z 95 | Z """ 96 | 97 | MAZE = parse(RAW) 98 | 99 | def shortest_path(maze: Maze) -> int: 100 | visited = {maze.start} 101 | q = deque([(maze.start, 0)]) 102 | 103 | 104 | 105 | while q: 106 | loc, num_steps = q.popleft() 107 | 108 | for nbor in maze.neighbors[loc]: 109 | if nbor == maze.end: 110 | return num_steps + 1 111 | 112 | if nbor not in visited: 113 | visited.add(nbor) 114 | q.append((nbor, num_steps + 1)) 115 | 116 | 117 | with open('day20.txt') as f: 118 | raw = f.read() 119 | 120 | maze = parse(raw) 121 | 122 | print(shortest_path(maze)) -------------------------------------------------------------------------------- /day20/day20.txt: -------------------------------------------------------------------------------- 1 | H E A R Z K 2 | U U S N V S 3 | ###############################.#########.#########.#####.#########.###.##################################### 4 | #.......#.#.#.#.#.....#...#...#...#...#.....#.....#...#.......#...#.#...#.#.#...#.......#.#.....#.#.#.#.....# 5 | #######.#.#.#.#.#####.###.###.#.###.#.#.#.###.#.#####.###.#.###.#.#.###.#.#.#.###.#######.#.#####.#.#.#.###.# 6 | #.....#.......#.#.#.................#.#.#...#.#.....#...#.#...#.#.#...#.#.....#.#...#.......#.#.#...#...#...# 7 | #.###.###.###.#.#.#####.#.#.###.#.###.#.#######.###.#.#####.#.#.#.#.#.#.#.###.#.#.#####.#####.#.#.###.#####.# 8 | #.#.#.#.#.#.............#.#...#.#...#.#.#.#.......#.......#.#.#.#.#.#.#.#.#.#.................#.#.......#.#.# 9 | ###.#.#.#####.###.#.###.#############.#.#.#######.#.#.#######.#.#.#.###.#.#.#.#.#.###.#.#.#.###.#.#######.### 10 | #.............#.#.#.#...#...........#.#.#...#.....#.#.#.......#.#.#...#.....#.#.#.#.#.#.#.#.#.#.....#.#.....# 11 | #############.#.#####.###.#####.###.#.#.###.#####.#########.###.#.###.###.###.#####.#########.#.###.#.###.### 12 | #.#.#...#.#.....#.#...#.....#.#.#.#.#.#.#.#.#.#.....#...#...#...#.......#.#.......#.....#...#.#.#.#.#...#.#.# 13 | #.#.#.###.#######.###.#.#####.#.#.#.#.#.#.#.#.###.###.#####.###.#############.#.###.#####.###.#.#.###.###.#.# 14 | #.#...........#.......#.#...#.#.#.....#.#.....#.#...#.#.#.....#...........#.#.#.....#...#.#.....#...#.......# 15 | #.###.###.#.#.###.###.###.###.#.#.#####.###.###.#.###.#.#.#######.###.#.###.#.#.###.#.###.###.###.#####.###.# 16 | #.....#...#.#.#...#.....#.#.....#.#.....#.#.........#...#.#...#.#.#...#.#.....#.#.#.#...#...#.#...#.#.....#.# 17 | #.#######.#####.#########.#####.#####.#.#.###.#.#.###.#.#.#.###.#####.#.#.###.###.###.###.###.#.###.#.#####.# 18 | #.#...........#.#...#...#...#.......#.#...#...#.#.#...#.........#...#.#.#.#...........#...#.#.....#.#.#.#.#.# 19 | #########.#########.#.#####.###.#######.#######.###.###.#####.#####.#.###.###.#######.#.###.#.#####.#.#.#.#.# 20 | #...#...........#.#...#...#.....#.#.....#...#.....#.#.#.....#.#.......#.#.#.....#.#...#.....#.#...#.....#.#.# 21 | ###.#.#######.###.###.#.#.#.###.#.###.#.#.#####.#####.###.###########.#.#####.###.#####.#####.###.###.###.### 22 | #.#.#.......#.#.......#.#...#.#...#...#.....#.......#.#.........#.....#.........#...#...#...#.#...#.#.......# 23 | #.#.#.###.#######.###.#######.#.###.###.###.#####.#.#.#.#####.#######.#######.###.#.###.#.###.###.#.#.####### 24 | #.#...#.#...#.#.#.#.#.#.....#.#.#...#.#.#.#...#.#.#...#...#.#...#...#.#...#.#.....#.......#...#.......#.....# 25 | #.#.###.#####.#.###.#.#####.#.#.#####.#.#.###.#.#####.#.###.#######.#.###.#.#.###.###.#######.###.###.#.##### 26 | #...........#.#.........#.#.........#.....#.....#.....#.#.....#.......#.#...#.#.#...#.#...#.#.......#...#...# 27 | #####.#.#.###.###.#.#####.#.###.#######.#####.#.#.#####.#.#######.###.#.#.#.#.#.#######.###.###.###########.# 28 | #...#.#.#...#.#...#.#...#...#.........#.#.....#.#.....#...#.........#.#...#...........#.#.#.#.#.#.#...#.....# 29 | #.###.#######.#####.###.#######.###########.#######.###.###########.#####.#########.###.#.#.#.#.#.#.#######.# 30 | #.........#...#...#.......# E W F B Q F #.......#.#.......#.#...#.# 31 | ###.#.#.#####.#.#####.#.#.# U V O I K A #.#######.###.#####.###.#.# 32 | ZA....#.#.#.#.#.#.#.#.#.#.#.# #...#.......#.#.#...#.....# 33 | #.#.#####.#.#.#.#.#.#####.# ###########.#.#.#.#####.### 34 | #.#...#.........#...#.#.#..XT #.#.....#.........#.#.....# 35 | #.#.#########.###.###.#.#.# #.###.#####.#####.#.#####.# 36 | #.#.#.....#.#...#.......#.# #...#...#.......#.....#...# 37 | #.#####.###.#.###.###.###.# #.###.#########.#####.#.#.# 38 | #.................#.......# KL..#.#...#.#...#...#...#.#..LM 39 | #.###.#.#####.#########.### #.#.###.#.#######.#.###.#.# 40 | #.#...#...#.#.#.#.....#.#..PD #.................#.....#.# 41 | #.###.#.###.###.###.#.###.# ########################### 42 | #...#.#.#.....#.#.#.#.....# #...........#.............# 43 | #########.#####.#.#.#####.# ###.#.###.#.#.###.#.#.###.# 44 | DY....#.#.....#...#.......#.# #...#...#.#.....#.#.#.#.#.# 45 | #.#.#.#####.#.#.#.#####.#.# #######.###############.#.# 46 | KC..#...........#.....#.#.#..KS KI....#...#.....#.#.#.#......FA 47 | #####################.###.# ###.#.#.###.###.#.#.#####.# 48 | ZL....#.....#.......#.....#.# #.....#.#.#...#.....#...#.# 49 | #.#####.#.#.###.#.#.#####.# #########.###.#.#######.### 50 | #.#.....#.#.#.#.#.....#.#.# #.....#.#...........#.....# 51 | #.#.#####.#.#.#######.#.### ###.#.#.###.#######.#.###.# 52 | #...#.....#.......#.....#.# ZV....#...........#.....#...# 53 | #.###.#####.#######.#####.# #.#.###.###.#.###.#.#.#.### 54 | #...#.......#.#...#........QO #.#.#...#...#...#.#.#.#.#..ZU 55 | #####.#######.#.########### ###############.#######.#.# 56 | #...#.#.#.............#...# #.#...#.#.#.#.....#.#.....# 57 | ###.###.###.#.###.###.#.#.# #.#.###.#.#.#######.#####.# 58 | #...........#.#...#...#.#..ZL #.......#...........#...#.# 59 | #.###.#.#.#.#.#####.###.#.# #.#.#.#.#.#######.###.#.### 60 | #...#.#.#.#.#.#.......#.#.# #.#.#.#.....#.......#.#.#..QK 61 | #####.###.#.#########.#.### #.###.#.###.#.#####.#.#.#.# 62 | KL........#.#.....#.......#.# KC....#.#.#.#.#.#.....#.#.#.# 63 | #####.#.###.#######.###.#.# ###.#.###.#########.#.#.#.# 64 | AA..#...#.#...#.#.......#.#.# #...#...#.....#.......#...# 65 | #.#########.#.###########.# #########.###############.# 66 | #...#...#.....#.....#.#....ND #.....#...............#.#.# 67 | #.#####.###.#.#.#####.#.### #####.#.#.#####.#.###.#.### 68 | BI..#.......#.#.#.#.#...#...# #.#.....#.#.#...#...#.....# 69 | #.#.###.#######.#.#.#.###.# #.#####.###.#####.###.##### 70 | #...#...............#.....# XX..#.#.#.#.#...#...#.....#..ND 71 | #########.#######.###.###.# #.#.#.#.#.#.###########.#.# 72 | #.#.....#.#.#.#.#.#.#...#.# #...........#.#.#.#...#...# 73 | #.#.###.###.#.#.###.####### #############.#.#.###.##### 74 | KI..#.#...................#..RN #.................#.#......XX 75 | #.#.###.###.###.###.#####.# #.#.#.#.#.#.#.###.#.#.#.#.# 76 | #.....#...#.#...#.........# #.#.#.#.#.#.#.#.....#.#.#.# 77 | #######.#####.#.###.####### #.#########.#######.#####.# 78 | QO..#.#...#.#.#.#.#.#.#...#..GN DY..........#...#.........#.# 79 | #.#.#.#.#.#.#####.#####.#.# #.###.#.#.#.#####.###.###.# 80 | #.#.#.#.#...#.....#.......# #.#...#.#.#...#...#.......# 81 | #.#.#####.#####.#.#####.#.# #.###.#.#######.###.#.#.### 82 | #.#...#.....#...#.....#.#.# #.#.#.#.#.#.#...#...#.#...# 83 | #.#.#.#.#.#.#.###.#.#.#.### #.#.#.###.#.#.###.#####.### 84 | #...#...#.#...#...#.#.....# #.#.#.....#...#...#.....#.# 85 | #.#.#.#.###.#.###.###.#.### Z L H A Z S O ###.#######.#.#.#####.#.#.# 86 | #.#.#.#.#...#...#.#...#.#.# A M U S U W W #.#.#.#.#...#.#.#.#...#...# 87 | #.###.#######.#######.#.#.#######.###.#############.#####.#######.#######.###.#####.#.#.#.#######.#.###.#.### 88 | #...#.....#.....#.....#...#.......#.#.....#...........#...#.......#.........#...............#...#.....#.#...# 89 | #.#.###.###.#.#####.#.#.#.###.#####.#.#######.###.#######.###.#####.#.#.#######.#.#.#.###.###.###########.### 90 | #.#.#.#...#.#...#.#.#.#.#.#.......#...#.#.#...#.......#.#...#...#.#.#.#...#.#...#.#.#.#...........#...#.....# 91 | #.###.#.###.#####.###.#######.#.#.###.#.#.###.#####.###.#.#####.#.#.#######.#.#.#############.#.###.#####.#.# 92 | #...#...#...#.......#...#.#.#.#.#...#...#...#...#.....#.....#...#...#.....#...#...........#.#.#...#.......#.# 93 | #.#.###.#######.#######.#.#.#.#.#######.#.#.###.#######.###.#.#####.###.###.#####.###.#.###.###.#.###.###.### 94 | #.#...#.......#.....#.#.#...#.#.#.#.#.#...#...#.#...#.#.#...#.#.#...#.....#...#.#...#.#.....#.#.#.#.#.#.....# 95 | #.#.###.###########.#.#####.###.#.#.#.#####.###.###.#.###.###.#.###.#.###.#.###.#.#####.#####.#.###.###.#.#.# 96 | #.#.#.....#.......................#...#.....#.....#.......#...#...#.#...#.#.#...#...#.#.....#.......#...#.#.# 97 | #######.#########.#.#.#######.#######.#.###.#.#.#######.#####.#.###.###.#.#.#.#######.###.#####.#.#.######### 98 | #.........#...#...#.#.#...#...#.........#...#.#.....#.......#.....#.....#.#.....#.#...#.#.#.#...#.#.#...#.#.# 99 | #.#####.#####.#.#.#######.#.#.#####.#.###.###.#########.###.#.#######.#.#.#.###.#.#.###.###.###.#.#.###.#.#.# 100 | #.#...........#.#.#...#.#...#.#.....#...#.#...#.#...#...#...#...#.....#.#.#.#...............#...#.#...#.....# 101 | #####.###.###.#.#.###.#.###.#.###.#.#.#####.#.###.#.#.###.###.#######.###.#.#.###.#####.###.#######.###.##### 102 | #.#.....#.#.#.#.#.#.#.......#...#.#.#...#...#.....#.#.#.....#.......#.#...#.#.#.......#...#.......#.........# 103 | #.#####.#.#.###.###.#.#######.#######.#####.#.#.###.###.###.###.###.#####.#.#########.#.#######.###.#.###.#.# 104 | #.......#.....#.#.#.#.#...#.#...#.#.#...#...#.#.#.....#.#...#.#...#.#.....#.........#.#.#.#...#...#.#...#.#.# 105 | #.#.#######.#####.#.#.###.#.#.#.#.#.###.###.#####.#.#####.###.#.###.#.#.###.#.#######.###.#.#########.#.###.# 106 | #.#...#.....#.....#...#.......#.#.........#.#.....#...#...#.......#.#.#.#.#.#.#.....#.#.....#.#...#.#.#...#.# 107 | #.#########.###.#.###.###.#.#.#######.#.#.#.#####.#.###.#.###.###.###.###.#.#.###.#####.###.#.#.###.###.###.# 108 | #...#.......#...#...#.#.#.#.#.#...#...#.#.#.#.....#.#...#...#.#.#.#.......#.#.#.#.......#.#.#.#.#.....#.#...# 109 | #####.#.###.#######.###.#####.###.###.###########.#######.###.#.###.#.###.#.###.#.#.###.#.###.#.###.#.###.### 110 | #.....#.#.#.#...#.............#.....#.#.#.#.........#.....#.......#.#.#...#.#.....#.#...............#...#...# 111 | ###.#.#.#.#.###.#######.###.#.#.###.#.#.#.#####.#####.#######.#########.#.#.###.#.#######.###.#.#.#.#####.#.# 112 | #...#.#...#.#...........#...#...#...#.....#.........#.......#.........#.#.#.....#.......#...#.#.#.#.....#.#.# 113 | #################################.#####.###########.#####.#.#####.#######.###.############################### 114 | P F X Z O S G W 115 | D O T Z W W N V -------------------------------------------------------------------------------- /day20/day20_part2.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Set, Iterator, NamedTuple 2 | from collections import defaultdict, deque 3 | 4 | deltas1 = [(0, 1), (1, 0)] 5 | deltas2 = [(0, 1), (1, 0), (0, -1), (-1, 0)] 6 | 7 | class XY(NamedTuple): 8 | x: int 9 | y: int 10 | 11 | 12 | class Maze(NamedTuple): 13 | start: XY 14 | end: XY 15 | neighbors: Dict[XY, List[XY]] 16 | 17 | 18 | def parse(raw: str) -> Maze: 19 | lines = raw.strip("\n").split("\n") 20 | nr = len(lines) 21 | nc = len(lines[0]) 22 | 23 | def neighbors(loc: XY, deltas) -> Iterator[XY]: 24 | x, y = loc 25 | for dx, dy in deltas: 26 | neighbor = XY(x + dx, y + dy) 27 | if 0 <= neighbor.x < nr and 0 <= neighbor.y < nc: 28 | yield neighbor 29 | 30 | # First let's find all the portals 31 | portals: Dict[str, List[XY]] = defaultdict(list) 32 | 33 | for i, line in enumerate(lines): 34 | for j, c in enumerate(line): 35 | if 'A' <= c <= 'Z': 36 | for i2, j2 in neighbors(XY(i, j), deltas1): 37 | c2 = lines[i2][j2] 38 | if 'A' <= c2 <= 'Z': 39 | portals[f"{c}{c2}"].extend([XY(i, j), XY(i2, j2)]) 40 | 41 | # handle start and end separately 42 | for i, line in enumerate(lines): 43 | for j, c in enumerate(line): 44 | if c == '.': 45 | loc = XY(i, j) 46 | for loc2 in neighbors(loc, deltas2): 47 | if loc2 in portals['AA']: 48 | start = loc 49 | elif loc2 in portals['ZZ']: 50 | end = loc 51 | 52 | inside_portal_neighbors = {} 53 | inside_neighbor_portals = {} 54 | outside_portal_neighbors = {} 55 | outside_neighbor_portals = {} 56 | 57 | 58 | # find dots that are next to portals 59 | for i, line in enumerate(lines): 60 | for j, c in enumerate(line): 61 | if c == '.': 62 | loc = XY(i, j) 63 | outside = i == 2 or i == nr - 3 or j == 2 or j == nc - 3 64 | for loc2 in neighbors(loc, deltas2): 65 | for portal, locs in portals.items(): 66 | if loc2 in locs: 67 | if outside: 68 | outside_neighbor_portals[loc] = portal 69 | outside_portal_neighbors[portal] = loc 70 | else: 71 | inside_neighbor_portals[loc] = portal 72 | inside_portal_neighbors[portal] = loc 73 | 74 | 75 | # each entry has to have a square, and a change in level 76 | neighbor_dict = defaultdict(list) 77 | 78 | # find neighbors 79 | for i, line in enumerate(lines): 80 | for j, c in enumerate(line): 81 | if c == '.': 82 | loc = XY(i, j) 83 | # get direct neighbors 84 | for loc2 in neighbors(loc, deltas2): 85 | i2, j2 = loc2 86 | c2 = lines[i2][j2] 87 | if c2 == '.': 88 | neighbor_dict[loc].append((loc2, 0)) 89 | # get portal neighbors 90 | outside_portal = outside_neighbor_portals.get(loc) 91 | if outside_portal and outside_portal not in ['AA', 'ZZ']: 92 | other_loc = inside_portal_neighbors[outside_portal] 93 | neighbor_dict[loc].append((other_loc, -1)) 94 | 95 | inside_portal = inside_neighbor_portals.get(loc) 96 | if inside_portal: 97 | other_loc = outside_portal_neighbors[inside_portal] 98 | neighbor_dict[loc].append((other_loc, +1)) 99 | 100 | return Maze(start, 101 | end, 102 | neighbors=neighbor_dict) 103 | 104 | RAW = """ A 105 | A 106 | #######.######### 107 | #######.........# 108 | #######.#######.# 109 | #######.#######.# 110 | #######.#######.# 111 | ##### B ###.# 112 | BC...## C ###.# 113 | ##.## ###.# 114 | ##...DE F ###.# 115 | ##### G ###.# 116 | #########.#####.# 117 | DE..#######...###.# 118 | #.#########.###.# 119 | FG..#########.....# 120 | ###########.##### 121 | Z 122 | Z """ 123 | 124 | MAZE = parse(RAW) 125 | 126 | def shortest_path(maze: Maze) -> int: 127 | # (loc, level) 128 | visited = {(maze.start, 0)} 129 | # (loc, level, num_steps) 130 | q = deque([(maze.start, 0, 0)]) 131 | 132 | 133 | while q: 134 | loc, level, num_steps = q.popleft() 135 | print(loc, level, num_steps) 136 | 137 | for nbor, level_delta in maze.neighbors[loc]: 138 | if nbor == maze.end and level == 0: 139 | return num_steps + 1 140 | 141 | new_level = level + level_delta 142 | 143 | if new_level >= 0 and (nbor, new_level) not in visited: 144 | visited.add((nbor, new_level)) 145 | q.append((nbor, new_level, num_steps + 1)) 146 | 147 | 148 | with open('day20.txt') as f: 149 | raw = f.read() 150 | 151 | maze = parse(raw) 152 | 153 | print(shortest_path(maze)) -------------------------------------------------------------------------------- /day21/day21.scratchwork: -------------------------------------------------------------------------------- 1 | NOT A J # set J to "A is a hole" 2 | NOT B T # set T to "B is a hole" 3 | AND T J # set J to "A is a hole and B is a hole" 4 | NOT C T # set T to "C is a hole" 5 | AND T J # 6 | AND D J 7 | 8 | 9 | .....@...@....... 10 | ....@.@.@.@...... 11 | ...J...J...@..... 12 | #####..#.######## 13 | ABCD 14 | ABCD 15 | 16 | 17 | NOT A J # set J to "A is a hole" 18 | NOT B T # set J to "B is a hole" 19 | OR T J # set J to "A is a hole or B is a hole" 20 | 21 | 22 | ................. 23 | ................. 24 | ................. 25 | #####...######### 26 | 27 | NOT A J # set J to "A is a hole" 28 | NOT B T # set J to "B is a hole" 29 | OR T J # set J to "A is a hole or B is a hole" 30 | NOT D T # set T to "D is a hole" 31 | NOT T T # set T to "D is not a hole" 32 | AND T J # set J to "(~A or ~B) and D" 33 | 34 | 35 | ................. 36 | ................. 37 | @................ 38 | #####.#..######## 39 | ABCD 40 | ABCD 41 | 42 | NOT A J # set J to "A is a hole" 43 | NOT B T # set T to "B is a hole" 44 | OR T J # set J to "A is a hole or B is a hole" 45 | NOT C T # set T to "C is a hole" 46 | OR T J # set J to "A is a hole, or B is a hole, or C is a hole" 47 | NOT D T # set T to "D is a hole" 48 | NOT T T # set T to "D is not a hole" 49 | AND T J # set J to "(~A or ~B) and D" 50 | 51 | 52 | 53 | ................. 54 | ................. 55 | ......@.......... 56 | #####.#.#..###### 57 | ABCDEFGHI 58 | ABCDEFGHI 59 | ABCDEFGHI 60 | 61 | D is solid 62 | H is solid 63 | A or B or C or D is not solid 64 | 65 | NOT A J # set J to "A is a hole" 66 | NOT B T # set T to "B is a hole" 67 | OR T J # set J to "A is a hole or B is a hole" 68 | NOT C T # set T to "C is a hole" 69 | OR T J # set J to "A is a hole, or B is a hole, or C is a hole" 70 | NOT D T # set T to "D is a hole" 71 | NOT T T # set T to "D is not a hole" 72 | AND T J # set J to "(~A or ~B) and D" 73 | NOT H T # set T to "H is a hole" 74 | NOT T T # set T to "H is not a hole" 75 | AND T J # set J to 76 | 77 | ................. 78 | ................. 79 | ...@............. 80 | #####...####..### 81 | ABCDEFGHI 82 | ABCDE 83 | 84 | 85 | -------------------------------------------------------------------------------- /day21/day21.scratchwork2: -------------------------------------------------------------------------------- 1 | #####..#.######## 2 | JABCD 3 | JABCD 4 | 5 | #####...######### 6 | JABCD 7 | 8 | #####.#..######## 9 | JABCD 10 | JABCD 11 | 12 | #####.#.#..###### 13 | JABCD 14 | JABCD 15 | 16 | #####...####..### 17 | JABCD 18 | JABCD 19 | 20 | #####.#.######### 21 | JABCD 22 | 23 | #####.##.##..#### 24 | JABCD 25 | JABCD 26 | JABCDEFGHI 27 | 28 | #####..#.######## 29 | JABCD 30 | JABCD 31 | 32 | not A or (NOT C and D) or (NOT E and D) 33 | 34 | -- ~A 35 | NOT A J 36 | -- ~C & D 37 | NOT C T 38 | AND D T 39 | OR T J 40 | 41 | NOT E T 42 | AND D T 43 | OR T J 44 | 45 | D AND (NOT A or NOT B or NOT C) AND (E OR H) 46 | 47 | NOT A J 48 | NOT B T 49 | OR T J 50 | NOT C T 51 | OR T J 52 | AND D J 53 | OR E T 54 | OR H T 55 | AND 56 | 57 | 58 | -------------------------------------------------------------------------------- /day22/day22.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | Deck = List[int] 4 | 5 | def deal_into_new_stack(deck: Deck) -> Deck: 6 | return list(reversed(deck)) 7 | 8 | def cut(deck: Deck, n: int) -> Deck: 9 | num_cards = len(deck) 10 | n = n % num_cards 11 | return deck[n:] + deck[:n] 12 | 13 | def with_increment(deck: Deck, n: int) -> Deck: 14 | result = deck[:] 15 | num_cards = len(deck) 16 | pos = 0 17 | for card in deck: 18 | result[pos] = card 19 | pos = (pos + n) % num_cards 20 | 21 | return result 22 | 23 | TEST_DECK = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 24 | assert deal_into_new_stack(TEST_DECK) == [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 25 | assert cut(TEST_DECK, 6) == [6, 7, 8, 9, 0, 1, 2, 3, 4, 5] 26 | assert cut(TEST_DECK, -4) == [6, 7, 8, 9, 0, 1, 2, 3, 4, 5] 27 | assert with_increment(TEST_DECK, 3) == [0, 7, 4, 1, 8, 5, 2, 9, 6, 3] 28 | 29 | deck = list(range(10007)) 30 | 31 | with open('day22.txt') as f: 32 | for line in f: 33 | line = line.strip() 34 | if line == "deal into new stack": 35 | deck = deal_into_new_stack(deck) 36 | elif line.startswith("cut"): 37 | n = int(line.split()[-1]) 38 | deck = cut(deck, n) 39 | elif line.startswith("deal with increment"): 40 | n = int(line.split()[-1]) 41 | deck = with_increment(deck, n) 42 | 43 | -------------------------------------------------------------------------------- /day22/day22.txt: -------------------------------------------------------------------------------- 1 | deal with increment 73 2 | cut -6744 3 | deal into new stack 4 | cut 9675 5 | deal with increment 63 6 | cut -8047 7 | deal with increment 21 8 | cut -4726 9 | deal with increment 39 10 | cut -537 11 | deal with increment 39 12 | cut -6921 13 | deal with increment 15 14 | cut 2673 15 | deal into new stack 16 | cut 2483 17 | deal with increment 66 18 | deal into new stack 19 | cut 1028 20 | deal with increment 48 21 | deal into new stack 22 | cut -418 23 | deal with increment 15 24 | cut 9192 25 | deal with increment 62 26 | deal into new stack 27 | deal with increment 23 28 | cut 2840 29 | deal with increment 50 30 | cut 6222 31 | deal with increment 20 32 | deal into new stack 33 | cut -6855 34 | deal with increment 50 35 | cut -1745 36 | deal with increment 27 37 | cut 4488 38 | deal with increment 71 39 | deal into new stack 40 | deal with increment 28 41 | cut -2707 42 | deal with increment 40 43 | deal into new stack 44 | deal with increment 32 45 | cut 8171 46 | deal with increment 74 47 | deal into new stack 48 | cut -2070 49 | deal with increment 61 50 | deal into new stack 51 | deal with increment 46 52 | cut 4698 53 | deal with increment 23 54 | cut -3480 55 | deal with increment 30 56 | cut -6662 57 | deal with increment 53 58 | cut -5283 59 | deal with increment 43 60 | deal into new stack 61 | cut -1319 62 | deal with increment 9 63 | cut -8990 64 | deal into new stack 65 | deal with increment 9 66 | deal into new stack 67 | cut -5058 68 | deal with increment 28 69 | cut -7975 70 | deal with increment 57 71 | cut 2766 72 | deal with increment 19 73 | cut 8579 74 | deal into new stack 75 | deal with increment 22 76 | deal into new stack 77 | cut 9835 78 | deal with increment 36 79 | cut -2485 80 | deal with increment 52 81 | cut -5818 82 | deal with increment 9 83 | cut 5946 84 | deal with increment 51 85 | deal into new stack 86 | cut -5600 87 | deal with increment 75 88 | cut -9885 89 | deal with increment 27 90 | cut -2942 91 | deal with increment 68 92 | cut 3874 93 | deal with increment 36 94 | deal into new stack 95 | deal with increment 20 96 | cut -2565 97 | deal with increment 17 98 | cut -9109 99 | deal with increment 62 100 | cut 2175 -------------------------------------------------------------------------------- /day22/day22_part2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | m = 119315717514047 4 | n = 101741582076661 5 | pos = 2020 6 | shuffles = { 'deal with increment ': lambda x,m,a,b: (a*x %m, b*x %m), 7 | 'deal into new stack': lambda _,m,a,b: (-a %m, (m-1-b)%m), 8 | 'cut ': lambda x,m,a,b: (a, (b-x)%m) } 9 | a,b = 1,0 10 | with open('day22.txt') as f: 11 | for s in f.read().strip().split('\n'): 12 | for name,fn in shuffles.items(): 13 | if s.startswith(name): 14 | arg = int(s[len(name):]) if name[-1] == ' ' else 0 15 | a,b = fn(arg, m, a, b) 16 | break 17 | r = (b * pow(1-a, m-2, m)) % m 18 | print(f"Card at #{pos}: {((pos - r) * pow(a, n*(m-2), m) + r) % m}") -------------------------------------------------------------------------------- /day24/day24.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | Grid = List[bool] # 5x5 4 | 5 | def getxy(grid: Grid, x: int, y: int) -> bool: 6 | return grid[x + 5 * y] 7 | 8 | 9 | deltas = [(0, 1), (1, 0), (0, -1), (-1, 0)] 10 | 11 | def step(grid: Grid) -> Grid: 12 | next_grid = grid[:] 13 | 14 | for x in range(5): 15 | for y in range(5): 16 | num_adjacent = sum(getxy(grid, x + dx, y + dy) 17 | for dx, dy in deltas 18 | if 0 <= x + dx < 5 and 0 <= y + dy < 5) 19 | """ 20 | A bug dies (becoming an empty space) unless there is exactly one bug adjacent to it. 21 | An empty space becomes infested with a bug if exactly one or two bugs are adjacent to it. 22 | """ 23 | bug_now = getxy(grid, x, y) 24 | bug_next = (bug_now and num_adjacent == 1) or (not bug_now and num_adjacent in (1, 2)) 25 | next_grid[x + 5 * y] = bug_next 26 | 27 | return next_grid 28 | 29 | RAW = """....# 30 | #..#. 31 | #..## 32 | ..#.. 33 | #....""" 34 | 35 | def parse(raw: str) -> Grid: 36 | return [c == '#' for line in raw.strip().split("\n") for c in line.strip()] 37 | 38 | def biodiversity(grid: Grid) -> int: 39 | return sum(2 ** i * bug for i, bug in enumerate(grid)) 40 | 41 | def find_repeat(grid: Grid) -> int: 42 | seen = set() 43 | while True: 44 | bio = biodiversity(grid) 45 | if bio in seen: 46 | return bio 47 | seen.add(bio) 48 | grid = step(grid) 49 | 50 | raw = """..### 51 | .#### 52 | ...#. 53 | .#..# 54 | #.###""" 55 | 56 | grid = parse(raw) 57 | 58 | print(find_repeat(grid)) -------------------------------------------------------------------------------- /day24/day24_part2.py: -------------------------------------------------------------------------------- 1 | from typing import List, NamedTuple, Iterable 2 | from collections import defaultdict 3 | import copy 4 | 5 | Grid = List[bool] # 5x5 6 | 7 | class Loc(NamedTuple): 8 | level: int 9 | x: int 10 | y: int 11 | 12 | deltas = [(1, 0), (-1, 0), (0, 1), (0, -1)] 13 | 14 | class RecursiveGrids: 15 | def __init__(self, top_level_grid: Grid) -> None: 16 | self.grids = defaultdict(lambda: [False for _ in range(25)]) 17 | self.grids[0] = top_level_grid[:] 18 | 19 | def total_bugs(self) -> int: 20 | return sum(v for grid in self.grids.values() for v in grid) 21 | 22 | def neighbors(self, loc: Loc) -> Iterable[Loc]: 23 | level, x, y = loc 24 | if (x, y) == (2, 1): 25 | yield Loc(level, 2, 0) 26 | yield Loc(level, 1, 1) 27 | yield Loc(level, 3, 1) 28 | for x in range(5): 29 | yield Loc(level + 1, x, 0) 30 | elif (x, y) == (1, 2): 31 | yield Loc(level, 0, 2) 32 | yield Loc(level, 1, 1) 33 | yield Loc(level, 1, 3) 34 | for y in range(5): 35 | yield Loc(level + 1, 0, y) 36 | elif (x, y) == (3, 2): 37 | yield Loc(level, 4, 2) 38 | yield Loc(level, 3, 1) 39 | yield Loc(level, 3, 3) 40 | for y in range(5): 41 | yield Loc(level + 1, 4, y) 42 | elif (x, y) == (2, 3): 43 | yield Loc(level, 1, 3) 44 | yield Loc(level, 3, 3) 45 | yield Loc(level, 2, 4) 46 | for x in range(5): 47 | yield Loc(level + 1, x, 4) 48 | else: 49 | for dx, dy in deltas: 50 | newx = x + dx 51 | newy = y + dy 52 | if 0 <= newx < 5 and 0 <= newy < 5: 53 | yield Loc(level, newx, newy) 54 | if x == 0: 55 | yield Loc(level - 1, 1, 2) 56 | if y == 0: 57 | yield Loc(level - 1, 2, 1) 58 | if x == 4: 59 | yield Loc(level - 1, 3, 2) 60 | if y == 4: 61 | yield Loc(level - 1, 2, 3) 62 | 63 | def step(self) -> None: 64 | num_grids = len(self.grids) 65 | # add to defaultdict 66 | maxk = max(self.grids) 67 | mink = min(self.grids) 68 | 69 | self.grids[maxk+1] 70 | self.grids[mink-1] 71 | 72 | new_grids = copy.deepcopy(self.grids) 73 | levels = list(self.grids.keys()) 74 | for level in levels: 75 | grid = self.grids[level] 76 | for x in range(5): 77 | for y in range(5): 78 | if x == y == 2: 79 | continue 80 | 81 | loc = Loc(level, x, y) 82 | neighbors = list(self.neighbors(loc)) 83 | #print(neighbors) 84 | values = [getxy(self.grids[nlevel], nx, ny) 85 | for (nlevel, nx, ny) in neighbors] 86 | #print(values) 87 | num_adjacent = sum(values) 88 | 89 | #print(loc, num_adjacent) 90 | #print() 91 | 92 | bug_now = getxy(grid, x, y) 93 | bug_next = (bug_now and num_adjacent == 1) or (not bug_now and num_adjacent in (1, 2)) 94 | new_grids[level][x + 5 * y] = bug_next 95 | 96 | self.grids = new_grids 97 | 98 | 99 | def getxy(grid: Grid, x: int, y: int) -> bool: 100 | return grid[x + 5 * y] 101 | 102 | 103 | RAW = """ 104 | ....# 105 | #..#. 106 | #..## 107 | ..#.. 108 | #....""" 109 | 110 | def parse(raw: str) -> Grid: 111 | return [c == '#' for line in raw.strip().split("\n") for c in line.strip()] 112 | 113 | def show(grid: Grid): 114 | for start in range(0, 25, 5): 115 | line = grid[start:start+5] 116 | chars = ['#' if bug else '.' for bug in line] 117 | print("".join(chars)) 118 | 119 | GRID = parse(RAW) 120 | GRIDS = RecursiveGrids(GRID) 121 | 122 | for i in range(10): 123 | print(i, GRIDS.total_bugs()) 124 | GRIDS.step() 125 | print(GRIDS.total_bugs()) 126 | 127 | raw = """..### 128 | .#### 129 | ...#. 130 | .#..# 131 | #.###""" 132 | 133 | grid = parse(raw) 134 | grids = RecursiveGrids(grid) 135 | 136 | for i in range(200): 137 | print(i, grids.total_bugs()) 138 | grids.step() 139 | print(grids.total_bugs()) 140 | -------------------------------------------------------------------------------- /day25/day25.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List, NamedTuple, Tuple, Iterable, Set, Dict, Callable 3 | from enum import Enum 4 | import itertools 5 | from collections import deque, defaultdict 6 | import logging 7 | import copy 8 | 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | 13 | class Opcode(Enum): 14 | ADD = 1 15 | MULTIPLY = 2 16 | STORE_INPUT = 3 17 | SEND_TO_OUTPUT = 4 18 | JUMP_IF_TRUE = 5 19 | JUMP_IF_FALSE = 6 20 | LESS_THAN = 7 21 | EQUALS = 8 22 | ADJUST_RELATIVE_BASE = 9 23 | 24 | END_PROGRAM = 99 25 | 26 | 27 | Modes = List[int] 28 | Program = List[int] 29 | 30 | 31 | class EndProgram(Exception): pass 32 | 33 | 34 | def parse_opcode(opcode: int, num_modes: int = 3) -> Tuple[Opcode, Modes]: 35 | # logging.debug(f"parsing {opcode}") 36 | 37 | opcode_part = opcode % 100 38 | 39 | modes: List[int] = [] 40 | opcode = opcode // 100 41 | 42 | for _ in range(num_modes): 43 | modes.append(opcode % 10) 44 | opcode = opcode // 10 45 | 46 | return Opcode(opcode_part), modes 47 | 48 | 49 | class IntcodeComputer: 50 | def __init__(self, program: List[int], get_input: Callable[[], int]) -> None: 51 | self.program = defaultdict(int) 52 | self.program.update({i: value for i, value in enumerate(program)}) 53 | self.get_input = get_input 54 | self.pos = 0 55 | self.relative_base = 0 56 | 57 | def save(self): 58 | return [ 59 | copy.deepcopy(self.program), 60 | self.get_input, 61 | self.pos, 62 | self.relative_base 63 | ] 64 | 65 | @staticmethod 66 | def load(program, get_input, pos, relative_base): 67 | computer = IntcodeComputer([], get_input) 68 | computer.program = program 69 | computer.pos = pos 70 | computer.relative_base = relative_base 71 | return computer 72 | 73 | def _get_value(self, pos: int, mode: int) -> int: 74 | if mode == 0: 75 | # pointer mode 76 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos]]}") 77 | return self.program[self.program[pos]] 78 | elif mode == 1: 79 | # immediate mode 80 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 81 | return self.program[pos] 82 | elif mode == 2: 83 | # relative mode 84 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[self.program[pos] + self.relative_base]}") 85 | return self.program[self.program[pos] + self.relative_base] 86 | else: 87 | raise ValueError(f"unknown mode: {mode}") 88 | 89 | def _loc(self, pos: int, mode: int) -> int: 90 | if mode == 0: 91 | # pointer mode 92 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos]}") 93 | return self.program[pos] 94 | elif mode == 2: 95 | # relative mode 96 | # logging.debug(f"pos: {pos}, mode: {mode}, value: {self.program[pos] + self.relative_base}") 97 | return self.program[pos] + self.relative_base 98 | 99 | def go(self) -> int: 100 | 101 | while True: 102 | # logging.debug(f"program: {self.program}") 103 | # logging.debug(f"pos: {self.pos}, inputs: {self.inputs}, relative_base: {self.relative_base}") 104 | 105 | opcode, modes = parse_opcode(self.program[self.pos]) 106 | 107 | # logging.debug(f"opcode: {opcode}, modes: {modes}") 108 | 109 | if opcode == Opcode.END_PROGRAM: 110 | raise EndProgram 111 | 112 | elif opcode == Opcode.ADD: 113 | value1 = self._get_value(self.pos + 1, modes[0]) 114 | value2 = self._get_value(self.pos + 2, modes[1]) 115 | loc = self._loc(self.pos + 3, modes[2]) 116 | 117 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 118 | 119 | self.program[loc] = value1 + value2 120 | self.pos += 4 121 | 122 | elif opcode == Opcode.MULTIPLY: 123 | value1 = self._get_value(self.pos + 1, modes[0]) 124 | value2 = self._get_value(self.pos + 2, modes[1]) 125 | loc = self._loc(self.pos + 3, modes[2]) 126 | 127 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 128 | 129 | self.program[loc] = value1 * value2 130 | self.pos += 4 131 | 132 | elif opcode == Opcode.STORE_INPUT: 133 | # Get input and store at location 134 | loc = self._loc(self.pos + 1, modes[0]) 135 | input_value = self.get_input() 136 | self.program[loc] = input_value 137 | self.pos += 2 138 | 139 | elif opcode == Opcode.SEND_TO_OUTPUT: 140 | # Get output from location 141 | value = self._get_value(self.pos + 1, modes[0]) 142 | self.pos += 2 143 | # logging.debug(f"output: {value}") 144 | 145 | #### 146 | #### 147 | 148 | return value 149 | 150 | elif opcode == Opcode.JUMP_IF_TRUE: 151 | # jump if true 152 | value1 = self._get_value(self.pos + 1, modes[0]) 153 | value2 = self._get_value(self.pos + 2, modes[1]) 154 | 155 | # logging.debug(f"value1: {value1}, value2: {value2}") 156 | 157 | if value1 != 0: 158 | self.pos = value2 159 | else: 160 | self.pos += 3 161 | 162 | elif opcode == Opcode.JUMP_IF_FALSE: 163 | value1 = self._get_value(self.pos + 1, modes[0]) 164 | value2 = self._get_value(self.pos + 2, modes[1]) 165 | 166 | # logging.debug(f"value1: {value1}, value2: {value2}") 167 | 168 | if value1 == 0: 169 | self.pos = value2 170 | else: 171 | self.pos += 3 172 | 173 | elif opcode == Opcode.LESS_THAN: 174 | value1 = self._get_value(self.pos + 1, modes[0]) 175 | value2 = self._get_value(self.pos + 2, modes[1]) 176 | loc = self._loc(self.pos + 3, modes[2]) 177 | 178 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 179 | 180 | if value1 < value2: 181 | self.program[loc] = 1 182 | else: 183 | self.program[loc] = 0 184 | self.pos += 4 185 | 186 | elif opcode == Opcode.EQUALS: 187 | value1 = self._get_value(self.pos + 1, modes[0]) 188 | value2 = self._get_value(self.pos + 2, modes[1]) 189 | loc = self._loc(self.pos + 3, modes[2]) 190 | 191 | # logging.debug(f"value1: {value1}, value2: {value2}, loc: {loc}") 192 | 193 | if value1 == value2: 194 | self.program[loc] = 1 195 | else: 196 | self.program[loc] = 0 197 | self.pos += 4 198 | 199 | elif opcode == Opcode.ADJUST_RELATIVE_BASE: 200 | value = self._get_value(self.pos + 1, modes[0]) 201 | 202 | # logging.debug(f"value: {value}") 203 | 204 | self.relative_base += value 205 | self.pos += 2 206 | 207 | else: 208 | raise ValueError(f"invalid opcode: {opcode}") 209 | 210 | with open('day25.txt') as f: 211 | raw = f.read() 212 | 213 | program = [int(x) for x in raw.strip().split(",")] 214 | 215 | buffer = deque() 216 | 217 | def get_input() -> int: 218 | if not buffer: 219 | for c in input(): 220 | buffer.append(ord(c)) 221 | buffer.append(10) 222 | return buffer.popleft() 223 | 224 | 225 | computer = IntcodeComputer(program, get_input) 226 | 227 | while True: 228 | print(chr(computer.go()), end='') 229 | -------------------------------------------------------------------------------- /day25/gen.py: -------------------------------------------------------------------------------- 1 | items = [ 2 | 'easter egg', 3 | 'mug', 4 | 'sand', 5 | 'weather machine', 6 | 'festive hat', 7 | 'shell', 8 | 'whirled peas', 9 | 'space heater' 10 | ] 11 | 12 | def make_command(sset): 13 | drops = [f"drop {item}" for item in items] 14 | takes = [f"take {item}" for item in sset] 15 | south = ["south"] 16 | 17 | return "\n".join(drops + takes + south) 18 | 19 | for x in range(2 ** 8): 20 | sset = {item for i, item in enumerate(items) if x >> i & 1} 21 | print(make_command(sset)) 22 | input() 23 | 24 | --------------------------------------------------------------------------------