├── .activate.sh ├── .deactivate.sh ├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── day00 ├── __init__.py ├── input.txt └── part1.py ├── day01 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day02 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day03 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day04 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day05 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day06 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day07 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day08 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day09 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day10 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day11 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day12 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day13 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day14 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day15 ├── __init__.py ├── i2.txt ├── input.txt ├── part1.py └── part2.py ├── day16 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day17 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day18 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day19 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day20 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day21 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day22 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day23 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day24 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day25 ├── __init__.py ├── input.txt └── part1.py ├── requirements.txt ├── setup.cfg └── support-src ├── setup.cfg ├── setup.py ├── support.py └── support_test.py /.activate.sh: -------------------------------------------------------------------------------- 1 | venv/bin/activate -------------------------------------------------------------------------------- /.deactivate.sh: -------------------------------------------------------------------------------- 1 | deactivate 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | /.env 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: /input\.txt$ 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: end-of-file-fixer 8 | - id: check-yaml 9 | - id: debug-statements 10 | - id: double-quote-string-fixer 11 | - id: name-tests-test 12 | - id: requirements-txt-fixer 13 | - repo: https://github.com/asottile/reorder_python_imports 14 | rev: v3.14.0 15 | hooks: 16 | - id: reorder-python-imports 17 | args: [ 18 | --py312-plus, 19 | --add-import, 'from __future__ import annotations', 20 | --application-directories, '.:support-src', 21 | ] 22 | - repo: https://github.com/asottile/add-trailing-comma 23 | rev: v3.1.0 24 | hooks: 25 | - id: add-trailing-comma 26 | args: [--py36-plus] 27 | - repo: https://github.com/asottile/pyupgrade 28 | rev: v3.19.0 29 | hooks: 30 | - id: pyupgrade 31 | args: [--py312-plus] 32 | - repo: https://github.com/hhatto/autopep8 33 | rev: v2.3.1 34 | hooks: 35 | - id: autopep8 36 | - repo: https://github.com/PyCQA/flake8 37 | rev: 7.1.1 38 | hooks: 39 | - id: flake8 40 | - repo: https://github.com/pre-commit/mirrors-mypy 41 | rev: v1.13.0 42 | hooks: 43 | - id: mypy 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | advent of code 2024 2 | =================== 3 | 4 | https://adventofcode.com/2024 5 | 6 | ### stream / youtube 7 | 8 | - [Streamed daily on twitch](https://twitch.tv/anthonywritescode) 9 | - [Streams uploaded to youtube afterwards](https://www.youtube.com/@anthonywritescode-vods) 10 | 11 | ### about 12 | 13 | for 2024, I'm planning to implement in python 14 | -------------------------------------------------------------------------------- /day00/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day00/__init__.py -------------------------------------------------------------------------------- /day00/input.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day00/input.txt -------------------------------------------------------------------------------- /day00/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | numbers = support.parse_numbers_split(s) 15 | for n in numbers: 16 | pass 17 | 18 | lines = s.splitlines() 19 | for line in lines: 20 | pass 21 | # TODO: implement solution here! 22 | return 0 23 | 24 | 25 | INPUT_S = '''\ 26 | 27 | ''' 28 | EXPECTED = 1 29 | 30 | 31 | @pytest.mark.parametrize( 32 | ('input_s', 'expected'), 33 | ( 34 | (INPUT_S, EXPECTED), 35 | ), 36 | ) 37 | def test(input_s: str, expected: int) -> None: 38 | assert compute(input_s) == expected 39 | 40 | 41 | def main() -> int: 42 | parser = argparse.ArgumentParser() 43 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 44 | args = parser.parse_args() 45 | 46 | with open(args.data_file) as f, support.timing(): 47 | print(compute(f.read())) 48 | 49 | return 0 50 | 51 | 52 | if __name__ == '__main__': 53 | raise SystemExit(main()) 54 | -------------------------------------------------------------------------------- /day01/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day01/__init__.py -------------------------------------------------------------------------------- /day01/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | lst1, lst2 = [], [] 15 | for line in s.splitlines(): 16 | n1_s, n2_s = line.split() 17 | lst1.append(int(n1_s)) 18 | lst2.append(int(n2_s)) 19 | 20 | lst1.sort() 21 | lst2.sort() 22 | return sum(abs(n2 - n1) for n1, n2 in zip(lst1, lst2)) 23 | 24 | 25 | INPUT_S = '''\ 26 | 3 4 27 | 4 3 28 | 2 5 29 | 1 3 30 | 3 9 31 | 3 3 32 | ''' 33 | EXPECTED = 11 34 | 35 | 36 | @pytest.mark.parametrize( 37 | ('input_s', 'expected'), 38 | ( 39 | (INPUT_S, EXPECTED), 40 | ), 41 | ) 42 | def test(input_s: str, expected: int) -> None: 43 | assert compute(input_s) == expected 44 | 45 | 46 | def main() -> int: 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 49 | args = parser.parse_args() 50 | 51 | with open(args.data_file) as f, support.timing(): 52 | print(compute(f.read())) 53 | 54 | return 0 55 | 56 | 57 | if __name__ == '__main__': 58 | raise SystemExit(main()) 59 | -------------------------------------------------------------------------------- /day01/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | lst1 = [] 16 | lst2: collections.Counter[int] = collections.Counter() 17 | for line in s.splitlines(): 18 | n1_s, n2_s = line.split() 19 | lst1.append(int(n1_s)) 20 | lst2[int(n2_s)] += 1 21 | 22 | return sum(n * lst2[n] for n in lst1) 23 | 24 | 25 | INPUT_S = '''\ 26 | 3 4 27 | 4 3 28 | 2 5 29 | 1 3 30 | 3 9 31 | 3 3 32 | ''' 33 | EXPECTED = 31 34 | 35 | 36 | @pytest.mark.parametrize( 37 | ('input_s', 'expected'), 38 | ( 39 | (INPUT_S, EXPECTED), 40 | ), 41 | ) 42 | def test(input_s: str, expected: int) -> None: 43 | assert compute(input_s) == expected 44 | 45 | 46 | def main() -> int: 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 49 | args = parser.parse_args() 50 | 51 | with open(args.data_file) as f, support.timing(): 52 | print(compute(f.read())) 53 | 54 | return 0 55 | 56 | 57 | if __name__ == '__main__': 58 | raise SystemExit(main()) 59 | -------------------------------------------------------------------------------- /day02/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day02/__init__.py -------------------------------------------------------------------------------- /day02/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def _is_safe(nums: list[int]) -> bool: 14 | if nums[1] < nums[0]: 15 | direction = -1 16 | else: 17 | direction = 1 18 | 19 | for n1, n2 in zip(nums, nums[1:]): 20 | diff = direction * (n2 - n1) 21 | if not (1 <= diff <= 3): 22 | return False 23 | else: 24 | return True 25 | 26 | 27 | def compute(s: str) -> int: 28 | return sum( 29 | _is_safe(support.parse_numbers_split(line)) 30 | for line in s.splitlines() 31 | ) 32 | 33 | 34 | INPUT_S = '''\ 35 | 7 6 4 2 1 36 | 1 2 7 8 9 37 | 9 7 6 2 1 38 | 1 3 2 4 5 39 | 8 6 4 4 1 40 | 1 3 6 7 9 41 | ''' 42 | EXPECTED = 2 43 | 44 | 45 | @pytest.mark.parametrize( 46 | ('input_s', 'expected'), 47 | ( 48 | (INPUT_S, EXPECTED), 49 | ), 50 | ) 51 | def test(input_s: str, expected: int) -> None: 52 | assert compute(input_s) == expected 53 | 54 | 55 | def main() -> int: 56 | parser = argparse.ArgumentParser() 57 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 58 | args = parser.parse_args() 59 | 60 | with open(args.data_file) as f, support.timing(): 61 | print(compute(f.read())) 62 | 63 | return 0 64 | 65 | 66 | if __name__ == '__main__': 67 | raise SystemExit(main()) 68 | -------------------------------------------------------------------------------- /day02/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def _is_safe(nums: list[int]) -> bool: 14 | if nums[1] < nums[0]: 15 | direction = -1 16 | else: 17 | direction = 1 18 | 19 | for n1, n2 in zip(nums, nums[1:]): 20 | diff = direction * (n2 - n1) 21 | if not (1 <= diff <= 3): 22 | return False 23 | else: 24 | return True 25 | 26 | 27 | def compute(s: str) -> int: 28 | total = 0 29 | for line in s.splitlines(): 30 | nums = support.parse_numbers_split(line) 31 | if _is_safe(nums): 32 | total += 1 33 | else: 34 | for i in range(len(nums)): 35 | newnums = nums[:i] + nums[i + 1:] 36 | if _is_safe(newnums): 37 | total += 1 38 | break 39 | 40 | return total 41 | 42 | 43 | INPUT_S = '''\ 44 | 7 6 4 2 1 45 | 1 2 7 8 9 46 | 9 7 6 2 1 47 | 1 3 2 4 5 48 | 8 6 4 4 1 49 | 1 3 6 7 9 50 | ''' 51 | EXPECTED = 4 52 | 53 | 54 | @pytest.mark.parametrize( 55 | ('input_s', 'expected'), 56 | ( 57 | (INPUT_S, EXPECTED), 58 | ), 59 | ) 60 | def test(input_s: str, expected: int) -> None: 61 | assert compute(input_s) == expected 62 | 63 | 64 | def main() -> int: 65 | parser = argparse.ArgumentParser() 66 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 67 | args = parser.parse_args() 68 | 69 | with open(args.data_file) as f, support.timing(): 70 | print(compute(f.read())) 71 | 72 | return 0 73 | 74 | 75 | if __name__ == '__main__': 76 | raise SystemExit(main()) 77 | -------------------------------------------------------------------------------- /day03/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day03/__init__.py -------------------------------------------------------------------------------- /day03/input.txt: -------------------------------------------------------------------------------- 1 | ,what(936,615)*:who()[[[~:mul(364,505)~;&{-*mul(431,254)) select(){}#*+]mul(617,948)$mul(117,664){) &why()<,why()mul(271,823)what(674,989);/~{'+[mul(311,405),-!mul(651,968)$?;[from()+ {mul(595,193)*}]what()^mul(250,791)!mul(114,297))]$from()from(573,794)how()why()how()mul(130,657)how()select(){what()mul(676,119)>{~@{%why(105,423)mul(307,665)&mul(757,115)/'*{};:mul(800,484),[:(,+why()~mul(679,186)]#where()'~where() do()+mul(699,53))/>}mul(332,602)@)}})mul(393,425)?(why(265,628)where()how()when()mul(598,633)]>? mul(200,581)@+[$who()mul(627,543):%(<^?mul(884,432)mul(896,310)mul(49,151)#^where()^>(+-@mul(732,887)from()where()%)?@);mul(176,994):from()*;&,/?mul(436,323) what()select()->/}-?:mul(312,150):<+mul(156,680)#;who()mul(768,550)>how()>what()#why()$don't()mul(713,94)@why(26,903):}where())+mul(173,537)mul(999,212)who(447,54)how(283,784)[{)where()/how() mul(966,107)select()-mul(246,450)'&mul(669,517)where(){mul(833,144)>/ ]]}( ~select()mul(890,267)mul(255,376):)'^@^*/do(){ , :{-mul(504,930):[when()^mul(558,410)}~+why():--#:(do()&]{where()#when()}#mul(407,999)%from(250,471)don't()>'@mul(750,449)what()mul(407,714)~from()how()mul(305,743),;who()-/[ }-mul(424,849)}]from()who()how(104,652)-<<:mul(423,458)@@)%~select()/mul(860,398)%(mul(943,958))$< /#+&[,mul(412,371)when(940,34)mul(482,638)when()(from(972,898)select()!mul(831,126)%don't()!mul(157,741)/;-}&+where()mul(307,860)<'(,why()mul(649,820);mul(275,560)}'+how()do()?,]why(49,835)#mul(936,375)where()mul(60,815)?&$+,,?who()mul(241,128)>]'?();-don't()*} ,who()mul(797,221)<]-$~mul(876,802)$what()%**mul(987,371)^ mul(918,15)'mul(323,693):why()@;who()([select()from()mul(408,140)#&'#who()why())^:}mul(850,606)^mul(661,471)who(),where(),when()[why();where()mul(439,437)*when(),:# mul??}from()-mul(151,663)$select(),~}when()select(303,600),select()mul(954,887),#mul(204,473)+~~:who():how()?what()when()mul(295,834)@{@!mul(321,586)>what()mul(848,598):~+from() mul(79,54)&*from()when()&]mul(856,657)@!!?}>mul(186,780)mul(280,999)how()+*don't()when()when()}}&how()$mul(161,922)mul(296,159)>'mul(664,400)* #mul(818>>;mul(589,575)^why()mul(598,449)-^+from()*mul(168,826)##where();mul(68,484)*[-}mul(767,665)%where()+mul(323,94)#~!who()what()mul(937,993) !%#+')$mul(920,949)?]: $!mul(606,26),what()*-how()where()do():+<-,mul(854,484)]$'(~select(229,111) '#when(793,632)mul(732,170)-mulwhy())who(759,347)%$,;who()when()%mul(957,812)~when():(select()> (&)mul(573,961)what()what()#!)(where()[mul(99,37):why()(mul(297,734!-+mul(333,746)#?where():mul(60,228)#{)!who()%mul(89,999)])(>?why();^mul(359,707)*;select()mul(116,922)when():%[/who()*mul(320,991) 2 | *+mul(493,591)-/}$mul(91,783)what()*%#^mul(806,374)where():[mul(958,335)-&&!from()$''mul(281who()*when()?%what(580,440)][+what()mul(509,540)+select()when()select(56,885)why(376,584);'mul(217,966)when()<@^+$,why()[mul(247,428)%,select()~mul(138,420)--mul(476,605){?mul(468,911)>!do()mul(255,269){who()%select()who()select()mul(36,834)!when(562,637)-@?select(933,67)how()[mul(739,400):what()]}*(mul(694,323)!^]mul(454,448)what(){>@;mul(272,610)mul(738,477);~ )from()$mul(876,457)); ;/+][from()))when()#mul(483,391)'}where()'when(609,88)from(),@why();mul(153,986)%mul(591,201)mul(738,458)select()<-<#mul(773 -?what()select()[$when()where()mul(5,483)<%[mul(499,582)~mul(163,315)what()select()mul(402,163}mul(948,693)select()mul(831,988)'}(~who()mul(446,802)how()mul(774,486)&%what()select();),^~>mul(755,126))where()mul(961,642)mul]]~when()from():'mul(858,593)why()><)mul(928,842)-)[mul(263,295)(@what()};, @mul(105,256)-#/>what()%mul(712,214)select()(from()mul(502,844)][(why()+(mul(321,154){what()[+why()mul(823,327),[when()#mul(340,443){'mul(96,564)when()what()/mul(851,26)mul(207,207)$where()##~?-why()^mul(675,26)<:%from()*!mul(327,650) mul(259,233)[(!mul(988,958)*,~~mul(251,348)what()+mul(914,701)/from()})<(^mul(787,646)?-?*what(579,540)select()mul(651,39)&#[+}*'^{mul(341,73):mul(623,630)mul(84,356)(mul(323,42)why()}why(122,686)^$what()~';mul/do()#)select()+#where()mul(23,522)%)])mul(184,346from()]select()?mul(83,389)+how(),why()who()mul(142,536);:mul(928,118)why()mul(703,997))/where()why()%%-(,mul(65,386)#select()from()+^what()where();$mul(674,585)/%'/why()[/):#mul(971,693)#,/mul(335,902)select()>,+mul(191,675) mul(522,174){(who()mul(83:&mul(484,524):](*what()!^mul(178,617)**?@do()<]!^#)@]when()^mul(727,829)+)~+,how()^do()%[what()+]mul(839,249)@^mul(104,433)'* ;}@[^mul(692,606)select())(^from()*:(mul(24,900)&(select()+when()mul(812,794)<;mul(925,341)>mul(365,784)*?^?~''~mul(275,690)who()select()[:($mul(447,489))^()*)%what()how()why(468,537)mul(77,201)();#^,>'mul(733,315)mul(850,854)@$why()(when()mul(421,536)what()'-^+>:why()mul(722,332)&from()#[*&mul(746,186)#*; /where()select()mul(376,137)'(]/+#from()mulwhat():;;~mul(229,429)%;^{ from(),@why()mul(169,569)/from()when()+<^where()mul(73,254),where()mul(84,140)}]mul(976,738)%>mul(350,459)#]'{mul(591,862) 3 | [!(@{mul(366,513)mul(878,543)how()}mul(922,30)%select()?mul(767,176)select()mul]@[+)mul(815,681) :when();mul(87,245)*@?;why():&;<#mul(535,470){, @where()%how()mul(830who(575,955)what()#where(919,307)why()!*]~mul(233,814))<'>#?what()mul(127,180)+,where()>!::*mul(579,498*{why()mul(418,393)~#:,;mul(264,329))?~mul(743,344)':,)mul(727,235),!where()->what()^mul(236,130):mul(473,181)]select()&/-why()from()mul(121,297)?{;#[{'mul(416,389~from()-;][mul(31,555))#(>why(974,532)/)who()'~mul(486,593)'select()what()when()mul(365,633)/select()what()'@select()what(),mul(724,381)/+mul(166,268)!when(858,667)/)what()mul(133,803)+don't()when();how()mul(463,838)select(441,978)$?#{mul(317,193)why()#-@mul(410,395) /^?why())mul(504,778)(:how()&# @)$[mul(387,981))when(566,719)/who(250,360)who(137,588)mul(324,685)mul(604,345)+:select()mul(769,189)+what())mul(544,481) who()){*when()how()[)mul(453,120)mul(270,73)~:[#?what()do()'mul(412,833)#(who()>[@when() mul(133,896)(<(%from()select()'select()mul(416,503)(*}how(930,729):mul(198,432)};~,why()#+mul(925,673);how()mul(433,922<}]{(*&+%mul(872,81)^@[][)?mul(532,353)^*how()mul(883,756)do()'@mul(363,561)why(938,173)),{do()mul(940,613)#!#!%>where()})mul(582,742)#who()}]'from(628,90)select()~mul(230,336)when()%what()how()*how()'mul(390,297)mul(258,984)$,mul(67,720)#what()mul(718,797what()where()*~?(how()>]}?mul(490,457)&*]?:?mul(978,639)what()how()-'!who(),}:don't()>*mul(604,711)^-where()&] mul(879,235)where()mul(179,65)(?;)how()who()from()don't()why()how()who()!(from()]select(180,350)mul(23,319)#!(mul(789,115)from()*,($mul(231,932))#when();from()[mul(619,377)(where()don't()where(998,613);*mul(892,277)who()'who()who()mul(668,73)'where() >^(why()mul(634,416)@+[~~mul(353,482)<<[,#~-select()do()mul(802,411)how():~{[mul(364,145)^ +@)$<+mul(738,946)}why()?}+)*^+$mul(198,712)/~!mul(740,158)^<^/!mul(904,309)%]how()who();mul(884,292)who()*^+mul(683,391)/)*)-+]mul(226,37)#select()why():-when())mul(194,461)+why()+how()mul(383,917>[-(why():#/when()mul(885,707))>select()]@*do()*{#*mul(166,956)select()@-when()>%mul(692,209)+<~~what()$(mul(828,644)&why(799,466)$why()-!mul(691,598)who(868,775)]who()~~when()'mul(759,166)}what()%]](}&mul(144,589)}>mul(211,314)' why()mul(471,19)+mul(378,424)%who()~@&how()^what()~^mul(980,381)mul(538,166)@who()how()do()who(459,112)how()why()select()$@when()when()<[mul(434,460)from() *(mul(575,162)$+^why()[mul(528,793)^/when()mul(406,843),#when();mul(753,396)/+&&@]do()%what()@what()!when()how()@what()~mul(322,908)where()']-;'mul(759,320)&~/$who()[+mul(86,675)^]+how()&mul(91,996)+^mul(262,908)/why()mul(250,843)+ why()how(){)]&'#!/usr/bin/perlmul(629,107) 4 | ?+when()-mul(895*,!$;[~?/don't()';#mul(813,596){;mul(443,525)mul(568,373)mul(562,661)select()'select()#~'from(541,223) :why()mul]$%mul(235,124)where()-:from()<*@who()-?mul(268,919)mul(460,967) ~mul(71,173)(who(17,54)#where()select()~how()mul(502,446)@how():(mul(724,103)*mul ~how()how()~'%>}what()select()mul(206,702)why()%?~mul(152,874)when()where()who()<#}(^<[mul(784,415)when()#);{>mul(648,341)mul(842,838)*;from()when()'mul(334,67) /#mul(964,285)]:how()!mul(985,585)!mul(120,507)mul(679,201)what()when():@/+select()]mul(634,373)what(),'from(471,181)%%)mul(899,586)>%-{*?}]when()mul(943,417){from()^/,/why()mul(304,754)mul(813,371)&>,mul(447,797)&#$)>>{{+from()when()('}mul(166,556)what()mul(67,233)how(){mul(736,874)$,-mul(314,249)why() -how()$,where()select()mul(224when(379,562)#^+from()/-mul(755,62);-*mul(250,569)+who(307,838)+-from()mul(637,410)mul}:$$$mul(868,919)!when(){^&from()mul(587,834),mul(41,503),/!/]):[mul(266,118)<{<(&do()! <>from()mul;%what()'$>what()[mul(709,366):%why()(?#]+?mul(955,288)who()#what()mul(990,858)$% when()&*mul(409,113)/ from(),&-(*mul(456,740)do()>who()what()why()%what()[mul(810,186)mul(263,285)$'from()*:select()where()who())do()-%(why()@&mul(705,640);>select()why(61,263) {where()mul(742,869)when()mul(651,369){ why()why()how()'@mul(661,360)where()(~)mul(432,713)>/who()how()mul(86,348)select())mul(747,783)why()//>?, ^~@mul(158,203)][({select()^what()select()mul(229,945)]%~from()*<}mul(968,325)what()&do())~~how()mul(101,882)^what()<;where()>from()select(184,173)}mul(513,563)what()()&*where()$mul(442,237))mul(39,572)^*mul(753,637);mul(46,122):select()-how(974,73)/ (mul(827,817)]?where()$]from()what()^)mul(559,203)?<,where()(^>/!%mul(823,69)select()~;%~*how()!$mul(291,838)[mul(888,306)~#:$ }#^~why(270,533)mul(463,460)$@don't()mul(975,594)select()~@;who(973,38)when()'mul(665,454)mul(570,640)/([who()@who()?why()mul(97,557);([[mul(291,602)what(590,172)$}++';/mul(831,385){mul(299,25)mul(402,592)):what()]mul(49,44)/ &?~~,@$$)^%mul(21,890)}when()]+mul(696,311)<&when()when()/who()%^&mul(967,563)*@why(38,563){%{(mul(539,838)''&%)!mul(140,574)@mul(872,800)$$*[};?(mul(589,800)'[[<))'what()mul(571,216)+{!$!mul(327,56)}>@mul(179]{mul(728,336))who()*}/#from(385,957)mul(528,971)how(607,239)who()when()>mul(699,576),[where()-?;,mul(953,122why()from())-mul(50,327))&]select()*mul(191$<*}+*;%?~mul(717,832);!,-;#mul(877,863)})mul(10,865)how()>~mul(471,191)when()+;-,:#+>{mul(98,339) ,%%who()select()mul(590from()^,from(943,773)/%/(mul(498,29)+[-when()mul(556,808)$,{why()'who()what()}select()$mul(408,17)}@^}%what()when(533,550)mul(119,401)<&?select()!!],mul(800,82)~'++'-^'mul(202,735)'-;how()(why()-mul(878,380)++:,'%>select()why(),mul(517,67):(,mul(57,468)]!!?^who()'who()how()(mul(362,246){[select()@-,mul(790,848)who()/how()&;~mul(896,459)mul(244,758)^do()]*mul(393,498)how(827,483)++[;$^mul(242,357)?mul(246,74)mul(243,685)$-*how(); @;${when()[mul(507,599)'(>mul(814,895)mul(334,391)):/don't()$~,'{)#&+@mul(346}~:{,from()/[mul(545,281))[!&**/,what()mul(505,164)mul(992,413)from()why()from()#}&#!%mul(570,812)/&:&%~*who()who(),>%'#mul(849,141)mul(808,151)>'}~!>select()[:when()mul(547,749)/&+}how();when()$[:don't()from()&@&~mul(48,835)%:&){(who()<'mul(939,418):where()what()%mul(281,802)],who();^^#[mul(243,694)^[ mul(155,458)where(933,846)select()/)$>~%!$mul(412,437)mul(760,807)when()who()! what()<)who()mul(835,695)]&>when()#^mul(605,495) *)',:^*'mulwhat()what()}(,},/mul(268,328)]&mul(311,488)from(246,449):-!mul(728,913)who()]!why()*-why()({&mul(147,343)mul(87,236)from(233,81)#how()from()mul(72,161)!when()-/))mul(649,993)how()}where()where()~}do()<(mul(909,297)/?++mul(458,304)-mul(189,748):['!^%)mul(472,150)}#-mul(813,811)-who()mul(999,939)mul(748,494)why()<$$;!@{select()mul(679,545)who()!+%;mul(539,710)#}{#{do(){where()('(>*what(161,284)*]mul(822,730){)who()/mul(834,405)@-when()[why()^?#mul(651,356)don't()select()~ @+[mul(647,152)who()from(){mul(313,955)from()}{}#+why(),from(),mul(322,157)'+{^*mul(616,129)@%+where(),don't()%who()why()*#![@mul(765,206)%from()>when()where()@>mul!(mul(613,820)!(why()[[mul(195,689)select()mul(287,64}(mul(227,843){}@/^mul(755,318)mul(545,146) )select()'&mul(157,240)-!]}mul(19,327,~mul(784,50)&?mul(852,312):what(313,343)~@:where()!&,mul(255,419)when()*%do()+#'>:what(),$)mul(677,837)+)}* from()where()select()who()what()mul(997,527)#^/how()mul(213,791#}('why()^ }&mul(919,990)+%&$(mul(215,630/')&~^where()>{~mul(654,677)why()/how()when()select()mul(852,638)mul(71,501)where())/+:mul(141,473)%)why();mul(504,165)select()#,( ~mul(30,152)^where(),what()$from()>&)mul(352,484)who()?[)*]+$:@don't()~})$$(mul(795,493)*mul(523,934)-$#when()where()mul(628,695)+[mul(350,307)!-#<(don't();-+/mul(454(+mul(351,814)when()&when();%;:mul> ?/*+(['&mul(588,769)what(893,970)+ }mul(192,561)from()mul(949,495)<%%@/how()~%where()mul(240,75)mul(498,644)what(443,293)select()@'[mul(339,505))~@$who(55,600)&-mul(802,658); select(),mul(411,736)#::[mul(535,739)#}where()mul(628,133)what(){[]what()do()mul(378,833)}^[+who()when()mul(272,918)]mul(142,109){?:^<)mul(385,751){select()mul(888,606)who()who())/%;'when()mul(21,135)&'*,+): ;mul(185,209)mul(827,134)/~why()when()*@mul(878,159)#mul(621,651)mul(366,275)#!?~!>{mul(695,683);mul(625,943)-{who()/mul(358,71)#~'+why(845,349)[mul(599,523)@^mul(492,279)<)$why(),from()select()from()+/mul(763,890)^who(416,926)-@@+?~who()mul(220,104) +/<-(,(do()+when();>~mul(4,604)what()from()don't():/%who()$mul(263,144)mul(524,547)mul(751,211)^when();:;+when()mul(79,232)mul(918,973)how(449,332)@who()){from():@mul(235,405)$[select()})&when()do()*how()-&mul(679,136)>~mul(374,747)}from()mul(835,437)'when();^';/mul(197,989)^when()?+;/+mul(344,942)*%where()!from()%(mul(798,73)who(){#who()*select(628,265)$]do()>^${>why()-mul(430,277)]select():!%mul(678,453)~how()%how()/*mul(832,734)*who():/?who(156,121)/why()do()<#<$@,why()@mul(945,879)&^]&?#]where()( don't();)~*'!<+mul(286,848)~(^^mul(773,10)mul(66,770)#from()(~!::why()select()*mul(261,873)why(279,409);]}+mul(122,222)[how()+why()when()why()what()^,mul(531,742)+}}!how()^mul(937,106)&#how()~^:>-(mul(482,447)(mul(282,573)}?)mul(123,394)mul(918,324),@^+{{*@do()mul(288,561)/what()[# %~mul(426,213);*do())!+@where()&)mul(117,442)$~ $?~:why()[:don't() from()-from()>mul(818,513)how(800,358)where(),#+[+mul(463,762)mul(400,662)]#mul(689,771)select()when()>why(),*where()mul(933,332)mul(976,816))~from()%{mul(310,407)(:!(/-@!*$mul(536,576)? [!when()!^ mul(862,302)]select(336,393)@select()mul(296,939)mul(745,689)mul(162,820)((@when()when(115,188) 7 | -------------------------------------------------------------------------------- /day03/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | PAT = re.compile(r'mul\((\d+),(\d+)\)') 14 | 15 | 16 | def compute(s: str) -> int: 17 | return sum(int(p1) * int(p2) for p1, p2 in PAT.findall(s)) 18 | 19 | 20 | INPUT_S = '''\ 21 | xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5)) 22 | ''' 23 | EXPECTED = 161 24 | 25 | 26 | @pytest.mark.parametrize( 27 | ('input_s', 'expected'), 28 | ( 29 | (INPUT_S, EXPECTED), 30 | ), 31 | ) 32 | def test(input_s: str, expected: int) -> None: 33 | assert compute(input_s) == expected 34 | 35 | 36 | def main() -> int: 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 39 | args = parser.parse_args() 40 | 41 | with open(args.data_file) as f, support.timing(): 42 | print(compute(f.read())) 43 | 44 | return 0 45 | 46 | 47 | if __name__ == '__main__': 48 | raise SystemExit(main()) 49 | -------------------------------------------------------------------------------- /day03/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | PAT = re.compile(r"(?:(do|don't)\(\)|mul\((\d+),(\d+)\))") 14 | 15 | 16 | def compute(s: str) -> int: 17 | total = 0 18 | enabled = True 19 | for parts in PAT.findall(s): 20 | match parts: 21 | case 'do', '', '': 22 | enabled = True 23 | case "don't", '', '': 24 | enabled = False 25 | case '', p1, p2: 26 | if enabled: 27 | total += int(p1) * int(p2) 28 | case unreachable: 29 | raise AssertionError(unreachable) 30 | return total 31 | 32 | 33 | INPUT_S = '''\ 34 | xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) 35 | ''' 36 | EXPECTED = 48 37 | 38 | 39 | @pytest.mark.parametrize( 40 | ('input_s', 'expected'), 41 | ( 42 | (INPUT_S, EXPECTED), 43 | ), 44 | ) 45 | def test(input_s: str, expected: int) -> None: 46 | assert compute(input_s) == expected 47 | 48 | 49 | def main() -> int: 50 | parser = argparse.ArgumentParser() 51 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 52 | args = parser.parse_args() 53 | 54 | with open(args.data_file) as f, support.timing(): 55 | print(compute(f.read())) 56 | 57 | return 0 58 | 59 | 60 | if __name__ == '__main__': 61 | raise SystemExit(main()) 62 | -------------------------------------------------------------------------------- /day04/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day04/__init__.py -------------------------------------------------------------------------------- /day04/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | total = 0 15 | lines = s.splitlines() 16 | 17 | def _check(x: int, y: int, dx: int, dy: int) -> bool: 18 | for i, c in enumerate('XMAS'): 19 | cx, cy = x + i * dx, y + i * dy 20 | if ( 21 | 0 <= cx < len(lines[0]) and 22 | 0 <= cy < len(lines) and 23 | lines[cy][cx] == c 24 | ): 25 | continue 26 | else: 27 | return False 28 | else: 29 | return True 30 | 31 | for y, line in enumerate(lines): 32 | for x, c in enumerate(line): 33 | if c == 'X': 34 | for dx, dy in support.adjacent_8(0, 0): 35 | total += _check(x, y, dx, dy) 36 | return total 37 | 38 | 39 | INPUT_S = '''\ 40 | MMMSXXMASM 41 | MSAMXMSMSA 42 | AMXSXMAAMM 43 | MSAMASMSMX 44 | XMASAMXAMM 45 | XXAMMXXAMA 46 | SMSMSASXSS 47 | SAXAMASAAA 48 | MAMMMXMMMM 49 | MXMXAXMASX 50 | ''' 51 | EXPECTED = 18 52 | 53 | 54 | @pytest.mark.parametrize( 55 | ('input_s', 'expected'), 56 | ( 57 | (INPUT_S, EXPECTED), 58 | ), 59 | ) 60 | def test(input_s: str, expected: int) -> None: 61 | assert compute(input_s) == expected 62 | 63 | 64 | def main() -> int: 65 | parser = argparse.ArgumentParser() 66 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 67 | args = parser.parse_args() 68 | 69 | with open(args.data_file) as f, support.timing(): 70 | print(compute(f.read())) 71 | 72 | return 0 73 | 74 | 75 | if __name__ == '__main__': 76 | raise SystemExit(main()) 77 | -------------------------------------------------------------------------------- /day04/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | total = 0 15 | lines = s.splitlines() 16 | 17 | def _check(x: int, y: int) -> bool: 18 | b_slash = {lines[y - 1][x - 1], lines[y + 1][x + 1]} 19 | f_slash = {lines[y + 1][x - 1], lines[y - 1][x + 1]} 20 | return b_slash == f_slash == {'M', 'S'} 21 | 22 | for y, line in enumerate(lines[1:-1], 1): 23 | for x, c in enumerate(line[1:-1], 1): 24 | if c == 'A': 25 | total += _check(x, y) 26 | return total 27 | 28 | 29 | INPUT_S = '''\ 30 | MMMSXXMASM 31 | MSAMXMSMSA 32 | AMXSXMAAMM 33 | MSAMASMSMX 34 | XMASAMXAMM 35 | XXAMMXXAMA 36 | SMSMSASXSS 37 | SAXAMASAAA 38 | MAMMMXMMMM 39 | MXMXAXMASX 40 | ''' 41 | EXPECTED = 9 42 | 43 | 44 | @pytest.mark.parametrize( 45 | ('input_s', 'expected'), 46 | ( 47 | (INPUT_S, EXPECTED), 48 | ), 49 | ) 50 | def test(input_s: str, expected: int) -> None: 51 | assert compute(input_s) == expected 52 | 53 | 54 | def main() -> int: 55 | parser = argparse.ArgumentParser() 56 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 57 | args = parser.parse_args() 58 | 59 | with open(args.data_file) as f, support.timing(): 60 | print(compute(f.read())) 61 | 62 | return 0 63 | 64 | 65 | if __name__ == '__main__': 66 | raise SystemExit(main()) 67 | -------------------------------------------------------------------------------- /day05/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day05/__init__.py -------------------------------------------------------------------------------- /day05/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | sort_rules_s, rest = s.split('\n\n') 16 | sort_rules = frozenset( 17 | tuple(int(n) for n in line.split('|')) 18 | for line in sort_rules_s.splitlines() 19 | ) 20 | 21 | @functools.cmp_to_key 22 | def key_func(o1: int, o2: int) -> int: 23 | if o1 == o2: 24 | return 0 25 | elif (o1, o2) in sort_rules: 26 | return -1 27 | else: 28 | return 1 29 | 30 | total = 0 31 | for line in rest.splitlines(): 32 | nums = support.parse_numbers_comma(line) 33 | newnums = sorted(nums, key=key_func) 34 | if nums == newnums: 35 | total += nums[len(nums) // 2] 36 | return total 37 | 38 | 39 | INPUT_S = '''\ 40 | 47|53 41 | 97|13 42 | 97|61 43 | 97|47 44 | 75|29 45 | 61|13 46 | 75|53 47 | 29|13 48 | 97|29 49 | 53|29 50 | 61|53 51 | 97|53 52 | 61|29 53 | 47|13 54 | 75|47 55 | 97|75 56 | 47|61 57 | 75|61 58 | 47|29 59 | 75|13 60 | 53|13 61 | 62 | 75,47,61,53,29 63 | 97,61,53,29,13 64 | 75,29,13 65 | 75,97,47,61,53 66 | 61,13,29 67 | 97,13,75,29,47 68 | ''' 69 | EXPECTED = 143 70 | 71 | 72 | @pytest.mark.parametrize( 73 | ('input_s', 'expected'), 74 | ( 75 | (INPUT_S, EXPECTED), 76 | ), 77 | ) 78 | def test(input_s: str, expected: int) -> None: 79 | assert compute(input_s) == expected 80 | 81 | 82 | def main() -> int: 83 | parser = argparse.ArgumentParser() 84 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 85 | args = parser.parse_args() 86 | 87 | with open(args.data_file) as f, support.timing(): 88 | print(compute(f.read())) 89 | 90 | return 0 91 | 92 | 93 | if __name__ == '__main__': 94 | raise SystemExit(main()) 95 | -------------------------------------------------------------------------------- /day05/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | sort_rules_s, rest = s.split('\n\n') 16 | sort_rules = frozenset( 17 | tuple(int(n) for n in line.split('|')) 18 | for line in sort_rules_s.splitlines() 19 | ) 20 | 21 | @functools.cmp_to_key 22 | def key_func(o1: int, o2: int) -> int: 23 | if o1 == o2: 24 | return 0 25 | elif (o1, o2) in sort_rules: 26 | return -1 27 | else: 28 | return 1 29 | 30 | total = 0 31 | for line in rest.splitlines(): 32 | nums = support.parse_numbers_comma(line) 33 | newnums = sorted(nums, key=key_func) 34 | if nums != newnums: 35 | total += newnums[len(newnums) // 2] 36 | return total 37 | 38 | 39 | INPUT_S = '''\ 40 | 47|53 41 | 97|13 42 | 97|61 43 | 97|47 44 | 75|29 45 | 61|13 46 | 75|53 47 | 29|13 48 | 97|29 49 | 53|29 50 | 61|53 51 | 97|53 52 | 61|29 53 | 47|13 54 | 75|47 55 | 97|75 56 | 47|61 57 | 75|61 58 | 47|29 59 | 75|13 60 | 53|13 61 | 62 | 75,47,61,53,29 63 | 97,61,53,29,13 64 | 75,29,13 65 | 75,97,47,61,53 66 | 61,13,29 67 | 97,13,75,29,47 68 | ''' 69 | EXPECTED = 123 70 | 71 | 72 | @pytest.mark.parametrize( 73 | ('input_s', 'expected'), 74 | ( 75 | (INPUT_S, EXPECTED), 76 | ), 77 | ) 78 | def test(input_s: str, expected: int) -> None: 79 | assert compute(input_s) == expected 80 | 81 | 82 | def main() -> int: 83 | parser = argparse.ArgumentParser() 84 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 85 | args = parser.parse_args() 86 | 87 | with open(args.data_file) as f, support.timing(): 88 | print(compute(f.read())) 89 | 90 | return 0 91 | 92 | 93 | if __name__ == '__main__': 94 | raise SystemExit(main()) 95 | -------------------------------------------------------------------------------- /day06/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day06/__init__.py -------------------------------------------------------------------------------- /day06/input.txt: -------------------------------------------------------------------------------- 1 | ................#......#...........#...........................#......#...........#........................................#...... 2 | .......................................................................................#........#............#.............#.....# 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 | ..#..............................................#.........................#.............#...#.#.................................. 31 | ................#.............................#....................................#.....#...................#..#.....#........... 32 | #............................#...#........................#................#....#.............................................#... 33 | ...............#.............#........#...................................................................#.......#....#..#....... 34 | ...........#......#.....#........................#.#..........................................#............#...................... 35 | .#.....................................................#...............#.........................#................#...........#... 36 | .........................................#.............#......#.#....................................................#.........#.. 37 | ................................#...#......................................................#..........................#..........# 38 | .#.........................#..............#.#..................................................................................... 39 | .........#..........#.............##.........#..#.................#........................#.....#.##....................#........ 40 | ...................................................................................#...........................................#.. 41 | ................#..........................................#...#...#.................................................#.........#.. 42 | ..............................................#..............#..................#......#...#....#..#.............................. 43 | ......#......#.................#.............#..................#...............#.............#..................#...............# 44 | .................................#.......#......#.......#.............................................................#........#.. 45 | ..................#......#...................................#.................#.......#.......................................... 46 | ....................#.........#.........................#..........#..........#..................#................................ 47 | ..........#...........#...............#.#..................................................................................#...... 48 | ..................#..#....................................................#..........#......#................................#.... 49 | ............#.................................................................................................#........#.......... 50 | .........#...............................................#.........##...........................#................................. 51 | ...#.........#........................................................#....................................#...................... 52 | ........#......................#......#............#........#...............#.......................#............................. 53 | .......#..............................................................................................#....................#...... 54 | .#.#..........#.......#.............##......................#..........................................................#.......... 55 | ............#.#...........#............................................................#.......................................... 56 | ............#..........................................................................#....................#...............#..... 57 | .......................#.....#...#........................................#.......#.........#.........................#........... 58 | ....................#..............#......#....................................................................................... 59 | ......................#....................................................................#...............#...#............#..... 60 | ........#..........#....................#...#........#......#..........................#....#..................#..#............... 61 | ................#...........................................................................................................#..... 62 | .#...............#..........................................................................................#......#..#.#......... 63 | ..............#................#...............#.........#.........#................................#.#........................... 64 | ...#................#..............#........................#...#.......................................#.#....................... 65 | #.........................................#..#....................................................................#............... 66 | .............#...........#.......#.#.....................................................................#.....#.................. 67 | .............#..............................................................................#..................................... 68 | ........................#........#....#........#.#.............................................^..........#....................... 69 | ...............................#.......................................................#.............##.......#.......#........... 70 | #.#..............#..........................................#.......#..........................................#.................. 71 | ...##...............................#........#.............##..................................................#.................. 72 | ..#..#.................................................................#............#...#...#..................................... 73 | ....................#.#...#............#.......#.............#.......#..............#............................................. 74 | ..#..#.........#.............................................................................#......................#...#......... 75 | ....................................................#.........................#..#...#.#.#......#...............................#. 76 | ......#.....#.....................................................................#..............................................# 77 | ....................#........#..........................................................#.......#........................#........ 78 | ................#.........................#...........................#...........................................#..............# 79 | ..#.......#..................................................#..................................#................................. 80 | ..........#.....................#...#.#...........#....................................#.......................................... 81 | .....................................#...........................................................#..........#...................#. 82 | ....#............................#.........................................#.........................#............................ 83 | ......#............#...........#......#.#..................................#...................................................... 84 | ..................#.......#.....#......#.................................................................#................#....... 85 | ...........#.....#........................................................#.............#......................................... 86 | .................................#...........................#........#.........#............#.............................#.....# 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 | ....................#.................#..........#..............#.......................#...................................#..... 128 | ............#..............................#.............................#......#......#......................#...#............... 129 | .#..........#...............#..................................................................#....#............................. 130 | ....#............................................#......#..................#.....#............#.........................#......... 131 | -------------------------------------------------------------------------------- /day06/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | world = support.parse_coords_hash(s) 15 | bx, by = support.bounds(world) 16 | 17 | (x, y), = ( 18 | (x, y) 19 | for y, line in enumerate(s.splitlines()) 20 | for x, c in enumerate(line) if c == '^' 21 | ) 22 | 23 | seen = {(x, y)} 24 | direction = support.Direction4.UP 25 | while True: 26 | if x not in bx.range or y not in by.range: 27 | break 28 | elif (x, y) in world: 29 | x, y = direction.opposite.apply(x, y) 30 | direction = direction.cw 31 | x, y = direction.apply(x, y) 32 | else: 33 | seen.add((x, y)) 34 | x, y = direction.apply(x, y) 35 | 36 | return len(seen) 37 | 38 | 39 | INPUT_S = '''\ 40 | ....#..... 41 | .........# 42 | .......... 43 | ..#....... 44 | .......#.. 45 | .......... 46 | .#..^..... 47 | ........#. 48 | #......... 49 | ......#... 50 | ''' 51 | EXPECTED = 41 52 | 53 | 54 | @pytest.mark.parametrize( 55 | ('input_s', 'expected'), 56 | ( 57 | (INPUT_S, EXPECTED), 58 | ), 59 | ) 60 | def test(input_s: str, expected: int) -> None: 61 | assert compute(input_s) == expected 62 | 63 | 64 | def main() -> int: 65 | parser = argparse.ArgumentParser() 66 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 67 | args = parser.parse_args() 68 | 69 | with open(args.data_file) as f, support.timing(): 70 | print(compute(f.read())) 71 | 72 | return 0 73 | 74 | 75 | if __name__ == '__main__': 76 | raise SystemExit(main()) 77 | -------------------------------------------------------------------------------- /day06/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | world = support.parse_coords_hash(s) 15 | bounds = support.bounds(world) 16 | 17 | pos, = support.parse_coords_hash(s, wall='^') 18 | 19 | seen = set() 20 | seen1 = set() 21 | direction = support.Direction4.UP 22 | 23 | def _loops( 24 | pos: tuple[int, int], 25 | direction: support.Direction4, 26 | new_wall: tuple[int, int], 27 | ) -> bool: 28 | seen2 = set() 29 | direction = direction.cw 30 | while True: 31 | if not support.in_bounds(pos, bounds): 32 | return False 33 | elif pos in world or pos == new_wall: 34 | pos = direction.opposite.apply(*pos) 35 | direction = direction.cw 36 | elif (pos, direction) in seen1 or (pos, direction) in seen2: 37 | return True 38 | else: 39 | seen2.add((pos, direction)) 40 | pos = direction.apply(*pos) 41 | 42 | found = set() 43 | while True: 44 | if not support.in_bounds(pos, bounds): 45 | break 46 | elif pos in world: 47 | pos = direction.opposite.apply(*pos) 48 | direction = direction.cw 49 | else: 50 | nextpos = direction.apply(*pos) 51 | if ( 52 | nextpos not in seen and 53 | nextpos not in world and 54 | _loops(pos, direction, nextpos) 55 | ): 56 | found.add(nextpos) 57 | seen1.add((pos, direction)) 58 | seen.add(pos) 59 | pos = nextpos 60 | 61 | return len(found) 62 | 63 | 64 | INPUT_S = '''\ 65 | ....#..... 66 | .........# 67 | .......... 68 | ..#....... 69 | .......#.. 70 | .......... 71 | .#..^..... 72 | ........#. 73 | #......... 74 | ......#... 75 | ''' 76 | EXPECTED = 6 77 | 78 | 79 | @pytest.mark.parametrize( 80 | ('input_s', 'expected'), 81 | ( 82 | (INPUT_S, EXPECTED), 83 | ), 84 | ) 85 | def test(input_s: str, expected: int) -> None: 86 | assert compute(input_s) == expected 87 | 88 | 89 | def main() -> int: 90 | parser = argparse.ArgumentParser() 91 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 92 | args = parser.parse_args() 93 | 94 | with open(args.data_file) as f, support.timing(): 95 | print(compute(f.read())) 96 | 97 | return 0 98 | 99 | 100 | if __name__ == '__main__': 101 | raise SystemExit(main()) 102 | -------------------------------------------------------------------------------- /day07/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day07/__init__.py -------------------------------------------------------------------------------- /day07/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import operator 5 | import os.path 6 | from collections.abc import Callable 7 | from collections.abc import Generator 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | 16 | def _operators(n: int) -> Generator[tuple[Callable[[int, int], int], ...]]: 17 | if n == 0: 18 | yield () 19 | else: 20 | for rest in _operators(n - 1): 21 | yield (operator.add, *rest) 22 | yield (operator.mul, *rest) 23 | 24 | 25 | def _possible(target: int, nums: list[int]) -> bool: 26 | for opcomb in _operators(len(nums) - 1): 27 | val = nums[0] 28 | for i, op in enumerate(opcomb): 29 | val = op(val, nums[i + 1]) 30 | if val > target: 31 | break 32 | if val == target: 33 | return True 34 | else: 35 | return False 36 | 37 | 38 | def compute(s: str) -> int: 39 | total = 0 40 | for line in s.splitlines(): 41 | target_s, rest = line.split(': ') 42 | target = int(target_s) 43 | nums = support.parse_numbers_split(rest) 44 | 45 | if _possible(target, nums): 46 | total += target 47 | 48 | return total 49 | 50 | 51 | INPUT_S = '''\ 52 | 190: 10 19 53 | 3267: 81 40 27 54 | 83: 17 5 55 | 156: 15 6 56 | 7290: 6 8 6 15 57 | 161011: 16 10 13 58 | 192: 17 8 14 59 | 21037: 9 7 18 13 60 | 292: 11 6 16 20 61 | ''' 62 | EXPECTED = 3749 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ('input_s', 'expected'), 67 | ( 68 | (INPUT_S, EXPECTED), 69 | ), 70 | ) 71 | def test(input_s: str, expected: int) -> None: 72 | assert compute(input_s) == expected 73 | 74 | 75 | def main() -> int: 76 | parser = argparse.ArgumentParser() 77 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 78 | args = parser.parse_args() 79 | 80 | with open(args.data_file) as f, support.timing(): 81 | print(compute(f.read())) 82 | 83 | return 0 84 | 85 | 86 | if __name__ == '__main__': 87 | raise SystemExit(main()) 88 | -------------------------------------------------------------------------------- /day07/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import operator 5 | import os.path 6 | from collections.abc import Callable 7 | from collections.abc import Generator 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | 16 | def cat(n1: int, n2: int) -> int: 17 | return int(f'{n1}{n2}') 18 | 19 | 20 | def _operators(n: int) -> Generator[tuple[Callable[[int, int], int], ...]]: 21 | if n == 0: 22 | yield () 23 | else: 24 | for rest in _operators(n - 1): 25 | yield (operator.add, *rest) 26 | yield (operator.mul, *rest) 27 | yield (cat, *rest) 28 | 29 | 30 | def _possible(target: int, nums: list[int]) -> bool: 31 | for opcomb in _operators(len(nums) - 1): 32 | val = nums[0] 33 | for i, op in enumerate(opcomb): 34 | val = op(val, nums[i + 1]) 35 | if val > target: 36 | break 37 | if val == target: 38 | return True 39 | else: 40 | return False 41 | 42 | 43 | def compute(s: str) -> int: 44 | total = 0 45 | for line in s.splitlines(): 46 | target_s, rest = line.split(': ') 47 | target = int(target_s) 48 | nums = support.parse_numbers_split(rest) 49 | 50 | if _possible(target, nums): 51 | total += target 52 | 53 | return total 54 | 55 | 56 | INPUT_S = '''\ 57 | 190: 10 19 58 | 3267: 81 40 27 59 | 83: 17 5 60 | 156: 15 6 61 | 7290: 6 8 6 15 62 | 161011: 16 10 13 63 | 192: 17 8 14 64 | 21037: 9 7 18 13 65 | 292: 11 6 16 20 66 | ''' 67 | EXPECTED = 11387 68 | 69 | 70 | @pytest.mark.parametrize( 71 | ('input_s', 'expected'), 72 | ( 73 | (INPUT_S, EXPECTED), 74 | ), 75 | ) 76 | def test(input_s: str, expected: int) -> None: 77 | assert compute(input_s) == expected 78 | 79 | 80 | def main() -> int: 81 | parser = argparse.ArgumentParser() 82 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 83 | args = parser.parse_args() 84 | 85 | with open(args.data_file) as f, support.timing(): 86 | print(compute(f.read())) 87 | 88 | return 0 89 | 90 | 91 | if __name__ == '__main__': 92 | raise SystemExit(main()) 93 | -------------------------------------------------------------------------------- /day08/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day08/__init__.py -------------------------------------------------------------------------------- /day08/input.txt: -------------------------------------------------------------------------------- 1 | ...........g...................................... 2 | ......e.g......s.............R...........I........ 3 | ............g........................I............ 4 | ..b......Q....s.....................P............. 5 | .......e..T...................K...........P...F... 6 | .....g................U.............4............. 7 | .........b..........4..RU..................1.F.... 8 | ....a.....Q..........b........R..U...............1 9 | .S...T............s.............I.........f....... 10 | ...A....T...............................I......... 11 | .....Qa............A.G...K...........P............ 12 | ...........................G................1..... 13 | ...D...................................4.f........ 14 | .................................................. 15 | ................k.......R....................t.... 16 | .........T.e..............K........u.......t...... 17 | ....................A............................. 18 | ......S....a.............F...........KG........... 19 | ....D...h......k.................................. 20 | ..D...............k............................4.. 21 | ..............................................i... 22 | .........S..................d..................... 23 | ......QU....S......s..d...G............i.......... 24 | ...........d....9...F.h...E....................... 25 | .................d....B........................... 26 | ...h........................H.......t............. 27 | .........h.B..3.E.............H..r................ 28 | .......E......B......2...5.....H.................. 29 | .z...........9................................t... 30 | .....9...D........................................ 31 | .....Z.......39..a................................ 32 | ..........3.............r......................... 33 | ...............Er.............................7... 34 | .........................J...k.r.q.......i.8.p.... 35 | .......................u...............H.p..q..... 36 | ..............................i.u6........p....... 37 | ....................................0............. 38 | ...............3..J....P...0...................... 39 | ........................2j........................ 40 | ...............................j2B0............... 41 | ................J..2...5.....6......p....8........ 42 | ............y.........................7........... 43 | ..............5.........y...........6............. 44 | ................................j................. 45 | .........................Y.J.....0................ 46 | .........................................y........ 47 | ..................Z...uy...................q...... 48 | .......z.........Z............Y.6.............8... 49 | z.........................Y..........7............ 50 | ....................Z...5..Y...................... 51 | -------------------------------------------------------------------------------- /day08/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import itertools 6 | import os.path 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def compute(s: str) -> int: 16 | beacons: dict[str, list[tuple[int, int]]] 17 | beacons = collections.defaultdict(list) 18 | 19 | for y, line in enumerate(s.splitlines()): 20 | for x, c in enumerate(line): 21 | if c != '.': 22 | beacons[c].append((x, y)) 23 | 24 | bx = range(0, len(s.splitlines()[0])) 25 | by = range(0, len(s.splitlines())) 26 | 27 | antinodes = set() 28 | for nodes in beacons.values(): 29 | for (x1, y1), (x2, y2) in itertools.combinations(nodes, 2): 30 | dx, dy = x2 - x1, y2 - y1 31 | antinodes.add((x2 + dx, y2 + dy)) 32 | antinodes.add((x1 - dx, y1 - dy)) 33 | 34 | return len({(x, y) for x, y in antinodes if x in bx if y in by}) 35 | 36 | 37 | INPUT_S = '''\ 38 | ............ 39 | ........0... 40 | .....0...... 41 | .......0.... 42 | ....0....... 43 | ......A..... 44 | ............ 45 | ............ 46 | ........A... 47 | .........A.. 48 | ............ 49 | ............ 50 | ''' 51 | EXPECTED = 14 52 | 53 | 54 | @pytest.mark.parametrize( 55 | ('input_s', 'expected'), 56 | ( 57 | (INPUT_S, EXPECTED), 58 | ), 59 | ) 60 | def test(input_s: str, expected: int) -> None: 61 | assert compute(input_s) == expected 62 | 63 | 64 | def main() -> int: 65 | parser = argparse.ArgumentParser() 66 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 67 | args = parser.parse_args() 68 | 69 | with open(args.data_file) as f, support.timing(): 70 | print(compute(f.read())) 71 | 72 | return 0 73 | 74 | 75 | if __name__ == '__main__': 76 | raise SystemExit(main()) 77 | -------------------------------------------------------------------------------- /day08/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import itertools 6 | import os.path 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def compute(s: str) -> int: 16 | beacons: dict[str, list[tuple[int, int]]] 17 | beacons = collections.defaultdict(list) 18 | 19 | for y, line in enumerate(s.splitlines()): 20 | for x, c in enumerate(line): 21 | if c != '.': 22 | beacons[c].append((x, y)) 23 | 24 | bx = range(0, len(s.splitlines()[0])) 25 | by = range(0, len(s.splitlines())) 26 | 27 | antinodes = set() 28 | for nodes in beacons.values(): 29 | for (x1, y1), (x2, y2) in itertools.combinations(nodes, 2): 30 | dx, dy = x2 - x1, y2 - y1 31 | 32 | # positive 33 | cx, cy = x2, y2 34 | while cx in bx and cy in by: 35 | antinodes.add((cx, cy)) 36 | cx, cy = cx + dx, cy + dy 37 | 38 | # negative 39 | cx, cy = x1, y1 40 | while cx in bx and cy in by: 41 | antinodes.add((cx, cy)) 42 | cx, cy = cx - dx, cy - dy 43 | 44 | return len(antinodes) 45 | 46 | 47 | INPUT_S = '''\ 48 | ............ 49 | ........0... 50 | .....0...... 51 | .......0.... 52 | ....0....... 53 | ......A..... 54 | ............ 55 | ............ 56 | ........A... 57 | .........A.. 58 | ............ 59 | ............ 60 | ''' 61 | EXPECTED = 34 62 | 63 | 64 | @pytest.mark.parametrize( 65 | ('input_s', 'expected'), 66 | ( 67 | (INPUT_S, EXPECTED), 68 | ), 69 | ) 70 | def test(input_s: str, expected: int) -> None: 71 | assert compute(input_s) == expected 72 | 73 | 74 | def main() -> int: 75 | parser = argparse.ArgumentParser() 76 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 77 | args = parser.parse_args() 78 | 79 | with open(args.data_file) as f, support.timing(): 80 | print(compute(f.read())) 81 | 82 | return 0 83 | 84 | 85 | if __name__ == '__main__': 86 | raise SystemExit(main()) 87 | -------------------------------------------------------------------------------- /day09/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day09/__init__.py -------------------------------------------------------------------------------- /day09/input.txt: -------------------------------------------------------------------------------- 1 | 7951869951709134922282452546868858728072276373116135929485725576229056321127554484942498608920253277729336399124438846538848957831552147203896149936638237251843478240874014135795169860102115947428164014419681437324361464811846249931644629696816703867169780477745828189523980742227779954667392901880965034366061998678373432629412147795581297776384997775394754701459395198143772397263379674811463849540232488362349598653936699394049172453591244925022892058446761777427442826985372499785989991119370796053662235268897232816721765135397116899564018819273341954632918339796778680906210578688446814211096262816365341804416814593752535524646974224843710533934518530293069222736396163189981227781581992579976906017891437827517449023992212142373251742782489579739706655131389588695738610213383772193783629934995384544406572714144277976112533602286194119752753101478479476914011956369361566724169107389948373897359614898969529573833457070921566634454255458546027608297938137205522129078272861204428388574922820508010317565266695598118973815802787373628942620531054721791756012734456247583625125433946412872256077971746683896909072343268983741146715445395675788528595988359731415426281867564427673414797111742582330896598327095391112951936461052211061774122634365796973273774161051949634262161818747738463902097948840331648286948459539254570334648861843646019847753852538367385976985948960932175598683692913298864959729241158662780172114786731736410494387702342607156146292325641664228636711233497465224538379821855627715607672886832432372739120698049344989989529972122313543425769475063139987539941464726281463136765242617872942573523984519571916892081605130265927553075219578548862465668117139789399407753177190547817375610572456591979579685513734281083141719441024978876813547653598968060265656492997917326399019311750711318223919458388188643613067934225519060994288736535865190748231874956671359932014102879606224839754523085904840772725458710349663873142951665118689424127673294218716532342102257585070854379403894568441809526324673874634843866843161999291287292188828106389304390387658289077352135424944654688198893719824358938387062892827427435256177963753678211645125762279629090409778489637576923947526763131965138137598929216605518712980985932951196674097445510524146554330507518641479969646238484301929939516102630466587129263522771312634487336392237344488385689525677467520867220821615783676225769172662731590931560926529332756338761839654788661579726233954496059624590308696145816752265538416407096273520459469276566556680419726154458427080797824627589347648168557905134235919667583887988948225202789669889449476618433307756474563326380758481414811635533881668886530236129542975475315329217118959837479714046379518444863765057451698673090199385651293676013383413172266792375428166418797441791218010243375193299695957561715698554109199821693462017709084628693852728457130997441365442358034168561111758408968261426161139948645351221961486905510573826832826222053622254721591148948312939744116433914486613931043966635809831189977418048186871333854474459889973203168969838792699449543625016557956452146723487889856788116544771377544435781568959542854106394202812958290482017335085797374609788623467257426961794657723501721677371562757246888468118152567555635102423777464566829843731102098608091652890475250549528258449835755619193322770443391669496289355846493973023771725213657551757938888404428239494798083536048885657878880263727691041288746316337223355738851947843258436974548171790242394771638706290239261713324744332898121283212567599324433492025624478186535664978804913995734206882906481985666945085302684409190183394378975774192492632183354619980987454978438306513318021579899808872441041296664941359284942584519763264773467953012892440984066997243198330618327942755141266729335169227639460607020317574769197431071778168938863685976519061664361749070991553881931597267661381656740601467657649627711174311935416905124746590222429696921339821978250186642539750783614575087598481663864776767424364277964945628913245823276624653838769924877338858263559596222939523194096521631667670383577195062409342618521429628884825933949133425283879157174223051735382317940634573845336298657393066824921669423133798146942869882672258524621567775834071903431932295169366115236607644323757442735369820918632373292456387422772896925686138189542701646445385145028566262106870744634984593774468551766556930585156871760952613125569347948909373292913379332173973451273425947823536878494793542401937591061381971561834302783277887937298314044263576171291145460732792996510544315522020783739653674231027349530826776113542189296654735175423282577445325839193893897849467225330954166594064938016555641244316218936535248338083222437955944617194377040715279806321151241663856501777742698142350845061983539283559954382603181879968573666918046505161648325511749114186636134843937457046833484826543405896682480983353482875995366308287675048716361754753433489326913495294302894483761212068466228404479184633833576939633704272591265672588684174776135895945885826268342302423981779913153596641815966469934511574996285499432711617219175423965225766853773217616487025631663436948875549708331751283245959754030643312786379898150955111617234222567108248579679172140422133647445753633867336449597806123706512754495329524229250225719867836508729245622895573934042231829104656888114329775783054191696822171692753314564953469721240453184189238815581146980851099866368114030743453513858833969209295537958274299515144895144892740889046936554544561352059372440266783775210459750157380902419356177247774655016679737964895618168132980916952435584442040449617576622888313619435518619879225825375897748627920357131659487568530157761362482589787494180832032601151316438855078281131259285108418723829653836654792837370511625946027755818425174602163853232927925613188609266761052725426804898377859287513576848576692836038983365372931356222362269984184642023196292436678732730481353643461411523151288857468868422505274514883344620587163478768509831529469852261155879195017327023677067632270322648414293998225985417779159108110363117648170258089934535306055268294779577948348183072637590407672733841517718655434167380239822289019784537374534278278525830249033106111575890871111253556528464141462942924911110649128423720914953255256872427881893688058941544589394461621834853727159832070824798668711875346889684541732535225268683917685746296639235694354155927906631144112857359577077172349211658879939676617433461536250331610217733995342659523365165391281671552504957563812211271244176379549846094696559879498187182704174163131688132883628688255851284971669602632795612171097667959606067817393201815954917163090885378945682102393579010726518673197584776126637776043192810604153701180922333869815102733116930759024173450452784921347578030651827231957907229943092371417738958671020761317347621475068919628209115611821352315891520859241967439665266142346149440322620103859999529852292781574779575453591923344258464164081565986231655724265531948195467318586404495677917456861638343581377314335973015623174662484196754938059543358604317281129379355223327886542539631765376206323576014433783799457645220468531268059516391435483704993243666352037287026188893851837897359213171581756432436994130275332685172252615517139402332226352142022982149536521558135434824603567425915609571287295108089515862783453549041666173183928501596133944458115631418217816916440166342303731146844682545383947208142344948167823861311314816889775947623308579601622197192267595363057723495245657225920557616693922869658368953898242257284417468657233541246232557404320748611846190299567322662316027858791742389692639687745445951803110635462169063424315492291939944429082404819517349396647493216383811827840551564396553974855353024384850118225814814477216647390348999869752366398236197504124957578832456293678524028355819874410542311136160361444927318853710729926952170491346443511888014936868651263703612317092865115213377546712434353517644943016869536861755598890466124164646344695206910986917817683825880729038264769443537808838458485376687523923274240709752859434562261376626189941815376542115601227536430628195119037715745386330519486158879835063645574724584303781378030451866267129748677201294695966524426159034982497419654985441424354137236885188159591133660927357836694914689489216486628125233963393956661886120145567857031802474267147669577121386306235772845272875994969134597293918417458332363377531769131581190955459408336855063972976226294145419578145896025841311244968547763599082854610872945425776222354634598621846683330204642133550257426798397852724244530829761373642715223203228219921632880119123687326249481827028172540391943582444631297138654422521132330155683668915587656883978327183368215665348969956891495433590996593895524573166446078648775408768926151433836189312301148306270757468749051742828487084345718595022159649191783608813539671702926348562118789283695187692193714269424787614892914927429169578505728366530111723615241299324237471247459445764258639219351298352139889729734758868752163342748475673216687262615808253979385183716339915629435821550462015553642198712345182962541556331815847132493323012577340444887856349959553424567525953748040559410939752577262477240981735271716689736381020967732287333494823797536685759394194801325924252293094394040569118333434415721165457568114735083703678434726232888843580209044374870326677462530224241142539479249931380135442476289244339574154637787804019443013116183218716122992519021597254859766229831398316915959399539191779526071471830339492124383633318944940652340112016322540119427476175439821598931462139429278788791786462948485805454135823816826808854232398162283679997709921222794707762413819131967253831435277896077662593536648694797509928333235453941867789234265936876492437111084345730153884361161831614508038372637554862514230498576462723945892495832847752895998879510368888296015341185585973554590871233883088156530474517302543607037723192841519479372946378936631146748388285925817234134312185559056394667945464893094163557462711274357939257467491358952101559468566112516501237541294253142534779688380169426711090119873702952704471505851643928743172237027307615641825574735124068244761426199135419176682706421926632736462809095758666976259706177467487869060102774156957702329384440191575225410825780401310534387739026703751168073784483268835391524591716916327584570574276291641998437994239464675822262553170279390511167868777754365721594803850271857919151255658159969502428492574715132224147579214401074523564573954557399352317893877386047735921714652803227531124458669887775903327243663267579288333515667258698461295754149802185596645831445479880817477541749996062436173119561881860505487714827214592116782669445733045402652302836311938265189936063761298129212552580175638857617493210553862262785396131142428558153685299189417677199298999261396946861781758151527933762194091742772182868394835124140275430201411584592301873895655214943355874128263894683394076596863957619985674124219606153739076731924626567363457886674543193926992168268997896107045555773868446301091736042951263464590697253577468459875173127269991732149364072235278881255182489301448637721399890743169129644587059197334252074277136446759612026876196563833518074774888848639892186935080158091988955982198986536344162796882111723661475474184186643582215745548706921549333482364194633535929931343886118907416871819289561871975804560698318919581979445714391744355517839995741901473349613662349489944719846734735384110168010509262942239978763733322948899518131323482699024714247634410931236405835535997829829637934684529153299699745108694675277811468797652878384398655518745599294371450397679581214728248563227342082399948453766129759647623926444576889791069322085235750128336787187227732834235538836573852493598632378289457359746981898701969332631261970357120444895376218554860833774537458998697437447758375611198834499304563207161647659525320359525552726367126885460317891809491638566699010749286587782724880717073134352427031876282408517559786313873625415156459176844456080878974908226158052407939608119897595574860183671665494372882501270361748233223192713659467872281238421768311786914758943621082806185623734863890818375584027258858837697915817702533887559762946614868271018146249172232484360631321419691764475297954688420625079614081737548994983581058721259207326688395692664766424536095282892248312191090723595252744219373945619669047958122984578453919274257424957664469991757259366298522209835319254684768496528944825443518613118145316122699777591602988695188491677411888854157321559897024358530276947421071404972711237392560441024886644938267532420384963303589473819285214681420119039293312513541252747152344992996707738543190423619574657987910972491565157401597621953462591673659486765256615246215576785458550162089871451636470718043724828289265691086233619882332613663638046619830158863587288892461852862857980389569985420679565164775823629991762984251206276731164893924123366797641648576868475454136478273372591183632843226805844882463583545657286178210974271697820287238811714787054509697502996552025726456609963398175338259678574265679192110912553986378199394281717876849282967443730712371661292891266192153856317715797765358236178906433384543314438743378744882306985259441158930769631379155969633782176245113104916593880184525517697574576225067216671813922159286317010621261596510405160786787588398799835424966889533187088705469231525182472375740699551487028238614141476139029935362615421686793845211867667251226218410933138451267618995748175113371876914654873653310195672934397847745382385391821171810527914596959994368663863419637188284685158446492752197136455443744784386674173195314785993773044895681105667483157469357864820518639163727322887921152564331178947579264297765411098721115845067401617879468386330616410546975769337567185726856267633781140814090694258479298342947695341998717203628915461415793627838941178655337986198784796372158613195985695953081325658763326339129115730526967656872983395974127787631453050279626141440601638519914319361733651711454795843594268599085605595734092384318534221188632203563471935359762221229588138212783953179963771501047367015647088602095361949743276632252623752732354586268936556608289665357308517191770443110562457658364441268322679545497812931395439497327906550492120832255293161693544767419883056346278489950934228777799658163373358265639523714708030941231158343473411983238204977198685694118514476391385518919284097428385493914217489166248309216102769703250114833828841307314606179466944618578819614309463268066226890597858796465707198134449148928165666846314934166695242699887785262771559852339848935956439697977601696864598797221363353762987409162906843831659238563214049495457578736863175103716814886455750189373297929486584621539351569295196102565335818379793772879482820765456306344102846939596146724577483823740388881249440111236644248577531798641385464654382858896244436716749139279994499797379919714661748991511976837765492553643497925488425131028999229767125209026564090186851383927708975786884855084225498442626135358266756211255448769887469648193771847238964205696589831344053989932512741665011877394734291456865698188309030713035254274433075188811869048992011253734663297226680325486369152987461162929622564308572743355326329111687545632195567252349553137163679192416301458745012909374925736153525641258588427482531919167729237483229949894237088187020876650925473471089722790993955386847878333978036793937335199411275174690336924922187276630115443236328522880323525679592821361124622777571342869207748764115722317263881713343226594433424887251848256878697163151171652291793853428136111875676992726437337131180753093533182111366343789286956747021492615516664555324507741879986258579875382291945849426892560671533427398995629892985954810695838201427985522578524519836968027578458399544609060373883136217569886104612854595499468593324718188959430435377318286656894317226682297745024244597215290953778284974877368189076664533643015835285917997617635339988167640907129107910682684216546633344926680324910624595534895168025669689557413812379684350158816607373979618307826444742189962166296689359492179133425826447405184283469167440106431267383841932551379982811363791739758269511769777479949909499571482377288281445969045167969708379953114972423848168556318424595281512454730193594589532638510128660588721667928604613179899435477655957418963686172581120322129132891951943854113406924214169851642393761493481794898176121777572634428969175654989359368402128383683704127293578534551681143985591693773744738132949541387476323532034663870344449632677107244132089855085464310999967785885276373262055719532483323499490631356505282734762453546644348742576732860422071311642161068672178552370369271962037196534641343668065967631234249898896909929773832422420535042681366525661984590667721648756998387163270941021491614862172751494657581901240374148586673861388975510455548754423947247746368641466673499702153722673916942957353142374196970351531527120919692566939904067448724422532142744745128715363686523353996223589411566759365511337852971245865426784817711314268848449909092319138384148519742571455751781802661523539411597409125496782926058646548258985506284598259632666324717708842731261429522701068171459711147939662643277554385768829295771208178208680946865858758498137883640257715546418132037323495607339517654618041844075343252476920404720881968444553508910332467798856881484532128827158399795374839637630694753146314188495156850913690993930242372326947605871229889948466716416511541848689735314346643529540548757626384577636715380506683513960596598267772274572209157384240278465503691208798681827266991788750822123325679414920161350295664976695957310569563617472719860406768668661543945926568745199493970324941778749993321428442909665869713384879328872474954777717244128922756239218997468599087319129494575422286302678368060468764692687226654346378202074221045231090585394864895229090755889524473934194338099971495404293202278783745687956546166817543365071883423125846839442978028797879686217203399281541592651661586927578213447828861199380118483592059991465347225247165587734652353776015261341307931119998713452931989964346889676623981603536145095674089206054685827391272309829339774815983803653658728929456588361687949111258867690492626944734962988291030614182263461683880795127916546958086758851477711343188269821967452735745292775496215572355862992682787508815786154348125447086185786871172408444759476498377696296957253228353651083194127353711323354698667794071983225247685136858884811979261679094247778767712998420791288252356479293223670469551861453846594244658731961809874135268351361188639531134211930946484757215605750163975548256492331349176463768338718693968799941856546693426177247653716969985119944534852662830512066245944801226625653713657716038392496296790605473395663492419236526226765449423159494562166816190496016352572472584471920476688342212382784661593883250389288186782429976641374519140136396941513322283834617605736622269908851464411763796605638913929755526781657442990934412565469363247884740348538234996228159804633253260944560624741635938215427624012883030515890966795317455815342775890842323671232397711586340767498156071231065355560893429698694947083328128678335634524587968772521468313488385279571815292371277113176606637871480506737696919714463916793743973452480822773309628703411515253219548974756352119513862143341737525642213804238599864751891921274924410877181648574408569153549171697861683937149798357924434243121747321909415332499765947749972612720579095359332283331519317585130908633816881185747451525636853534574592879986051718245645749555943515733133849358674504167542610195098631412651367953963146812341183442646187748168337754712582024642797961482327594867111159488786951472138381426987934736297209055774497769333878043173623604286574586649147105286344824321727979254605566525266254171192815129883973272521922547275971980697887242845968666517755297357711473385998564818721620836238216428583845711322882111798158557627563954519 2 | -------------------------------------------------------------------------------- /day09/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import itertools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | disk: list[int | None] = [] 16 | s = s.strip() 17 | it = itertools.zip_longest(s[::2], s[1::2], fillvalue=0) 18 | for did, (c1, c2) in enumerate(it): 19 | n1 = int(c1) 20 | n2 = int(c2) 21 | disk.extend([did] * n1) 22 | disk.extend([None] * n2) 23 | 24 | def _pop(pos: int) -> int: 25 | last = disk.pop() 26 | while last is None: 27 | if len(disk) <= pos: 28 | return 0 29 | last = disk.pop() 30 | return last 31 | 32 | ret = 0 33 | for i, val in enumerate(disk): 34 | if val is None: 35 | val = _pop(i) 36 | ret += i * val 37 | 38 | return ret 39 | 40 | 41 | INPUT_S = '''\ 42 | 2333133121414131402 43 | ''' 44 | EXPECTED = 1928 45 | 46 | 47 | @pytest.mark.parametrize( 48 | ('input_s', 'expected'), 49 | ( 50 | (INPUT_S, EXPECTED), 51 | ('0015', 0), 52 | ), 53 | ) 54 | def test(input_s: str, expected: int) -> None: 55 | assert compute(input_s) == expected 56 | 57 | 58 | def main() -> int: 59 | parser = argparse.ArgumentParser() 60 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 61 | args = parser.parse_args() 62 | 63 | with open(args.data_file) as f, support.timing(): 64 | print(compute(f.read())) 65 | 66 | return 0 67 | 68 | 69 | if __name__ == '__main__': 70 | raise SystemExit(main()) 71 | -------------------------------------------------------------------------------- /day09/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import itertools 5 | import os.path 6 | from typing import NamedTuple 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | class File(NamedTuple): 16 | did: int 17 | size: int 18 | 19 | def value(self, offset: int) -> int: 20 | return sum(i * self.did for i in range(offset, offset + self.size)) 21 | 22 | 23 | class Free(NamedTuple): 24 | size: int 25 | 26 | 27 | def compute(s: str) -> int: 28 | disk: list[File | Free] = [] 29 | s = s.strip() 30 | it = itertools.zip_longest(s[::2], s[1::2], fillvalue=0) 31 | 32 | for did, (c1, c2) in enumerate(it): 33 | n1 = int(c1) 34 | n2 = int(c2) 35 | disk.append(File(did, n1)) 36 | disk.append(Free(n2)) 37 | 38 | searchfrom = len(disk) - 1 39 | 40 | def _pos(did: int) -> tuple[int, File]: 41 | nonlocal searchfrom 42 | segment = disk[searchfrom] 43 | while not isinstance(segment, File) or segment.did != did: 44 | searchfrom -= 1 45 | segment = disk[searchfrom] 46 | return searchfrom, segment 47 | 48 | def _target(*, size: int, maxpos: int) -> tuple[int, Free] | None: 49 | for i in range(maxpos): 50 | segment = disk[i] 51 | if isinstance(segment, Free) and segment.size >= size: 52 | return i, segment 53 | else: 54 | return None 55 | 56 | for did in range(did, -1, -1): 57 | pos, segment = _pos(did) 58 | 59 | target = _target(size=segment.size, maxpos=pos) 60 | if target is not None: 61 | target_pos, target_free = target 62 | if target_free.size == segment.size: 63 | disk[pos], disk[target_pos] = disk[target_pos], disk[pos] 64 | else: 65 | disk[pos] = Free(segment.size) 66 | disk[target_pos] = Free(target_free.size - segment.size) 67 | disk.insert(target_pos, segment) 68 | searchfrom += 1 69 | 70 | total = 0 71 | offset = 0 72 | for part in disk: 73 | if isinstance(part, File): 74 | total += part.value(offset) 75 | offset += part.size 76 | 77 | return total 78 | 79 | 80 | INPUT_S = '''\ 81 | 2333133121414131402 82 | ''' 83 | EXPECTED = 2858 84 | 85 | 86 | @pytest.mark.parametrize( 87 | ('input_s', 'expected'), 88 | ( 89 | (INPUT_S, EXPECTED), 90 | ('0015', 0), 91 | ), 92 | ) 93 | def test(input_s: str, expected: int) -> None: 94 | assert compute(input_s) == expected 95 | 96 | 97 | def main() -> int: 98 | parser = argparse.ArgumentParser() 99 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 100 | args = parser.parse_args() 101 | 102 | with open(args.data_file) as f, support.timing(): 103 | print(compute(f.read())) 104 | 105 | return 0 106 | 107 | 108 | if __name__ == '__main__': 109 | raise SystemExit(main()) 110 | -------------------------------------------------------------------------------- /day10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day10/__init__.py -------------------------------------------------------------------------------- /day10/input.txt: -------------------------------------------------------------------------------- 1 | 676781023010121078756541010565410126589652103 2 | 787692014523134569687238921076321087676543012 3 | 896543210674013278798107831089980896567122107 4 | 654100134985329143237356540123678925498033498 5 | 783210325676438050123445443294541012321044589 6 | 694309018984567267034576356789032008769655678 7 | 345678567823432178125689219878102109678724369 8 | 456969430214563089068701008765210234569013212 9 | 327854321005478873879010123674377654354321001 10 | 218901232876569912968123294589988912210132432 11 | 107650343985567803451054387487676903432101561 12 | 210545674783498712589965236596567876543650170 13 | 323432185692105605678876145645430967858743289 14 | 214981092185434104987986001034321458969801001 15 | 105670123076393213098887632125010321578932102 16 | 789889874101284332106798540136521230432840013 17 | 876776965692379876087034567287650145521051224 18 | 965460150789561045696129898398010676670569345 19 | 234321041276432038765408765498723487989678496 20 | 165432132345987129932317454389654395432310987 21 | 074540122187656087801326761230101276211001236 22 | 783458043090345196540410890121210989303456545 23 | 892169834901210105432589789032367893456327896 24 | 701078985810012234501678776543456302587410187 25 | 667654856798943107657578905494543211693217896 26 | 578983012367874038748765412387687600784506787 27 | 457832343455465129889854307898990521099615690 28 | 300761567854321012934781212387121434988794321 29 | 211650434969482103245690101236012345670188760 30 | 672349123478091014132386789845621012873279454 31 | 589678012562182365001675632736790123964560303 32 | 432547001601276478976543541345887654456781212 33 | 321232118762345569885012310212994569323998800 34 | 210321129098710378894322343200123478010878901 35 | 300410030123601256765011056123430765430765432 36 | 321567542034510349810782987001521894321045645 37 | 434788601945654878725693965432676541013236012 38 | 095699717898783965434344876501987034901107823 39 | 187659826500192854303239884567698127872787934 40 | 234549834410201601214138793298012016543396543 41 | 067812765325360519871025682105623456671230123 42 | 154908901876451456789014871234702965580145674 43 | 233217010945962359874323960189811874495432985 44 | 945606321034876543265221054076320432356781876 45 | 876545432321089012100100123165410321065690165 46 | -------------------------------------------------------------------------------- /day10/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | world = support.parse_coords_int(s) 15 | 16 | def _score(pos: tuple[int, int]) -> int: 17 | completed = set() 18 | todo = [(pos, 0)] 19 | while todo: 20 | pos, size = todo.pop() 21 | 22 | if size == 9: 23 | completed.add(pos) 24 | continue 25 | 26 | for coord in support.adjacent_4(*pos): 27 | if world.get(coord, -1) == size + 1: 28 | todo.append((coord, size + 1)) 29 | 30 | return len(completed) 31 | 32 | return sum(_score(k) for k, v in world.items() if v == 0) 33 | 34 | 35 | INPUT_S = '''\ 36 | 89010123 37 | 78121874 38 | 87430965 39 | 96549874 40 | 45678903 41 | 32019012 42 | 01329801 43 | 10456732 44 | ''' 45 | EXPECTED = 36 46 | 47 | 48 | @pytest.mark.parametrize( 49 | ('input_s', 'expected'), 50 | ( 51 | (INPUT_S, EXPECTED), 52 | ), 53 | ) 54 | def test(input_s: str, expected: int) -> None: 55 | assert compute(input_s) == expected 56 | 57 | 58 | def main() -> int: 59 | parser = argparse.ArgumentParser() 60 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 61 | args = parser.parse_args() 62 | 63 | with open(args.data_file) as f, support.timing(): 64 | print(compute(f.read())) 65 | 66 | return 0 67 | 68 | 69 | if __name__ == '__main__': 70 | raise SystemExit(main()) 71 | -------------------------------------------------------------------------------- /day10/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | world = support.parse_coords_int(s) 15 | 16 | def _score(pos: tuple[int, int]) -> int: 17 | completed = 0 18 | todo = [(pos, 0)] 19 | while todo: 20 | pos, size = todo.pop() 21 | 22 | if size == 9: 23 | completed += 1 24 | continue 25 | 26 | for coord in support.adjacent_4(*pos): 27 | if world.get(coord, -1) == size + 1: 28 | todo.append((coord, size + 1)) 29 | 30 | return completed 31 | 32 | return sum(_score(k) for k, v in world.items() if v == 0) 33 | 34 | 35 | INPUT_S = '''\ 36 | 89010123 37 | 78121874 38 | 87430965 39 | 96549874 40 | 45678903 41 | 32019012 42 | 01329801 43 | 10456732 44 | ''' 45 | EXPECTED = 81 46 | 47 | 48 | @pytest.mark.parametrize( 49 | ('input_s', 'expected'), 50 | ( 51 | (INPUT_S, EXPECTED), 52 | ), 53 | ) 54 | def test(input_s: str, expected: int) -> None: 55 | assert compute(input_s) == expected 56 | 57 | 58 | def main() -> int: 59 | parser = argparse.ArgumentParser() 60 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 61 | args = parser.parse_args() 62 | 63 | with open(args.data_file) as f, support.timing(): 64 | print(compute(f.read())) 65 | 66 | return 0 67 | 68 | 69 | if __name__ == '__main__': 70 | raise SystemExit(main()) 71 | -------------------------------------------------------------------------------- /day11/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day11/__init__.py -------------------------------------------------------------------------------- /day11/input.txt: -------------------------------------------------------------------------------- 1 | 0 89741 316108 7641 756 9 7832357 91 2 | -------------------------------------------------------------------------------- /day11/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | numbers = support.parse_numbers_split(s) 15 | 16 | def _transmute(n: int) -> tuple[int, ...]: 17 | if n == 0: 18 | return (1,) 19 | elif len(str(n)) % 2 == 0: 20 | nstr = str(n) 21 | midp = len(nstr) // 2 22 | return int(nstr[:midp]), int(nstr[midp:]) 23 | else: 24 | return (2024 * n,) 25 | 26 | for _ in range(25): 27 | numbers = [m for n in numbers for m in _transmute(n)] 28 | 29 | return len(numbers) 30 | 31 | 32 | INPUT_S = '''\ 33 | 125 17 34 | ''' 35 | EXPECTED = 55312 36 | 37 | 38 | @pytest.mark.parametrize( 39 | ('input_s', 'expected'), 40 | ( 41 | (INPUT_S, EXPECTED), 42 | ), 43 | ) 44 | def test(input_s: str, expected: int) -> None: 45 | assert compute(input_s) == expected 46 | 47 | 48 | def main() -> int: 49 | parser = argparse.ArgumentParser() 50 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 51 | args = parser.parse_args() 52 | 53 | with open(args.data_file) as f, support.timing(): 54 | print(compute(f.read())) 55 | 56 | return 0 57 | 58 | 59 | if __name__ == '__main__': 60 | raise SystemExit(main()) 61 | -------------------------------------------------------------------------------- /day11/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | @functools.cache 15 | def _compute(n: int, m: int) -> int: 16 | if m == 0: 17 | return 1 18 | elif n == 0: 19 | return _compute(1, m - 1) 20 | elif len(str(n)) % 2 == 0: 21 | nstr = str(n) 22 | midp = len(nstr) // 2 23 | return ( 24 | _compute(int(nstr[:midp]), m - 1) + 25 | _compute(int(nstr[midp:]), m - 1) 26 | ) 27 | else: 28 | return _compute(2024 * n, m - 1) 29 | 30 | 31 | def compute(s: str, *, iterations: int = 75) -> int: 32 | numbers = support.parse_numbers_split(s) 33 | 34 | return sum(_compute(n, iterations) for n in numbers) 35 | 36 | 37 | INPUT_S = '''\ 38 | 125 17 39 | ''' 40 | EXPECTED = 55312 41 | 42 | 43 | @pytest.mark.parametrize( 44 | ('input_s', 'expected'), 45 | ( 46 | (INPUT_S, EXPECTED), 47 | ), 48 | ) 49 | def test(input_s: str, expected: int) -> None: 50 | assert compute(input_s, iterations=25) == expected 51 | 52 | 53 | def main() -> int: 54 | parser = argparse.ArgumentParser() 55 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 56 | args = parser.parse_args() 57 | 58 | with open(args.data_file) as f, support.timing(): 59 | print(compute(f.read())) 60 | 61 | return 0 62 | 63 | 64 | if __name__ == '__main__': 65 | raise SystemExit(main()) 66 | -------------------------------------------------------------------------------- /day12/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day12/__init__.py -------------------------------------------------------------------------------- /day12/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | world = { 15 | (x, y): c 16 | for y, line in enumerate(s.splitlines()) 17 | for x, c in enumerate(line) 18 | } 19 | 20 | chunks = [] 21 | 22 | def _chunk(pos: tuple[int, int], c: str) -> None: 23 | chunk = {pos} 24 | todo = [pos] 25 | while todo: 26 | pos = todo.pop() 27 | 28 | for coord in support.adjacent_4(*pos): 29 | if world.get(coord) == c: 30 | world.pop(coord) 31 | todo.append(coord) 32 | chunk.add(coord) 33 | 34 | chunks.append(chunk) 35 | 36 | while world: 37 | k = next(iter(world)) 38 | v = world.pop(k) 39 | _chunk(k, v) 40 | 41 | def _adj(pos: tuple[int, int], chunk: set[tuple[int, int]]) -> int: 42 | return sum( 43 | 1 44 | for coord in support.adjacent_4(*pos) 45 | if coord not in chunk 46 | ) 47 | 48 | def _value(chunk: set[tuple[int, int]]) -> int: 49 | return len(chunk) * sum(_adj(pos, chunk) for pos in chunk) 50 | 51 | return sum(_value(chunk) for chunk in chunks) 52 | 53 | 54 | INPUT_S = '''\ 55 | RRRRIICCFF 56 | RRRRIICCCF 57 | VVRRRCCFFF 58 | VVRCCCJFFF 59 | VVVVCJJCFE 60 | VVIVCCJJEE 61 | VVIIICJJEE 62 | MIIIIIJJEE 63 | MIIISIJEEE 64 | MMMISSJEEE 65 | ''' 66 | EXPECTED = 1930 67 | 68 | 69 | @pytest.mark.parametrize( 70 | ('input_s', 'expected'), 71 | ( 72 | (INPUT_S, EXPECTED), 73 | ), 74 | ) 75 | def test(input_s: str, expected: int) -> None: 76 | assert compute(input_s) == expected 77 | 78 | 79 | def main() -> int: 80 | parser = argparse.ArgumentParser() 81 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 82 | args = parser.parse_args() 83 | 84 | with open(args.data_file) as f, support.timing(): 85 | print(compute(f.read())) 86 | 87 | return 0 88 | 89 | 90 | if __name__ == '__main__': 91 | raise SystemExit(main()) 92 | -------------------------------------------------------------------------------- /day12/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | world = { 16 | (x, y): c 17 | for y, line in enumerate(s.splitlines()) 18 | for x, c in enumerate(line) 19 | } 20 | 21 | chunks = [] 22 | 23 | def _chunk(pos: tuple[int, int], c: str) -> None: 24 | chunk = {pos} 25 | todo = [pos] 26 | while todo: 27 | pos = todo.pop() 28 | 29 | for coord in support.adjacent_4(*pos): 30 | if world.get(coord) == c: 31 | world.pop(coord) 32 | todo.append(coord) 33 | chunk.add(coord) 34 | 35 | chunks.append(chunk) 36 | 37 | while world: 38 | k = next(iter(world)) 39 | v = world.pop(k) 40 | _chunk(k, v) 41 | 42 | def _value(chunk: set[tuple[int, int]]) -> int: 43 | found: dict[tuple[int, int], set[support.Direction4]] 44 | found = collections.defaultdict(set) 45 | for pos in chunk: 46 | for d in support.Direction4: 47 | coord = d.apply(*pos) 48 | if coord not in chunk: 49 | found[coord].add(d) 50 | 51 | def _process( 52 | pos: tuple[int, int], 53 | d: support.Direction4, 54 | d_a: support.Direction4, 55 | ) -> None: 56 | pos = d_a.apply(*pos) 57 | while pos in found and d in found[pos]: 58 | found[pos].remove(d) 59 | if not found[pos]: 60 | found.pop(pos) 61 | pos = d_a.apply(*pos) 62 | 63 | def _del_side(pos: tuple[int, int], d: support.Direction4) -> None: 64 | _process(pos, d, d.cw) 65 | _process(pos, d, d.ccw) 66 | 67 | sides = 0 68 | while found: 69 | k = next(iter(found)) 70 | v = found[k].pop() 71 | if not found[k]: 72 | found.pop(k) 73 | _del_side(k, v) 74 | sides += 1 75 | 76 | return len(chunk) * sides 77 | 78 | return sum(_value(chunk) for chunk in chunks) 79 | 80 | 81 | INPUT_S = '''\ 82 | RRRRIICCFF 83 | RRRRIICCCF 84 | VVRRRCCFFF 85 | VVRCCCJFFF 86 | VVVVCJJCFE 87 | VVIVCCJJEE 88 | VVIIICJJEE 89 | MIIIIIJJEE 90 | MIIISIJEEE 91 | MMMISSJEEE 92 | ''' 93 | EXPECTED = 1206 94 | 95 | 96 | @pytest.mark.parametrize( 97 | ('input_s', 'expected'), 98 | ( 99 | (INPUT_S, EXPECTED), 100 | ), 101 | ) 102 | def test(input_s: str, expected: int) -> None: 103 | assert compute(input_s) == expected 104 | 105 | 106 | def main() -> int: 107 | parser = argparse.ArgumentParser() 108 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 109 | args = parser.parse_args() 110 | 111 | with open(args.data_file) as f, support.timing(): 112 | print(compute(f.read())) 113 | 114 | return 0 115 | 116 | 117 | if __name__ == '__main__': 118 | raise SystemExit(main()) 119 | -------------------------------------------------------------------------------- /day13/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day13/__init__.py -------------------------------------------------------------------------------- /day13/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 6 | from typing import NamedTuple 7 | from typing import Self 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | NUM = re.compile(r'X[+=](\d+), Y[+=](\d+)$') 16 | 17 | 18 | def _nums(s: str) -> tuple[int, int]: 19 | match = NUM.search(s) 20 | assert match, s 21 | return int(match[1]), int(match[2]) 22 | 23 | 24 | class Game(NamedTuple): 25 | a: tuple[int, int] 26 | b: tuple[int, int] 27 | target: tuple[int, int] 28 | 29 | def solve(self) -> tuple[int, int] | None: 30 | x1, y1 = self.a 31 | x2, y2 = self.b 32 | x3, y3 = self.target 33 | 34 | """\ 35 | A * 23 + B * 93 = 6993 36 | * 97 * 97 = * 97 37 | A * 97 + B * 12 = 2877 38 | * 23 * 23 * 23 39 | 40 | B * 93 * 97 - B * 12 * 23 = 6993 * 97 - 2877 * 23 41 | B * (93 * 97 - 12 * 23) = (6993 * 97 - 2877 * 23) 42 | B = (6993 * 97 - 2877 * 23) / (93 * 97 - 12 * 23) 43 | """ 44 | 45 | # zero division??? 46 | B = (x3 * y1 - y3 * x1) / (x2 * y1 - y2 * x1) 47 | if not B.is_integer() or B < 0: 48 | return None 49 | 50 | A = (x3 - B * x2) / x1 51 | if not A.is_integer() or A < 0: 52 | return None 53 | 54 | return int(A), int(B) 55 | 56 | @classmethod 57 | def parse(cls, s: str) -> Self: 58 | lines = s.splitlines() 59 | return cls(_nums(lines[0]), _nums(lines[1]), _nums(lines[2])) 60 | 61 | 62 | def compute(s: str) -> int: 63 | total = 0 64 | for part in s.split('\n\n'): 65 | game = Game.parse(part) 66 | solution = game.solve() 67 | if solution is not None and all(p <= 100 for p in solution): 68 | a, b = solution 69 | total += 3 * a + b 70 | 71 | return total 72 | 73 | 74 | INPUT_S = '''\ 75 | Button A: X+94, Y+34 76 | Button B: X+22, Y+67 77 | Prize: X=8400, Y=5400 78 | 79 | Button A: X+26, Y+66 80 | Button B: X+67, Y+21 81 | Prize: X=12748, Y=12176 82 | 83 | Button A: X+17, Y+86 84 | Button B: X+84, Y+37 85 | Prize: X=7870, Y=6450 86 | 87 | Button A: X+69, Y+23 88 | Button B: X+27, Y+71 89 | Prize: X=18641, Y=10279 90 | ''' 91 | EXPECTED = 480 92 | 93 | 94 | @pytest.mark.parametrize( 95 | ('input_s', 'expected'), 96 | ( 97 | (INPUT_S, EXPECTED), 98 | ), 99 | ) 100 | def test(input_s: str, expected: int) -> None: 101 | assert compute(input_s) == expected 102 | 103 | 104 | def main() -> int: 105 | parser = argparse.ArgumentParser() 106 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 107 | args = parser.parse_args() 108 | 109 | with open(args.data_file) as f, support.timing(): 110 | print(compute(f.read())) 111 | 112 | return 0 113 | 114 | 115 | if __name__ == '__main__': 116 | raise SystemExit(main()) 117 | -------------------------------------------------------------------------------- /day13/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 6 | from typing import NamedTuple 7 | from typing import Self 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | NUM = re.compile(r'X[+=](\d+), Y[+=](\d+)$') 14 | 15 | 16 | def _nums(s: str) -> tuple[int, int]: 17 | match = NUM.search(s) 18 | assert match, s 19 | return int(match[1]), int(match[2]) 20 | 21 | 22 | class Game(NamedTuple): 23 | a: tuple[int, int] 24 | b: tuple[int, int] 25 | target: tuple[int, int] 26 | 27 | def solve(self) -> tuple[int, int] | None: 28 | x1, y1 = self.a 29 | x2, y2 = self.b 30 | x3, y3 = self.target 31 | 32 | """\ 33 | A * 23 + B * 93 = 6993 34 | * 97 * 97 = * 97 35 | A * 97 + B * 12 = 2877 36 | * 23 * 23 * 23 37 | 38 | B * 93 * 97 - B * 12 * 23 = 6993 * 97 - 2877 * 23 39 | B * (93 * 97 - 12 * 23) = (6993 * 97 - 2877 * 23) 40 | B = (6993 * 97 - 2877 * 23) / (93 * 97 - 12 * 23) 41 | """ 42 | 43 | # zero division??? 44 | B = (x3 * y1 - y3 * x1) / (x2 * y1 - y2 * x1) 45 | if not B.is_integer() or B < 0: 46 | return None 47 | 48 | A = (x3 - B * x2) / x1 49 | if not A.is_integer() or A < 0: 50 | return None 51 | 52 | return int(A), int(B) 53 | 54 | @classmethod 55 | def parse(cls, s: str) -> Self: 56 | lines = s.splitlines() 57 | tx, ty = _nums(lines[2]) 58 | tx += 10000000000000 59 | ty += 10000000000000 60 | return cls(_nums(lines[0]), _nums(lines[1]), (tx, ty)) 61 | 62 | 63 | def compute(s: str) -> int: 64 | total = 0 65 | for part in s.split('\n\n'): 66 | game = Game.parse(part) 67 | solution = game.solve() 68 | if solution is not None: 69 | a, b = solution 70 | total += 3 * a + b 71 | 72 | return total 73 | 74 | 75 | def main() -> int: 76 | parser = argparse.ArgumentParser() 77 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 78 | args = parser.parse_args() 79 | 80 | with open(args.data_file) as f, support.timing(): 81 | print(compute(f.read())) 82 | 83 | return 0 84 | 85 | 86 | if __name__ == '__main__': 87 | raise SystemExit(main()) 88 | -------------------------------------------------------------------------------- /day14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day14/__init__.py -------------------------------------------------------------------------------- /day14/input.txt: -------------------------------------------------------------------------------- 1 | p=84,26 v=99,-23 2 | p=98,17 v=-41,-46 3 | p=3,84 v=-17,-23 4 | p=92,66 v=36,-4 5 | p=16,102 v=95,-60 6 | p=14,90 v=-71,-62 7 | p=26,29 v=91,-20 8 | p=74,36 v=67,30 9 | p=59,66 v=86,-4 10 | p=32,47 v=-79,7 11 | p=82,42 v=3,-84 12 | p=61,16 v=28,34 13 | p=82,9 v=-84,-78 14 | p=5,71 v=16,84 15 | p=15,79 v=-48,75 16 | p=76,92 v=-32,68 17 | p=43,93 v=54,78 18 | p=22,73 v=-49,68 19 | p=79,13 v=-78,-55 20 | p=61,17 v=1,90 21 | p=71,58 v=-73,16 22 | p=60,63 v=-31,87 23 | p=8,41 v=-17,-96 24 | p=13,51 v=-16,-51 25 | p=86,15 v=12,-65 26 | p=35,71 v=57,3 27 | p=77,81 v=89,86 28 | p=100,37 v=93,-84 29 | p=31,38 v=22,20 30 | p=9,98 v=99,-47 31 | p=29,21 v=72,-76 32 | p=67,99 v=76,-25 33 | p=62,67 v=54,78 34 | p=80,79 v=-1,91 35 | p=74,7 v=51,-42 36 | p=31,57 v=30,70 37 | p=7,74 v=57,-75 38 | p=49,29 v=-9,34 39 | p=27,70 v=-45,-54 40 | p=96,30 v=47,-77 41 | p=7,22 v=-65,52 42 | p=50,53 v=15,-1 43 | p=37,44 v=46,-96 44 | p=85,87 v=-10,-25 45 | p=91,8 v=47,-56 46 | p=96,11 v=-50,-67 47 | p=75,85 v=80,90 48 | p=51,72 v=41,79 49 | p=81,67 v=-93,-25 50 | p=78,51 v=65,-99 51 | p=94,21 v=15,38 52 | p=41,63 v=-77,89 53 | p=98,11 v=-52,31 54 | p=97,53 v=67,12 55 | p=10,93 v=78,-74 56 | p=13,23 v=53,40 57 | p=73,39 v=-11,58 58 | p=82,54 v=45,-39 59 | p=4,14 v=-52,60 60 | p=69,85 v=-93,86 61 | p=88,27 v=-98,-64 62 | p=54,88 v=50,-27 63 | p=53,84 v=-82,-15 64 | p=47,51 v=24,61 65 | p=100,94 v=-54,-52 66 | p=91,23 v=1,-66 67 | p=95,45 v=-42,7 68 | p=76,92 v=80,53 69 | p=29,0 v=13,-29 70 | p=38,46 v=40,2 71 | p=29,19 v=-24,-59 72 | p=4,53 v=-74,11 73 | p=77,87 v=45,-13 74 | p=75,12 v=-82,-52 75 | p=95,84 v=38,93 76 | p=52,52 v=20,17 77 | p=16,0 v=-83,51 78 | p=68,21 v=98,-72 79 | p=9,54 v=-76,20 80 | p=27,9 v=9,-55 81 | p=32,44 v=-11,-96 82 | p=90,59 v=67,6 83 | p=68,52 v=65,30 84 | p=71,44 v=-3,11 85 | p=6,14 v=16,44 86 | p=10,0 v=18,-40 87 | p=85,41 v=17,90 88 | p=15,4 v=-59,57 89 | p=44,65 v=2,97 90 | p=15,78 v=53,-48 91 | p=34,12 v=46,53 92 | p=43,45 v=86,-83 93 | p=76,52 v=-92,-81 94 | p=11,80 v=97,-24 95 | p=37,3 v=48,63 96 | p=1,54 v=49,-5 97 | p=45,61 v=-5,81 98 | p=71,63 v=87,95 99 | p=72,40 v=-83,32 100 | p=10,9 v=-83,47 101 | p=3,23 v=-10,-73 102 | p=84,1 v=85,52 103 | p=52,24 v=-18,36 104 | p=76,37 v=21,-84 105 | p=49,26 v=20,11 106 | p=66,54 v=30,-4 107 | p=24,66 v=-61,79 108 | p=34,22 v=41,-90 109 | p=43,6 v=-52,-66 110 | p=15,30 v=68,6 111 | p=72,74 v=-55,93 112 | p=35,25 v=24,-66 113 | p=80,21 v=56,36 114 | p=43,67 v=59,95 115 | p=61,39 v=-69,-88 116 | p=61,93 v=-66,65 117 | p=18,94 v=97,74 118 | p=4,43 v=13,-75 119 | p=13,62 v=-74,-91 120 | p=61,27 v=32,-59 121 | p=28,88 v=55,-19 122 | p=52,55 v=-94,-60 123 | p=83,37 v=46,97 124 | p=2,72 v=-52,-77 125 | p=35,31 v=-33,23 126 | p=75,73 v=30,-12 127 | p=55,7 v=87,-70 128 | p=48,52 v=-41,-22 129 | p=50,15 v=39,-54 130 | p=35,69 v=63,-92 131 | p=40,55 v=-20,-84 132 | p=51,27 v=61,-67 133 | p=24,29 v=62,21 134 | p=12,14 v=86,45 135 | p=57,29 v=-99,-66 136 | p=82,74 v=16,26 137 | p=2,40 v=-32,-69 138 | p=23,14 v=-81,49 139 | p=89,70 v=-32,99 140 | p=4,27 v=60,-67 141 | p=34,10 v=-77,-57 142 | p=40,73 v=88,73 143 | p=26,38 v=-90,21 144 | p=31,25 v=45,15 145 | p=93,8 v=-65,54 146 | p=61,25 v=-69,-79 147 | p=13,63 v=19,27 148 | p=58,13 v=15,-42 149 | p=29,1 v=3,65 150 | p=65,59 v=41,15 151 | p=86,69 v=-89,-14 152 | p=23,63 v=-87,-79 153 | p=14,7 v=81,-54 154 | p=3,92 v=-96,61 155 | p=78,24 v=-34,-70 156 | p=48,30 v=-22,10 157 | p=59,79 v=85,66 158 | p=91,7 v=5,-60 159 | p=61,25 v=-20,41 160 | p=5,90 v=60,87 161 | p=68,9 v=-24,25 162 | p=27,55 v=42,9 163 | p=48,41 v=37,-78 164 | p=43,44 v=80,-68 165 | p=73,20 v=30,-49 166 | p=13,62 v=-19,-3 167 | p=46,11 v=24,50 168 | p=17,90 v=86,75 169 | p=77,18 v=-84,79 170 | p=5,52 v=4,-30 171 | p=28,63 v=34,41 172 | p=72,83 v=87,-15 173 | p=11,16 v=51,-60 174 | p=4,33 v=-63,-71 175 | p=70,77 v=34,-25 176 | p=37,46 v=22,-20 177 | p=87,49 v=-25,2 178 | p=89,16 v=45,-59 179 | p=9,82 v=-33,97 180 | p=66,27 v=87,-72 181 | p=64,97 v=-38,-40 182 | p=51,90 v=-73,76 183 | p=25,57 v=-68,-95 184 | p=41,13 v=8,-48 185 | p=39,13 v=59,29 186 | p=42,51 v=13,9 187 | p=70,102 v=-14,-54 188 | p=41,69 v=2,83 189 | p=74,7 v=-10,39 190 | p=91,63 v=32,-99 191 | p=6,22 v=-70,-55 192 | p=78,5 v=-12,37 193 | p=54,76 v=-46,-84 194 | p=17,19 v=-4,-62 195 | p=70,101 v=76,58 196 | p=71,94 v=41,74 197 | p=16,91 v=18,-32 198 | p=10,90 v=95,-53 199 | p=97,61 v=71,5 200 | p=63,49 v=-73,-70 201 | p=85,27 v=-76,-56 202 | p=98,11 v=47,47 203 | p=58,7 v=-5,46 204 | p=5,48 v=-6,-94 205 | p=4,90 v=-87,-16 206 | p=55,84 v=-3,-29 207 | p=74,1 v=95,-35 208 | p=3,43 v=35,28 209 | p=11,29 v=91,40 210 | p=10,11 v=38,31 211 | p=61,44 v=-19,10 212 | p=87,52 v=76,96 213 | p=3,92 v=27,67 214 | p=68,64 v=87,-6 215 | p=74,88 v=87,-34 216 | p=24,52 v=43,-61 217 | p=91,10 v=-65,-63 218 | p=65,98 v=49,-21 219 | p=88,66 v=-71,-46 220 | p=21,84 v=-26,93 221 | p=7,69 v=84,81 222 | p=43,21 v=6,-9 223 | p=43,64 v=-73,-19 224 | p=35,47 v=79,-88 225 | p=54,21 v=39,-62 226 | p=20,63 v=-33,27 227 | p=21,51 v=99,10 228 | p=16,82 v=-72,-31 229 | p=23,10 v=-2,-45 230 | p=37,33 v=-22,21 231 | p=41,74 v=-9,88 232 | p=31,96 v=-15,65 233 | p=37,12 v=46,55 234 | p=72,54 v=-54,-52 235 | p=9,58 v=-28,-87 236 | p=0,13 v=-96,-50 237 | p=65,70 v=18,11 238 | p=32,38 v=90,-84 239 | p=97,67 v=40,-3 240 | p=45,17 v=90,57 241 | p=36,85 v=2,83 242 | p=29,39 v=63,78 243 | p=51,99 v=-7,-40 244 | p=18,93 v=18,-30 245 | p=75,93 v=-12,65 246 | p=1,85 v=27,-20 247 | p=17,84 v=97,72 248 | p=3,68 v=-74,96 249 | p=72,22 v=-49,52 250 | p=18,65 v=-50,1 251 | p=71,16 v=63,-59 252 | p=36,44 v=-20,85 253 | p=77,21 v=-27,96 254 | p=70,54 v=95,-70 255 | p=20,5 v=-44,-9 256 | p=97,64 v=55,94 257 | p=49,82 v=35,-16 258 | p=63,54 v=2,-51 259 | p=88,101 v=1,65 260 | p=1,3 v=-89,-34 261 | p=29,97 v=-24,-42 262 | p=45,46 v=-66,14 263 | p=51,33 v=68,35 264 | p=91,29 v=-78,-35 265 | p=10,56 v=-83,31 266 | p=11,94 v=-41,-15 267 | p=63,76 v=19,90 268 | p=84,84 v=21,84 269 | p=24,41 v=-79,25 270 | p=94,82 v=23,85 271 | p=60,52 v=-48,-4 272 | p=47,36 v=66,-51 273 | p=0,49 v=-6,-90 274 | p=40,23 v=-99,-63 275 | p=21,79 v=-64,-40 276 | p=71,18 v=-14,-67 277 | p=53,44 v=-51,7 278 | p=71,40 v=83,51 279 | p=96,87 v=93,-29 280 | p=46,64 v=22,88 281 | p=3,53 v=44,-52 282 | p=54,57 v=74,-95 283 | p=61,63 v=-95,-12 284 | p=96,82 v=3,-17 285 | p=41,96 v=5,89 286 | p=7,46 v=-19,20 287 | p=2,7 v=55,33 288 | p=27,15 v=29,33 289 | p=54,64 v=-7,2 290 | p=84,58 v=-93,59 291 | p=80,24 v=56,5 292 | p=23,13 v=66,-56 293 | p=93,100 v=-89,45 294 | p=89,82 v=91,69 295 | p=55,90 v=96,-5 296 | p=43,41 v=16,-93 297 | p=54,70 v=28,-85 298 | p=50,64 v=-33,-22 299 | p=99,35 v=71,17 300 | p=23,77 v=11,-3 301 | p=78,99 v=43,-38 302 | p=3,80 v=-57,8 303 | p=22,75 v=-48,-18 304 | p=3,94 v=-98,-33 305 | p=72,89 v=-90,39 306 | p=87,84 v=67,61 307 | p=28,0 v=22,55 308 | p=65,55 v=82,70 309 | p=55,48 v=17,-96 310 | p=11,37 v=-33,52 311 | p=63,63 v=-94,9 312 | p=0,10 v=-78,59 313 | p=91,17 v=23,-58 314 | p=56,77 v=72,81 315 | p=99,47 v=14,19 316 | p=15,18 v=31,24 317 | p=47,9 v=56,-13 318 | p=38,11 v=-9,-50 319 | p=93,2 v=-76,47 320 | p=97,52 v=-45,-32 321 | p=51,44 v=-65,68 322 | p=15,2 v=-83,72 323 | p=15,99 v=97,54 324 | p=85,81 v=45,78 325 | p=11,57 v=3,-13 326 | p=91,30 v=11,56 327 | p=42,90 v=-75,73 328 | p=24,7 v=99,-68 329 | p=55,58 v=83,-7 330 | p=35,4 v=15,31 331 | p=21,39 v=42,27 332 | p=62,9 v=6,-48 333 | p=59,99 v=-20,46 334 | p=93,71 v=1,96 335 | p=31,3 v=77,-47 336 | p=98,11 v=25,-52 337 | p=28,41 v=92,22 338 | p=90,64 v=93,81 339 | p=64,93 v=-71,-28 340 | p=31,65 v=-74,-47 341 | p=62,44 v=21,30 342 | p=35,22 v=-76,5 343 | p=69,2 v=-4,47 344 | p=34,28 v=52,32 345 | p=3,37 v=-17,21 346 | p=43,92 v=90,-50 347 | p=16,59 v=29,-1 348 | p=41,6 v=57,-50 349 | p=93,58 v=-74,-16 350 | p=88,60 v=-2,-10 351 | p=62,19 v=-51,37 352 | p=30,80 v=79,83 353 | p=75,14 v=-84,79 354 | p=77,10 v=-34,-71 355 | p=92,60 v=21,13 356 | p=21,25 v=-26,-67 357 | p=88,101 v=57,80 358 | p=60,93 v=14,-99 359 | p=55,33 v=61,-77 360 | p=76,51 v=-60,67 361 | p=23,31 v=-48,25 362 | p=47,28 v=71,18 363 | p=6,10 v=-6,55 364 | p=74,29 v=-51,-56 365 | p=43,49 v=-66,15 366 | p=53,15 v=17,46 367 | p=34,89 v=-90,-28 368 | p=53,12 v=26,-53 369 | p=68,96 v=32,68 370 | p=10,50 v=-6,5 371 | p=30,7 v=69,80 372 | p=2,69 v=60,84 373 | p=78,74 v=32,-12 374 | p=83,87 v=-8,-31 375 | p=86,86 v=48,-45 376 | p=19,6 v=7,-48 377 | p=53,32 v=-55,-89 378 | p=32,35 v=-23,-39 379 | p=95,23 v=-87,43 380 | p=56,82 v=50,-22 381 | p=28,101 v=99,-36 382 | p=8,77 v=5,-13 383 | p=74,21 v=-27,-90 384 | p=68,59 v=98,-6 385 | p=83,93 v=21,76 386 | p=80,24 v=-91,-78 387 | p=7,78 v=47,-8 388 | p=19,81 v=90,-14 389 | p=96,20 v=-30,44 390 | p=70,54 v=-14,10 391 | p=93,25 v=-65,33 392 | p=51,76 v=26,90 393 | p=79,25 v=45,-69 394 | p=89,6 v=-47,-54 395 | p=32,78 v=62,18 396 | p=98,32 v=57,23 397 | p=34,30 v=-22,-91 398 | p=37,94 v=-72,86 399 | p=81,94 v=-45,63 400 | p=20,37 v=-24,29 401 | p=2,63 v=-26,-19 402 | p=4,29 v=-72,-68 403 | p=18,95 v=45,71 404 | p=64,84 v=28,-25 405 | p=8,32 v=84,-93 406 | p=15,3 v=-6,-41 407 | p=42,53 v=81,-92 408 | p=20,73 v=79,72 409 | p=43,3 v=28,-38 410 | p=86,85 v=-21,72 411 | p=97,29 v=69,36 412 | p=23,74 v=-55,72 413 | p=83,87 v=45,-26 414 | p=70,0 v=-93,57 415 | p=61,0 v=-25,74 416 | p=22,0 v=-35,56 417 | p=12,8 v=-70,34 418 | p=27,24 v=66,-74 419 | p=27,87 v=29,78 420 | p=36,98 v=68,72 421 | p=26,40 v=40,-82 422 | p=38,34 v=-80,6 423 | p=34,83 v=33,74 424 | p=23,24 v=9,27 425 | p=46,96 v=96,-43 426 | p=80,41 v=52,19 427 | p=33,10 v=37,-47 428 | p=47,15 v=74,73 429 | p=25,102 v=-3,-72 430 | p=73,63 v=-71,-1 431 | p=27,78 v=-2,83 432 | p=28,8 v=18,55 433 | p=95,102 v=-39,-42 434 | p=65,69 v=-38,-16 435 | p=54,17 v=-18,48 436 | p=63,64 v=96,96 437 | p=3,32 v=-37,33 438 | p=96,84 v=36,61 439 | p=67,9 v=67,-54 440 | p=88,54 v=-74,-52 441 | p=70,52 v=52,14 442 | p=97,81 v=-55,-56 443 | p=4,11 v=40,54 444 | p=51,9 v=-16,-97 445 | p=59,88 v=85,75 446 | p=58,24 v=72,-67 447 | p=73,59 v=76,-93 448 | p=34,40 v=81,2 449 | p=38,22 v=94,52 450 | p=77,27 v=78,39 451 | p=65,50 v=28,16 452 | p=63,14 v=-38,-48 453 | p=13,70 v=-24,75 454 | p=60,16 v=12,-9 455 | p=43,65 v=92,50 456 | p=66,31 v=-24,-65 457 | p=67,32 v=10,4 458 | p=20,27 v=-27,97 459 | p=87,35 v=-52,38 460 | p=69,86 v=80,-41 461 | p=55,81 v=4,-15 462 | p=11,102 v=-28,54 463 | p=34,74 v=52,-16 464 | p=74,90 v=19,77 465 | p=100,71 v=-63,-17 466 | p=16,27 v=-52,30 467 | p=77,75 v=-59,-50 468 | p=18,76 v=27,81 469 | p=65,99 v=42,-45 470 | p=30,15 v=-79,48 471 | p=4,95 v=80,-31 472 | p=81,87 v=83,15 473 | p=22,31 v=33,34 474 | p=46,77 v=-43,-42 475 | p=36,37 v=-99,27 476 | p=35,86 v=29,20 477 | p=54,16 v=-9,-77 478 | p=58,5 v=-38,-34 479 | p=40,26 v=4,-53 480 | p=31,36 v=56,-5 481 | p=46,101 v=-5,97 482 | p=99,97 v=47,88 483 | p=52,42 v=94,-81 484 | p=92,100 v=95,65 485 | p=86,19 v=-78,-48 486 | p=75,100 v=-23,63 487 | p=74,38 v=98,-99 488 | p=61,92 v=-42,-40 489 | p=70,98 v=8,63 490 | p=4,82 v=-74,-32 491 | p=79,1 v=63,-33 492 | p=5,49 v=-17,11 493 | p=55,39 v=-82,-68 494 | p=91,98 v=-96,64 495 | p=55,82 v=24,77 496 | p=54,48 v=83,13 497 | p=0,23 v=-19,-74 498 | p=25,50 v=48,36 499 | p=99,21 v=36,-68 500 | p=26,33 v=9,9 501 | -------------------------------------------------------------------------------- /day14/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import math 6 | import os.path 7 | import re 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | PAT = re.compile(r'^p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)$') 16 | 17 | 18 | def compute(s: str, *, n: int = 100, w: int = 101, h: int = 103) -> int: 19 | quads: collections.Counter[tuple[bool, bool]] = collections.Counter() 20 | 21 | w_h = w // 2 22 | h_h = h // 2 23 | 24 | for line in s.splitlines(): 25 | match = PAT.fullmatch(line) 26 | assert match is not None, line 27 | px, py = int(match[1]), int(match[2]) 28 | vx, vy = int(match[3]), int(match[4]) 29 | 30 | x = (px + vx * n) % w 31 | y = (py + vy * n) % h 32 | if x == w_h or y == h_h: 33 | continue 34 | 35 | quads[(x > w_h, y > h_h)] += 1 36 | 37 | return math.prod(quads.values()) 38 | 39 | 40 | INPUT_S = '''\ 41 | p=0,4 v=3,-3 42 | p=6,3 v=-1,-3 43 | p=10,3 v=-1,2 44 | p=2,0 v=2,-1 45 | p=0,0 v=1,3 46 | p=3,0 v=-2,-2 47 | p=7,6 v=-1,-3 48 | p=3,0 v=-1,-2 49 | p=9,3 v=2,3 50 | p=7,3 v=-1,2 51 | p=2,4 v=2,-3 52 | p=9,5 v=-3,-3 53 | ''' 54 | EXPECTED = 12 55 | 56 | 57 | @pytest.mark.parametrize( 58 | ('input_s', 'expected'), 59 | ( 60 | (INPUT_S, EXPECTED), 61 | ), 62 | ) 63 | def test(input_s: str, expected: int) -> None: 64 | assert compute(input_s, w=11, h=7) == expected 65 | 66 | 67 | def main() -> int: 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 70 | args = parser.parse_args() 71 | 72 | with open(args.data_file) as f, support.timing(): 73 | print(compute(f.read())) 74 | 75 | return 0 76 | 77 | 78 | if __name__ == '__main__': 79 | raise SystemExit(main()) 80 | -------------------------------------------------------------------------------- /day14/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import itertools 5 | import os.path 6 | import re 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | PAT = re.compile(r'^p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)$') 13 | 14 | 15 | def compute(s: str, *, w: int = 101, h: int = 103) -> int: 16 | bots = [] 17 | 18 | for line in s.splitlines(): 19 | match = PAT.fullmatch(line) 20 | assert match is not None, line 21 | px, py = int(match[1]), int(match[2]) 22 | vx, vy = int(match[3]), int(match[4]) 23 | bots.append(((px, py), (vx, vy))) 24 | 25 | for n in itertools.count(): 26 | pts = set() 27 | 28 | for (px, py), (vx, vy) in bots: 29 | x = (px + vx * n) % w 30 | y = (py + vy * n) % h 31 | pts.add((x, y)) 32 | 33 | for midp in range(1, w - 2): 34 | sym_h_count = 0 35 | for cx, cy in pts: 36 | newx = midp - (cx - midp) 37 | sym_h_count += (newx, cy) in pts 38 | 39 | if sym_h_count > len(pts) / 2: 40 | return n 41 | 42 | raise AssertionError('unreachable') 43 | 44 | 45 | def main() -> int: 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 48 | args = parser.parse_args() 49 | 50 | with open(args.data_file) as f, support.timing(): 51 | print(compute(f.read())) 52 | 53 | return 0 54 | 55 | 56 | if __name__ == '__main__': 57 | raise SystemExit(main()) 58 | -------------------------------------------------------------------------------- /day15/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day15/__init__.py -------------------------------------------------------------------------------- /day15/i2.txt: -------------------------------------------------------------------------------- 1 | ####### 2 | #...#.# 3 | #.....# 4 | #.@OO.# 5 | #..O..# 6 | #.....# 7 | ####### 8 | 9 | >>>>> 10 | -------------------------------------------------------------------------------- /day15/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | map_s, moves_s = s.split('\n\n') 15 | walls = support.parse_coords_hash(map_s) 16 | boxes = support.parse_coords_hash(map_s, wall='O') 17 | bot, = support.parse_coords_hash(map_s, wall='@') 18 | 19 | moves_s = ''.join(moves_s.split()) 20 | for c in moves_s: 21 | d = support.Direction4.from_c(c) 22 | 23 | boxes_to_move = set() 24 | pos = d.apply(*bot) 25 | while pos in boxes: 26 | boxes_to_move.add(pos) 27 | pos = d.apply(*pos) 28 | 29 | if pos in walls: 30 | continue 31 | 32 | bot = d.apply(*bot) 33 | boxes -= boxes_to_move 34 | boxes |= {d.apply(*box) for box in boxes_to_move} 35 | 36 | return sum(100 * y + x for x, y in boxes) 37 | 38 | 39 | INPUT_S = '''\ 40 | ########## 41 | #..O..O.O# 42 | #......O.# 43 | #.OO..O.O# 44 | #..O@..O.# 45 | #O#..O...# 46 | #O..O..O.# 47 | #.OO.O.OO# 48 | #....O...# 49 | ########## 50 | 51 | ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ 52 | vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< 54 | <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ 55 | ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< 56 | ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ 58 | <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> 59 | ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< 60 | v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ 61 | ''' 62 | EXPECTED = 10092 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ('input_s', 'expected'), 67 | ( 68 | (INPUT_S, EXPECTED), 69 | ), 70 | ) 71 | def test(input_s: str, expected: int) -> None: 72 | assert compute(input_s) == expected 73 | 74 | 75 | def main() -> int: 76 | parser = argparse.ArgumentParser() 77 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 78 | args = parser.parse_args() 79 | 80 | with open(args.data_file) as f, support.timing(): 81 | print(compute(f.read())) 82 | 83 | return 0 84 | 85 | 86 | if __name__ == '__main__': 87 | raise SystemExit(main()) 88 | -------------------------------------------------------------------------------- /day15/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | map_s, moves_s = s.split('\n\n') 15 | map_s = map_s.translate({ 16 | ord('#'): '##', 17 | ord('.'): '..', 18 | ord('O'): '[]', 19 | ord('@'): '@.', 20 | }) 21 | walls = support.parse_coords_hash(map_s) 22 | boxes = support.parse_coords_hash(map_s, wall='[') 23 | bot, = support.parse_coords_hash(map_s, wall='@') 24 | 25 | def _boxes_to_move(d: support.Direction4) -> set[tuple[int, int]]: 26 | boxes_to_move: set[tuple[int, int]] = set() 27 | if d is support.Direction4.LEFT: 28 | pos = d.apply(*bot) 29 | if pos in walls: 30 | return boxes_to_move 31 | pos = d.apply(*pos) 32 | while pos in boxes: 33 | boxes_to_move.add(pos) 34 | pos = d.apply(*pos) 35 | if pos in walls: 36 | return boxes_to_move 37 | pos = d.apply(*pos) 38 | return boxes_to_move 39 | elif d is support.Direction4.RIGHT: 40 | pos = d.apply(*bot) 41 | while pos in boxes: 42 | boxes_to_move.add(pos) 43 | pos = d.apply(*d.apply(*pos)) 44 | return boxes_to_move 45 | elif d is support.Direction4.UP or d is support.Direction4.DOWN: 46 | pos1 = d.apply(*bot) 47 | pos2 = support.Direction4.LEFT.apply(*pos1) 48 | layer = {pos1, pos2} & boxes 49 | while layer: 50 | boxes_to_move.update(layer) 51 | newlayer: set[tuple[int, int]] = set() 52 | for box in layer: 53 | pos1 = d.apply(*box) 54 | pos2 = support.Direction4.LEFT.apply(*pos1) 55 | pos3 = support.Direction4.RIGHT.apply(*pos1) 56 | newlayer.update((pos1, pos2, pos3)) 57 | layer = newlayer & boxes 58 | return boxes_to_move 59 | else: 60 | raise AssertionError('unreachable') 61 | 62 | def _is_box_blocked(box: tuple[int, int], d: support.Direction4) -> bool: 63 | if d is support.Direction4.LEFT: 64 | return d.apply(*box) in walls 65 | elif d is support.Direction4.RIGHT: 66 | return d.apply(*d.apply(*box)) in walls 67 | elif d is support.Direction4.UP or d is support.Direction4.DOWN: 68 | return ( 69 | d.apply(*box) in walls or 70 | support.Direction4.RIGHT.apply(*d.apply(*box)) in walls 71 | ) 72 | else: 73 | raise AssertionError('unreachable') 74 | 75 | moves_s = ''.join(moves_s.split()) 76 | for c in moves_s: 77 | d = support.Direction4.from_c(c) 78 | 79 | if d.apply(*bot) in walls: 80 | continue 81 | 82 | boxes_to_move = _boxes_to_move(d) 83 | 84 | if any(_is_box_blocked(box, d) for box in boxes_to_move): 85 | continue 86 | 87 | bot = d.apply(*bot) 88 | boxes -= boxes_to_move 89 | boxes |= {d.apply(*box) for box in boxes_to_move} 90 | 91 | return sum(100 * y + x for x, y in boxes) 92 | 93 | 94 | INPUT_S = '''\ 95 | ########## 96 | #..O..O.O# 97 | #......O.# 98 | #.OO..O.O# 99 | #..O@..O.# 100 | #O#..O...# 101 | #O..O..O.# 102 | #.OO.O.OO# 103 | #....O...# 104 | ########## 105 | 106 | ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ 107 | vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< 109 | <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ 110 | ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< 111 | ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ 113 | <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> 114 | ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< 115 | v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ 116 | ''' 117 | EXPECTED = 9021 118 | 119 | 120 | @pytest.mark.parametrize( 121 | ('input_s', 'expected'), 122 | ( 123 | (INPUT_S, EXPECTED), 124 | ), 125 | ) 126 | def test(input_s: str, expected: int) -> None: 127 | assert compute(input_s) == expected 128 | 129 | 130 | def main() -> int: 131 | parser = argparse.ArgumentParser() 132 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 133 | args = parser.parse_args() 134 | 135 | with open(args.data_file) as f, support.timing(): 136 | print(compute(f.read())) 137 | 138 | return 0 139 | 140 | 141 | if __name__ == '__main__': 142 | raise SystemExit(main()) 143 | -------------------------------------------------------------------------------- /day16/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day16/__init__.py -------------------------------------------------------------------------------- /day16/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import heapq 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | world = support.parse_coords_hash(s) 16 | start, = support.parse_coords_hash(s, wall='S') 17 | end, = support.parse_coords_hash(s, wall='E') 18 | 19 | seen = set() 20 | todo = [(0, start, support.Direction4.RIGHT)] 21 | 22 | def _do(score: int, pos: tuple[int, int], d: support.Direction4) -> None: 23 | if (pos, d) not in seen: 24 | heapq.heappush(todo, (score, pos, d)) 25 | 26 | while todo: 27 | score, pos, direction = heapq.heappop(todo) 28 | 29 | if pos == end: 30 | return score 31 | else: 32 | seen.add((pos, direction)) 33 | 34 | # can we move forward? 35 | cand = direction.apply(*pos) 36 | if cand not in world: 37 | _do(score + 1, cand, direction) 38 | 39 | cand = direction.cw.apply(*pos) 40 | if cand not in world: 41 | _do(score + 1001, cand, direction.cw) 42 | 43 | cand = direction.ccw.apply(*pos) 44 | if cand not in world: 45 | _do(score + 1001, cand, direction.ccw) 46 | 47 | raise AssertionError('unreachable') 48 | 49 | 50 | INPUT_S = '''\ 51 | ############### 52 | #.......#....E# 53 | #.#.###.#.###.# 54 | #.....#.#...#.# 55 | #.###.#####.#.# 56 | #.#.#.......#.# 57 | #.#.#####.###.# 58 | #...........#.# 59 | ###.#.#####.#.# 60 | #...#.....#.#.# 61 | #.#.#.###.#.#.# 62 | #.....#...#.#.# 63 | #.###.#.#.#.#.# 64 | #S..#.....#...# 65 | ############### 66 | ''' 67 | EXPECTED = 7036 68 | 69 | 70 | @pytest.mark.parametrize( 71 | ('input_s', 'expected'), 72 | ( 73 | (INPUT_S, EXPECTED), 74 | ), 75 | ) 76 | def test(input_s: str, expected: int) -> None: 77 | assert compute(input_s) == expected 78 | 79 | 80 | def main() -> int: 81 | parser = argparse.ArgumentParser() 82 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 83 | args = parser.parse_args() 84 | 85 | with open(args.data_file) as f, support.timing(): 86 | print(compute(f.read())) 87 | 88 | return 0 89 | 90 | 91 | if __name__ == '__main__': 92 | raise SystemExit(main()) 93 | -------------------------------------------------------------------------------- /day16/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import heapq 5 | import os.path 6 | import sys 7 | from typing import NamedTuple 8 | from typing import Self 9 | 10 | import pytest 11 | 12 | import support 13 | 14 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 15 | 16 | 17 | def _best_score( 18 | world: set[tuple[int, int]], 19 | start: tuple[int, int], 20 | end: tuple[int, int], 21 | ) -> int: 22 | seen = set() 23 | todo = [(0, start, support.Direction4.RIGHT)] 24 | 25 | def _do(score: int, pos: tuple[int, int], d: support.Direction4) -> None: 26 | if (pos, d) not in seen: 27 | heapq.heappush(todo, (score, pos, d)) 28 | 29 | while True: 30 | score, pos, direction = heapq.heappop(todo) 31 | 32 | if pos == end: 33 | return score 34 | else: 35 | seen.add((pos, direction)) 36 | 37 | # can we move forward? 38 | cand = direction.apply(*pos) 39 | if cand not in world: 40 | _do(score + 1, cand, direction) 41 | 42 | # can we move turn and move forward? 43 | cand = direction.cw.apply(*pos) 44 | if cand not in world: 45 | _do(score + 1001, cand, direction.cw) 46 | 47 | # can we turn left and move forward? 48 | cand = direction.ccw.apply(*pos) 49 | if cand not in world: 50 | _do(score + 1001, cand, direction.ccw) 51 | 52 | 53 | class State(NamedTuple): 54 | score: int 55 | pos: tuple[int, int] 56 | direction: support.Direction4 57 | positions_seen: frozenset[tuple[int, int]] 58 | 59 | @classmethod 60 | def initial(cls, pos: tuple[int, int]) -> Self: 61 | return cls( 62 | score=0, 63 | pos=pos, 64 | direction=support.Direction4.RIGHT, 65 | positions_seen=frozenset((pos,)), 66 | ) 67 | 68 | def next_state( 69 | self, 70 | score: int, 71 | pos: tuple[int, int], 72 | d: support.Direction4, 73 | ) -> Self | None: 74 | if pos in self.positions_seen: 75 | return None 76 | 77 | return type(self)( 78 | score=score, 79 | pos=pos, 80 | direction=d, 81 | positions_seen=self.positions_seen | {pos}, 82 | ) 83 | 84 | 85 | def compute(s: str) -> int: 86 | world = support.parse_coords_hash(s) 87 | start, = support.parse_coords_hash(s, wall='S') 88 | end, = support.parse_coords_hash(s, wall='E') 89 | 90 | best_score = _best_score(world, start, end) 91 | 92 | bests: dict[tuple[int, int], int] = {} 93 | sits: set[tuple[int, int]] = set() 94 | paths = [State.initial(start)] 95 | 96 | def _do( 97 | score: int, 98 | pos: tuple[int, int], 99 | d: support.Direction4, 100 | state: State, 101 | ) -> None: 102 | best_at = bests.get(pos, sys.maxsize) 103 | if score > best_at + 1000 or score > best_score: 104 | return 105 | else: 106 | bests[pos] = min(score, best_at) 107 | 108 | new_state = state.next_state(score, pos, d) 109 | if new_state is not None: 110 | paths.append(new_state) 111 | 112 | while paths: 113 | state = paths.pop() 114 | pos = state.pos 115 | direction = state.direction 116 | 117 | if pos == end and state.score == best_score: 118 | sits.update(state.positions_seen) 119 | continue 120 | 121 | # can we move forward? 122 | cand = direction.apply(*pos) 123 | if cand not in world: 124 | _do(state.score + 1, cand, direction, state) 125 | 126 | # can we turn right and move? 127 | cand = direction.cw.apply(*pos) 128 | if cand not in world: 129 | _do(state.score + 1001, cand, direction.cw, state) 130 | 131 | # can we turn left and move? 132 | cand = direction.ccw.apply(*pos) 133 | if cand not in world: 134 | _do(state.score + 1001, cand, direction.ccw, state) 135 | 136 | return len(sits) 137 | 138 | 139 | INPUT_S = '''\ 140 | ############### 141 | #.......#....E# 142 | #.#.###.#.###.# 143 | #.....#.#...#.# 144 | #.###.#####.#.# 145 | #.#.#.......#.# 146 | #.#.#####.###.# 147 | #...........#.# 148 | ###.#.#####.#.# 149 | #...#.....#.#.# 150 | #.#.#.###.#.#.# 151 | #.....#...#.#.# 152 | #.###.#.#.#.#.# 153 | #S..#.....#...# 154 | ############### 155 | ''' 156 | EXPECTED = 45 157 | 158 | 159 | @pytest.mark.parametrize( 160 | ('input_s', 'expected'), 161 | ( 162 | (INPUT_S, EXPECTED), 163 | ), 164 | ) 165 | def test(input_s: str, expected: int) -> None: 166 | assert compute(input_s) == expected 167 | 168 | 169 | def main() -> int: 170 | parser = argparse.ArgumentParser() 171 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 172 | args = parser.parse_args() 173 | 174 | with open(args.data_file) as f, support.timing(): 175 | print(compute(f.read())) 176 | 177 | return 0 178 | 179 | 180 | if __name__ == '__main__': 181 | raise SystemExit(main()) 182 | -------------------------------------------------------------------------------- /day17/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day17/__init__.py -------------------------------------------------------------------------------- /day17/input.txt: -------------------------------------------------------------------------------- 1 | Register A: 28066687 2 | Register B: 0 3 | Register C: 0 4 | 5 | Program: 2,4,1,1,7,5,4,6,0,3,1,4,5,5,3,0 6 | -------------------------------------------------------------------------------- /day17/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> str: 14 | regs_s, prog_s = s.replace(':', '').split('\n\n') 15 | regs = {} 16 | for line in regs_s.splitlines(): 17 | _, name, val_s = line.split() 18 | regs[name] = int(val_s) 19 | 20 | def _val(i: int) -> int: 21 | if 0 <= i <= 3: 22 | return i 23 | elif i == 7: 24 | raise ValueError(f'unexpected {i=}') 25 | else: 26 | return regs[chr(ord('A') + i - 4)] 27 | 28 | out = [] 29 | prog = support.parse_numbers_comma(prog_s.split()[1]) 30 | pc = 0 31 | while pc < len(prog): 32 | op = prog[pc] 33 | operand = prog[pc + 1] 34 | if op == 0: 35 | regs['A'] = regs['A'] // (2 ** _val(operand)) 36 | pc += 2 37 | elif op == 6: 38 | regs['B'] = regs['A'] // (2 ** _val(operand)) 39 | pc += 2 40 | elif op == 7: 41 | regs['C'] = regs['A'] // (2 ** _val(operand)) 42 | pc += 2 43 | elif op == 1: 44 | regs['B'] = regs['B'] ^ operand 45 | pc += 2 46 | elif op == 2: 47 | regs['B'] = _val(operand) % 8 48 | pc += 2 49 | elif op == 3: 50 | if regs['A'] == 0: 51 | pc += 2 52 | else: 53 | pc = operand 54 | elif op == 4: 55 | regs['B'] ^= regs['C'] 56 | pc += 2 57 | elif op == 5: 58 | out.append(_val(operand) % 8) 59 | pc += 2 60 | 61 | return ','.join(str(n) for n in out) 62 | 63 | 64 | INPUT_S = '''\ 65 | Register A: 729 66 | Register B: 0 67 | Register C: 0 68 | 69 | Program: 0,1,5,4,3,0 70 | ''' 71 | EXPECTED = '4,6,3,5,6,3,5,2,1,0' 72 | 73 | 74 | @pytest.mark.parametrize( 75 | ('input_s', 'expected'), 76 | ( 77 | (INPUT_S, EXPECTED), 78 | ), 79 | ) 80 | def test(input_s: str, expected: str) -> None: 81 | assert compute(input_s) == expected 82 | 83 | 84 | def main() -> int: 85 | parser = argparse.ArgumentParser() 86 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 87 | args = parser.parse_args() 88 | 89 | with open(args.data_file) as f, support.timing(): 90 | print(compute(f.read())) 91 | 92 | return 0 93 | 94 | 95 | if __name__ == '__main__': 96 | raise SystemExit(main()) 97 | -------------------------------------------------------------------------------- /day17/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import z3 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | _, prog_s = s.replace(':', '').split('\n\n') 15 | 16 | prog = support.parse_numbers_comma(prog_s.split()[1]) 17 | 18 | o = z3.Optimize() 19 | orig_a = a = z3.BitVec('A', 64) 20 | 21 | for n in prog: 22 | b = a % 8 23 | b = b ^ 1 24 | c = a >> b 25 | b = b ^ c 26 | a = a >> 3 27 | b = b ^ 4 28 | o.add(b % 8 == n) 29 | o.add(a == 0) 30 | 31 | o.minimize(orig_a) 32 | 33 | assert o.check() == z3.sat 34 | return o.model()[orig_a].as_long() 35 | 36 | 37 | def main() -> int: 38 | parser = argparse.ArgumentParser() 39 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 40 | args = parser.parse_args() 41 | 42 | with open(args.data_file) as f, support.timing(): 43 | print(compute(f.read())) 44 | 45 | return 0 46 | 47 | 48 | if __name__ == '__main__': 49 | raise SystemExit(main()) 50 | -------------------------------------------------------------------------------- /day18/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day18/__init__.py -------------------------------------------------------------------------------- /day18/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | import sys 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def compute(s: str, *, size: int = 71, n: int = 1024) -> int: 16 | world = set() 17 | for line in s.splitlines()[:n]: 18 | x, y = support.parse_numbers_comma(line) 19 | world.add((x, y)) 20 | 21 | best: dict[tuple[int, int], int] = {} 22 | 23 | todo: collections.deque[tuple[tuple[int, int], frozenset[tuple[int, int]]]] 24 | todo = collections.deque([((0, 0), frozenset(((0, 0),)))]) 25 | while todo: 26 | pos, seen = todo.popleft() 27 | 28 | best_at = best.get(pos, sys.maxsize) 29 | if len(seen) >= best_at: 30 | continue 31 | else: 32 | best[pos] = len(seen) 33 | 34 | if pos == (size - 1, size - 1): 35 | return len(seen) - 1 36 | 37 | for cand in support.adjacent_4(*pos): 38 | cx, cy = cand 39 | if ( 40 | 0 <= cx < size and 41 | 0 <= cy < size and 42 | cand not in world and 43 | cand not in seen 44 | ): 45 | todo.append((cand, seen | {cand})) 46 | 47 | raise AssertionError('unreachable!') 48 | 49 | 50 | INPUT_S = '''\ 51 | 5,4 52 | 4,2 53 | 4,5 54 | 3,0 55 | 2,1 56 | 6,3 57 | 2,4 58 | 1,5 59 | 0,6 60 | 3,3 61 | 2,6 62 | 5,1 63 | 1,2 64 | 5,5 65 | 2,5 66 | 6,5 67 | 1,4 68 | 0,4 69 | 6,4 70 | 1,1 71 | 6,1 72 | 1,0 73 | 0,5 74 | 1,6 75 | 2,0 76 | ''' 77 | EXPECTED = 22 78 | 79 | 80 | @pytest.mark.parametrize( 81 | ('input_s', 'expected'), 82 | ( 83 | (INPUT_S, EXPECTED), 84 | ), 85 | ) 86 | def test(input_s: str, expected: int) -> None: 87 | assert compute(input_s, size=7, n=12) == expected 88 | 89 | 90 | def main() -> int: 91 | parser = argparse.ArgumentParser() 92 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 93 | args = parser.parse_args() 94 | 95 | with open(args.data_file) as f, support.timing(): 96 | print(compute(f.read())) 97 | 98 | return 0 99 | 100 | 101 | if __name__ == '__main__': 102 | raise SystemExit(main()) 103 | -------------------------------------------------------------------------------- /day18/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | import sys 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def _try(world: set[tuple[int, int]], size: int) -> bool: 16 | best: dict[tuple[int, int], int] = {} 17 | 18 | todo: collections.deque[tuple[tuple[int, int], frozenset[tuple[int, int]]]] 19 | todo = collections.deque([((0, 0), frozenset(((0, 0),)))]) 20 | while todo: 21 | pos, seen = todo.popleft() 22 | 23 | best_at = best.get(pos, sys.maxsize) 24 | if len(seen) >= best_at: 25 | continue 26 | else: 27 | best[pos] = len(seen) 28 | 29 | if pos == (size - 1, size - 1): 30 | return True 31 | 32 | for cand in support.adjacent_4(*pos): 33 | cx, cy = cand 34 | if ( 35 | 0 <= cx < size and 36 | 0 <= cy < size and 37 | cand not in world and 38 | cand not in seen 39 | ): 40 | todo.append((cand, seen | {cand})) 41 | 42 | return False 43 | 44 | 45 | def compute(s: str, *, size: int = 71, n: int = 1024) -> str: 46 | world = set() 47 | for line in s.splitlines()[:n]: 48 | x, y = support.parse_numbers_comma(line) 49 | world.add((x, y)) 50 | 51 | for line in s.splitlines()[n:]: 52 | x, y = support.parse_numbers_comma(line) 53 | world.add((x, y)) 54 | if not _try(world, size=size): 55 | return line 56 | 57 | raise AssertionError('unreachable!') 58 | 59 | 60 | INPUT_S = '''\ 61 | 5,4 62 | 4,2 63 | 4,5 64 | 3,0 65 | 2,1 66 | 6,3 67 | 2,4 68 | 1,5 69 | 0,6 70 | 3,3 71 | 2,6 72 | 5,1 73 | 1,2 74 | 5,5 75 | 2,5 76 | 6,5 77 | 1,4 78 | 0,4 79 | 6,4 80 | 1,1 81 | 6,1 82 | 1,0 83 | 0,5 84 | 1,6 85 | 2,0 86 | ''' 87 | EXPECTED = '6,1' 88 | 89 | 90 | @pytest.mark.parametrize( 91 | ('input_s', 'expected'), 92 | ( 93 | (INPUT_S, EXPECTED), 94 | ), 95 | ) 96 | def test(input_s: str, expected: str) -> None: 97 | assert compute(input_s, size=7, n=12) == expected 98 | 99 | 100 | def main() -> int: 101 | parser = argparse.ArgumentParser() 102 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 103 | args = parser.parse_args() 104 | 105 | with open(args.data_file) as f, support.timing(): 106 | print(compute(f.read())) 107 | 108 | return 0 109 | 110 | 111 | if __name__ == '__main__': 112 | raise SystemExit(main()) 113 | -------------------------------------------------------------------------------- /day19/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day19/__init__.py -------------------------------------------------------------------------------- /day19/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | patterns_s, targets = s.split('\n\n') 16 | possible = patterns_s.split(', ') 17 | 18 | @functools.cache 19 | def _possible(s: str) -> bool: 20 | if s == '': 21 | return True 22 | 23 | for cand in possible: 24 | if s.startswith(cand): 25 | if _possible(s.removeprefix(cand)): 26 | return True 27 | else: 28 | return False 29 | 30 | total = 0 31 | for line in targets.splitlines(): 32 | total += _possible(line) 33 | return total 34 | 35 | 36 | INPUT_S = '''\ 37 | r, wr, b, g, bwu, rb, gb, br 38 | 39 | brwrr 40 | bggr 41 | gbbr 42 | rrbgbr 43 | ubwu 44 | bwurrg 45 | brgr 46 | bbrgwb 47 | ''' 48 | EXPECTED = 6 49 | 50 | 51 | @pytest.mark.parametrize( 52 | ('input_s', 'expected'), 53 | ( 54 | (INPUT_S, EXPECTED), 55 | ), 56 | ) 57 | def test(input_s: str, expected: int) -> None: 58 | assert compute(input_s) == expected 59 | 60 | 61 | def main() -> int: 62 | parser = argparse.ArgumentParser() 63 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 64 | args = parser.parse_args() 65 | 66 | with open(args.data_file) as f, support.timing(): 67 | print(compute(f.read())) 68 | 69 | return 0 70 | 71 | 72 | if __name__ == '__main__': 73 | raise SystemExit(main()) 74 | -------------------------------------------------------------------------------- /day19/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | patterns_s, targets = s.split('\n\n') 16 | possible = patterns_s.split(', ') 17 | 18 | @functools.cache 19 | def _possible(s: str) -> int: 20 | if s == '': 21 | return 1 22 | 23 | ret = 0 24 | for cand in possible: 25 | if s.startswith(cand): 26 | ret += _possible(s.removeprefix(cand)) 27 | return ret 28 | 29 | total = 0 30 | for line in targets.splitlines(): 31 | total += _possible(line) 32 | return total 33 | 34 | 35 | INPUT_S = '''\ 36 | r, wr, b, g, bwu, rb, gb, br 37 | 38 | brwrr 39 | bggr 40 | gbbr 41 | rrbgbr 42 | ubwu 43 | bwurrg 44 | brgr 45 | bbrgwb 46 | ''' 47 | EXPECTED = 16 48 | 49 | 50 | @pytest.mark.parametrize( 51 | ('input_s', 'expected'), 52 | ( 53 | (INPUT_S, EXPECTED), 54 | ), 55 | ) 56 | def test(input_s: str, expected: int) -> None: 57 | assert compute(input_s) == expected 58 | 59 | 60 | def main() -> int: 61 | parser = argparse.ArgumentParser() 62 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 63 | args = parser.parse_args() 64 | 65 | with open(args.data_file) as f, support.timing(): 66 | print(compute(f.read())) 67 | 68 | return 0 69 | 70 | 71 | if __name__ == '__main__': 72 | raise SystemExit(main()) 73 | -------------------------------------------------------------------------------- /day20/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day20/__init__.py -------------------------------------------------------------------------------- /day20/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def _best_path( 15 | world: set[tuple[int, int]], 16 | start: tuple[int, int], 17 | end: tuple[int, int], 18 | ) -> dict[tuple[int, int], int]: 19 | seen = {start} 20 | todo: collections.deque[tuple[tuple[int, int], ...]] 21 | todo = collections.deque([(start,)]) 22 | while todo: 23 | path = todo.popleft() 24 | if path[-1] == end: 25 | break 26 | 27 | for cand in support.adjacent_4(*path[-1]): 28 | if cand not in seen and cand not in world: 29 | todo.append((*path, cand)) 30 | seen.add(cand) 31 | else: 32 | raise AssertionError('unreachable') 33 | 34 | return {pos: i for i, pos in enumerate(path)} 35 | 36 | 37 | def compute(s: str, *, at_least: int = 100) -> int: 38 | world = support.parse_coords_hash(s) 39 | start, = support.parse_coords_hash(s, wall='S') 40 | end, = support.parse_coords_hash(s, wall='E') 41 | 42 | best_at = _best_path(world, start, end) 43 | 44 | cheats: collections.Counter[int] = collections.Counter() 45 | 46 | seen = {start} 47 | todo: collections.deque[tuple[tuple[int, int], frozenset[tuple[int, int]]]] 48 | todo = collections.deque([(start, frozenset((start,)))]) 49 | while todo: 50 | pos, path = todo.popleft() 51 | if pos == end: 52 | continue 53 | 54 | # compute cheats at position! 55 | for c1 in support.adjacent_4(*pos): 56 | if c1 not in world: 57 | continue 58 | for c2 in support.adjacent_4(*c1): 59 | if c2 not in best_at: 60 | continue 61 | 62 | expected = len(seen) + 2 63 | got = best_at[c2] 64 | if expected < got: 65 | cheats[got - expected + 1] += 1 66 | 67 | # advance the path 68 | for cand in support.adjacent_4(*pos): 69 | if cand not in world and cand not in seen: 70 | todo.append((cand, path | {cand})) 71 | seen.add(cand) 72 | 73 | return sum(v for k, v in cheats.items() if k >= at_least) 74 | 75 | 76 | INPUT_S = '''\ 77 | ############### 78 | #...#...#.....# 79 | #.#.#.#.#.###.# 80 | #S#...#.#.#...# 81 | #######.#.#.### 82 | #######.#.#...# 83 | #######.#.###.# 84 | ###..E#...#...# 85 | ###.#######.### 86 | #...###...#...# 87 | #.#####.#.###.# 88 | #.#...#.#.#...# 89 | #.#.#.#.#.#.### 90 | #...#...#...### 91 | ############### 92 | ''' 93 | EXPECTED = 5 94 | 95 | 96 | @pytest.mark.parametrize( 97 | ('input_s', 'expected'), 98 | ( 99 | (INPUT_S, EXPECTED), 100 | ), 101 | ) 102 | def test(input_s: str, expected: int) -> None: 103 | assert compute(input_s, at_least=20) == expected 104 | 105 | 106 | def main() -> int: 107 | parser = argparse.ArgumentParser() 108 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 109 | args = parser.parse_args() 110 | 111 | with open(args.data_file) as f, support.timing(): 112 | print(compute(f.read())) 113 | 114 | return 0 115 | 116 | 117 | if __name__ == '__main__': 118 | raise SystemExit(main()) 119 | -------------------------------------------------------------------------------- /day20/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def _best_path( 15 | world: set[tuple[int, int]], 16 | start: tuple[int, int], 17 | end: tuple[int, int], 18 | ) -> dict[tuple[int, int], int]: 19 | seen = {start} 20 | todo: collections.deque[tuple[tuple[int, int], ...]] 21 | todo = collections.deque([(start,)]) 22 | while todo: 23 | path = todo.popleft() 24 | if path[-1] == end: 25 | break 26 | 27 | for cand in support.adjacent_4(*path[-1]): 28 | if cand not in seen and cand not in world: 29 | todo.append((*path, cand)) 30 | seen.add(cand) 31 | else: 32 | raise AssertionError('unreachable') 33 | 34 | return {pos: i for i, pos in enumerate(path)} 35 | 36 | 37 | def compute(s: str, *, at_least: int = 100) -> int: 38 | world = support.parse_coords_hash(s) 39 | start, = support.parse_coords_hash(s, wall='S') 40 | end, = support.parse_coords_hash(s, wall='E') 41 | 42 | best_at = _best_path(world, start, end) 43 | 44 | cheats: collections.Counter[int] = collections.Counter() 45 | 46 | seen = {start} 47 | todo: collections.deque[tuple[tuple[int, int], frozenset[tuple[int, int]]]] 48 | todo = collections.deque([(start, frozenset((start,)))]) 49 | while todo: 50 | pos, path = todo.popleft() 51 | if pos == end: 52 | continue 53 | 54 | # compute cheats at position! 55 | for dx in range(-20, 20 + 1): 56 | ymax = 20 - abs(dx) 57 | for dy in range(-ymax, ymax + 1): 58 | cand = (pos[0] + dx, pos[1] + dy) 59 | if cand not in best_at: 60 | continue 61 | 62 | expected = len(seen) + abs(dx) + abs(dy) 63 | got = best_at[cand] 64 | if expected < got: 65 | cheats[got - expected + 1] += 1 66 | 67 | # advance the path 68 | for cand in support.adjacent_4(*pos): 69 | if cand not in world and cand not in seen: 70 | todo.append((cand, path | {cand})) 71 | seen.add(cand) 72 | 73 | return sum(v for k, v in cheats.items() if k >= at_least) 74 | 75 | 76 | INPUT_S = '''\ 77 | ############### 78 | #...#...#.....# 79 | #.#.#.#.#.###.# 80 | #S#...#.#.#...# 81 | #######.#.#.### 82 | #######.#.#...# 83 | #######.#.###.# 84 | ###..E#...#...# 85 | ###.#######.### 86 | #...###...#...# 87 | #.#####.#.###.# 88 | #.#...#.#.#...# 89 | #.#.#.#.#.#.### 90 | #...#...#...### 91 | ############### 92 | ''' 93 | EXPECTED = 41 94 | 95 | 96 | @pytest.mark.parametrize( 97 | ('input_s', 'expected'), 98 | ( 99 | (INPUT_S, EXPECTED), 100 | ), 101 | ) 102 | def test(input_s: str, expected: int) -> None: 103 | assert compute(input_s, at_least=70) == expected 104 | 105 | 106 | def main() -> int: 107 | parser = argparse.ArgumentParser() 108 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 109 | args = parser.parse_args() 110 | 111 | with open(args.data_file) as f, support.timing(): 112 | print(compute(f.read())) 113 | 114 | return 0 115 | 116 | 117 | if __name__ == '__main__': 118 | raise SystemExit(main()) 119 | -------------------------------------------------------------------------------- /day21/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day21/__init__.py -------------------------------------------------------------------------------- /day21/input.txt: -------------------------------------------------------------------------------- 1 | 382A 2 | 176A 3 | 463A 4 | 083A 5 | 789A 6 | -------------------------------------------------------------------------------- /day21/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from typing import NamedTuple 7 | from typing import Self 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | KEYPAD_WORLD = { 16 | (0, 0): '7', 17 | (1, 0): '8', 18 | (2, 0): '9', 19 | (0, 1): '4', 20 | (1, 1): '5', 21 | (2, 1): '6', 22 | (0, 2): '1', 23 | (1, 2): '2', 24 | (2, 2): '3', 25 | (1, 3): '0', 26 | (2, 3): 'A', 27 | } 28 | ARROW_WORLD = { 29 | (1, 0): '^', 30 | (2, 0): 'A', 31 | (0, 1): '<', 32 | (1, 1): 'v', 33 | (2, 1): '>', 34 | } 35 | 36 | 37 | class State(NamedTuple): 38 | remaining: str 39 | keypad: tuple[int, int] 40 | bots: tuple[tuple[int, int], ...] 41 | 42 | @classmethod 43 | def initial(cls, s: str) -> Self: 44 | return cls(s, (2, 3), ((2, 0),) * 2) 45 | 46 | def press(self, c: str) -> State | None: 47 | new_bots = [] 48 | for i, bot in enumerate(self.bots): 49 | if c == 'A': 50 | new_bots.append(bot) 51 | c = ARROW_WORLD[bot] 52 | else: 53 | d = support.Direction4.from_c(c) 54 | new_bot = d.apply(*bot) 55 | if new_bot not in ARROW_WORLD: 56 | return None 57 | new_bots.append(new_bot) 58 | new_bots.extend(self.bots[i + 1:]) 59 | break 60 | else: 61 | if c == 'A': 62 | if KEYPAD_WORLD[self.keypad] == self.remaining[0]: 63 | return self._replace(remaining=self.remaining[1:]) 64 | else: 65 | d = support.Direction4.from_c(c) 66 | new_keypad = d.apply(*self.keypad) 67 | if new_keypad not in KEYPAD_WORLD: 68 | return None 69 | else: 70 | return self._replace(keypad=new_keypad) 71 | 72 | return self._replace(bots=tuple(new_bots)) 73 | 74 | 75 | def _val(s: str) -> int: 76 | initial = State.initial(s) 77 | seen = {initial} 78 | todo: collections.deque[tuple[int, State]] 79 | todo = collections.deque([(0, initial)]) 80 | 81 | def _next_state(newstate: State | None) -> None: 82 | if newstate is not None and newstate not in seen: 83 | todo.append((score + 1, newstate)) 84 | seen.add(newstate) 85 | 86 | while todo: 87 | score, state = todo.popleft() 88 | 89 | if state.remaining == '': 90 | return score * int(s[:-1]) 91 | 92 | for c in 'A<^>v': 93 | _next_state(state.press(c)) 94 | 95 | raise AssertionError('unreachable') 96 | 97 | 98 | def compute(s: str) -> int: 99 | return sum(_val(line) for line in s.splitlines()) 100 | 101 | 102 | INPUT_S = '''\ 103 | 029A 104 | 980A 105 | 179A 106 | 456A 107 | 379A 108 | ''' 109 | EXPECTED = 126384 110 | 111 | 112 | @pytest.mark.parametrize( 113 | ('input_s', 'expected'), 114 | ( 115 | (INPUT_S, EXPECTED), 116 | ), 117 | ) 118 | def test(input_s: str, expected: int) -> None: 119 | assert compute(input_s) == expected 120 | 121 | 122 | def main() -> int: 123 | parser = argparse.ArgumentParser() 124 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 125 | args = parser.parse_args() 126 | 127 | with open(args.data_file) as f, support.timing(): 128 | print(compute(f.read())) 129 | 130 | return 0 131 | 132 | 133 | if __name__ == '__main__': 134 | raise SystemExit(main()) 135 | -------------------------------------------------------------------------------- /day21/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import functools 6 | import os.path 7 | import sys 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | KEYPAD_WORLD = { 16 | (0, 0): '7', 17 | (1, 0): '8', 18 | (2, 0): '9', 19 | (0, 1): '4', 20 | (1, 1): '5', 21 | (2, 1): '6', 22 | (0, 2): '1', 23 | (1, 2): '2', 24 | (2, 2): '3', 25 | (1, 3): '0', 26 | (2, 3): 'A', 27 | } 28 | ARROW_WORLD = { 29 | (1, 0): '^', 30 | (2, 0): 'A', 31 | (0, 1): '<', 32 | (1, 1): 'v', 33 | (2, 1): '>', 34 | } 35 | 36 | 37 | def _chunks(s: str) -> int: 38 | chunks = 1 39 | c = s[0] 40 | for c2 in s[1:]: 41 | if c2 != c: 42 | c = c2 43 | chunks += 1 44 | return chunks 45 | 46 | 47 | def _paths( 48 | k1: tuple[int, int], 49 | k2: tuple[int, int], 50 | world: dict[tuple[int, int], str], 51 | ) -> list[str]: 52 | ret = [] 53 | best: dict[tuple[int, int], int] = {} 54 | todo = collections.deque([(k1, '')]) 55 | while todo: 56 | pos, path = todo.popleft() 57 | if pos == k2: 58 | ret.append(path) 59 | continue 60 | elif best.get(pos, sys.maxsize) < len(path): 61 | continue 62 | else: 63 | best[pos] = len(path) 64 | 65 | for d in support.Direction4: 66 | cand = d.apply(*pos) 67 | if cand in world: 68 | todo.append((cand, f'{path}{d.as_c()}')) 69 | 70 | minlen = min(len(s) for s in ret) 71 | ret = [s for s in ret if len(s) == minlen] 72 | minchunks = min(_chunks(s) for s in ret) 73 | ret = [s for s in ret if _chunks(s) == minchunks] 74 | return ret 75 | 76 | 77 | def _all_paths( 78 | world: dict[tuple[int, int], str], 79 | ) -> dict[tuple[str, str], list[str]]: 80 | ret = {} 81 | all_keys = tuple(world) 82 | for k1 in all_keys: 83 | v1 = world[k1] 84 | for k2 in all_keys: 85 | v2 = world[k2] 86 | if k1 == k2: 87 | ret[(v1, v2)] = [''] 88 | else: 89 | ret[(v1, v2)] = _paths(k1, k2, world) 90 | return ret 91 | 92 | 93 | KEYPAD_PATHS = _all_paths(KEYPAD_WORLD) 94 | ARROW_PATHS = _all_paths(ARROW_WORLD) 95 | 96 | 97 | @functools.cache 98 | def _bot_len(s: str, bots: int) -> int: 99 | if bots == 0: 100 | return len(s) 101 | else: 102 | total = 0 103 | for c1, c2 in zip(f'A{s}', s): 104 | possible = ARROW_PATHS[(c1, c2)] 105 | total += min(_bot_len(f'{p}A', bots=bots - 1) for p in possible) 106 | return total 107 | 108 | 109 | @functools.cache 110 | def _keypad_len(s: str, bots: int) -> int: 111 | total = 0 112 | for c1, c2 in zip(f'A{s}', s): 113 | possible = KEYPAD_PATHS[(c1, c2)] 114 | total += min(_bot_len(f'{p}A', bots=bots) for p in possible) 115 | return total 116 | 117 | 118 | def compute(s: str, *, bots: int = 25) -> int: 119 | total = 0 120 | for keypad_s in s.splitlines(): 121 | total += _keypad_len(keypad_s, bots=bots) * int(keypad_s[:-1]) 122 | return total 123 | 124 | 125 | @pytest.mark.parametrize( 126 | ('s', 'bots', 'expected'), 127 | ( 128 | ('029A', 0, 12), 129 | ('029A', 1, 28), 130 | ('029A', 2, 68), 131 | ('029A', 3, 164), 132 | ('029A', 4, 404), 133 | ('029A', 5, 998), 134 | ), 135 | ) 136 | def test_keypad_len(s: str, bots: int, expected: int) -> None: 137 | assert _keypad_len(s, bots=bots) == expected 138 | 139 | 140 | INPUT_S = '''\ 141 | 029A 142 | 980A 143 | 179A 144 | 456A 145 | 379A 146 | ''' 147 | EXPECTED = 126384 148 | 149 | 150 | @pytest.mark.parametrize( 151 | ('input_s', 'expected'), 152 | ( 153 | (INPUT_S, EXPECTED), 154 | ), 155 | ) 156 | def test(input_s: str, expected: int) -> None: 157 | assert compute(input_s, bots=2) == expected 158 | 159 | 160 | @pytest.mark.parametrize( 161 | ('n', 'expected'), 162 | ( 163 | (3, 310188), 164 | (4, 757754), 165 | (5, 1881090), 166 | ), 167 | ) 168 | def test_larger_n(n: int, expected: int) -> None: 169 | assert compute(INPUT_S, bots=n) == expected 170 | 171 | 172 | def main() -> int: 173 | parser = argparse.ArgumentParser() 174 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 175 | args = parser.parse_args() 176 | 177 | with open(args.data_file) as f, support.timing(): 178 | print(compute(f.read())) 179 | 180 | return 0 181 | 182 | 183 | if __name__ == '__main__': 184 | raise SystemExit(main()) 185 | -------------------------------------------------------------------------------- /day22/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day22/__init__.py -------------------------------------------------------------------------------- /day22/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def _compute(x: int, *, n: int) -> int: 14 | for _ in range(n): 15 | x ^= (x * 64) 16 | x %= 16777216 17 | x ^= (x // 32) 18 | x %= 16777216 19 | x ^= (x * 2048) 20 | x %= 16777216 21 | return x 22 | 23 | 24 | def compute(s: str) -> int: 25 | total = 0 26 | numbers = support.parse_numbers_split(s) 27 | for n in numbers: 28 | total += _compute(n, n=2000) 29 | return total 30 | 31 | 32 | INPUT_S = '''\ 33 | 1 34 | 10 35 | 100 36 | 2024 37 | ''' 38 | EXPECTED = 37327623 39 | 40 | 41 | @pytest.mark.parametrize( 42 | ('input_s', 'expected'), 43 | ( 44 | (INPUT_S, EXPECTED), 45 | ), 46 | ) 47 | def test(input_s: str, expected: int) -> None: 48 | assert compute(input_s) == expected 49 | 50 | 51 | def main() -> int: 52 | parser = argparse.ArgumentParser() 53 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 54 | args = parser.parse_args() 55 | 56 | with open(args.data_file) as f, support.timing(): 57 | print(compute(f.read())) 58 | 59 | return 0 60 | 61 | 62 | if __name__ == '__main__': 63 | raise SystemExit(main()) 64 | -------------------------------------------------------------------------------- /day22/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from collections.abc import Generator 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def _compute(x: int, *, n: int) -> Generator[tuple[int, int]]: 16 | prev = x % 10 17 | for _ in range(n): 18 | x ^= (x * 64) 19 | x %= 16777216 20 | x ^= (x // 32) 21 | x %= 16777216 22 | x ^= (x * 2048) 23 | x %= 16777216 24 | 25 | ones = x % 10 26 | yield ones - prev, ones 27 | prev = ones 28 | 29 | 30 | def _banans(x: int) -> dict[tuple[int, ...], int]: 31 | ret: dict[tuple[int, ...], int] = {} 32 | window4: collections.deque[int] = collections.deque(maxlen=4) 33 | for delta, val in _compute(x, n=2000): 34 | window4.append(delta) 35 | if len(window4) == 4: 36 | ret.setdefault(tuple(window4), val) 37 | return ret 38 | 39 | 40 | def compute(s: str) -> int: 41 | total = 0 42 | numbers = support.parse_numbers_split(s) 43 | banans = [_banans(n) for n in numbers] 44 | best = 0 45 | all_keys: set[tuple[int, ...]] = set() 46 | for dct in banans: 47 | all_keys |= dct.keys() 48 | 49 | best = 0 50 | for k in all_keys: 51 | total = sum(banan.get(k, 0) for banan in banans) 52 | best = max(total, best) 53 | 54 | return best 55 | 56 | 57 | INPUT_S = '''\ 58 | 1 59 | 2 60 | 3 61 | 2024 62 | ''' 63 | EXPECTED = 23 64 | 65 | 66 | @pytest.mark.parametrize( 67 | ('input_s', 'expected'), 68 | ( 69 | (INPUT_S, EXPECTED), 70 | ), 71 | ) 72 | def test(input_s: str, expected: int) -> None: 73 | assert compute(input_s) == expected 74 | 75 | 76 | def main() -> int: 77 | parser = argparse.ArgumentParser() 78 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 79 | args = parser.parse_args() 80 | 81 | with open(args.data_file) as f, support.timing(): 82 | print(compute(f.read())) 83 | 84 | return 0 85 | 86 | 87 | if __name__ == '__main__': 88 | raise SystemExit(main()) 89 | -------------------------------------------------------------------------------- /day23/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day23/__init__.py -------------------------------------------------------------------------------- /day23/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | nodes: set[str] = set() 15 | edges: set[tuple[str, str]] = set() 16 | for line in s.splitlines(): 17 | s1, s2 = line.split('-') 18 | assert s1 != s2, (s1, s2) 19 | nodes.update((s1, s2)) 20 | edges.update(((s1, s2), (s2, s1))) 21 | 22 | all_nodes = sorted(nodes) 23 | found = set() 24 | for i, n1 in enumerate(all_nodes): 25 | for j, n2 in enumerate(all_nodes[i + 1:]): 26 | for n3 in all_nodes[j + 1:]: 27 | if ( 28 | (n1, n2) in edges and 29 | (n1, n3) in edges and 30 | (n2, n3) in edges and 31 | any(n.startswith('t') for n in (n1, n2, n3)) 32 | ): 33 | found.add(frozenset((n1, n2, n3))) 34 | 35 | return len(found) 36 | 37 | 38 | INPUT_S = '''\ 39 | kh-tc 40 | qp-kh 41 | de-cg 42 | ka-co 43 | yn-aq 44 | qp-ub 45 | cg-tb 46 | vc-aq 47 | tb-ka 48 | wh-tc 49 | yn-cg 50 | kh-ub 51 | ta-co 52 | de-co 53 | tc-td 54 | tb-wq 55 | wh-td 56 | ta-ka 57 | td-qp 58 | aq-cg 59 | wq-ub 60 | ub-vc 61 | de-ta 62 | wq-aq 63 | wq-vc 64 | wh-yn 65 | ka-de 66 | kh-ta 67 | co-tc 68 | wh-qp 69 | tb-vc 70 | td-yn 71 | ''' 72 | EXPECTED = 7 73 | 74 | 75 | @pytest.mark.parametrize( 76 | ('input_s', 'expected'), 77 | ( 78 | (INPUT_S, EXPECTED), 79 | ), 80 | ) 81 | def test(input_s: str, expected: int) -> None: 82 | assert compute(input_s) == expected 83 | 84 | 85 | def main() -> int: 86 | parser = argparse.ArgumentParser() 87 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 88 | args = parser.parse_args() 89 | 90 | with open(args.data_file) as f, support.timing(): 91 | print(compute(f.read())) 92 | 93 | return 0 94 | 95 | 96 | if __name__ == '__main__': 97 | raise SystemExit(main()) 98 | -------------------------------------------------------------------------------- /day23/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> str: 15 | edges: collections.defaultdict[str, set[str]] 16 | edges = collections.defaultdict(set) 17 | for line in s.splitlines(): 18 | s1, s2 = line.split('-') 19 | assert s1 != s2, (s1, s2) 20 | edges[s1].add(s2) 21 | edges[s2].add(s1) 22 | 23 | seen = set() 24 | best: frozenset[str] = frozenset() 25 | todo = [(frozenset((n,)), frozenset(edges[n])) for n in edges] 26 | while todo: 27 | connected, possible = todo.pop() 28 | if len(connected) > len(best): 29 | best = connected 30 | elif connected in seen: 31 | continue 32 | else: 33 | seen.add(connected) 34 | 35 | for cand in possible: 36 | if connected & edges[cand] == connected: 37 | todo.append((connected | {cand}, possible - {cand})) 38 | 39 | return ','.join(sorted(best)) 40 | 41 | 42 | INPUT_S = '''\ 43 | kh-tc 44 | qp-kh 45 | de-cg 46 | ka-co 47 | yn-aq 48 | qp-ub 49 | cg-tb 50 | vc-aq 51 | tb-ka 52 | wh-tc 53 | yn-cg 54 | kh-ub 55 | ta-co 56 | de-co 57 | tc-td 58 | tb-wq 59 | wh-td 60 | ta-ka 61 | td-qp 62 | aq-cg 63 | wq-ub 64 | ub-vc 65 | de-ta 66 | wq-aq 67 | wq-vc 68 | wh-yn 69 | ka-de 70 | kh-ta 71 | co-tc 72 | wh-qp 73 | tb-vc 74 | td-yn 75 | ''' 76 | EXPECTED = 'co,de,ka,ta' 77 | 78 | 79 | @pytest.mark.parametrize( 80 | ('input_s', 'expected'), 81 | ( 82 | (INPUT_S, EXPECTED), 83 | ), 84 | ) 85 | def test(input_s: str, expected: str) -> None: 86 | assert compute(input_s) == expected 87 | 88 | 89 | def main() -> int: 90 | parser = argparse.ArgumentParser() 91 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 92 | args = parser.parse_args() 93 | 94 | with open(args.data_file) as f, support.timing(): 95 | print(compute(f.read())) 96 | 97 | return 0 98 | 99 | 100 | if __name__ == '__main__': 101 | raise SystemExit(main()) 102 | -------------------------------------------------------------------------------- /day24/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day24/__init__.py -------------------------------------------------------------------------------- /day24/input.txt: -------------------------------------------------------------------------------- 1 | x00: 1 2 | x01: 0 3 | x02: 0 4 | x03: 1 5 | x04: 1 6 | x05: 1 7 | x06: 0 8 | x07: 0 9 | x08: 1 10 | x09: 1 11 | x10: 0 12 | x11: 1 13 | x12: 0 14 | x13: 0 15 | x14: 1 16 | x15: 1 17 | x16: 1 18 | x17: 1 19 | x18: 0 20 | x19: 0 21 | x20: 0 22 | x21: 0 23 | x22: 1 24 | x23: 1 25 | x24: 1 26 | x25: 1 27 | x26: 1 28 | x27: 1 29 | x28: 0 30 | x29: 1 31 | x30: 1 32 | x31: 0 33 | x32: 1 34 | x33: 0 35 | x34: 0 36 | x35: 0 37 | x36: 0 38 | x37: 1 39 | x38: 1 40 | x39: 0 41 | x40: 0 42 | x41: 0 43 | x42: 0 44 | x43: 1 45 | x44: 1 46 | y00: 1 47 | y01: 1 48 | y02: 1 49 | y03: 1 50 | y04: 0 51 | y05: 0 52 | y06: 0 53 | y07: 0 54 | y08: 0 55 | y09: 1 56 | y10: 0 57 | y11: 1 58 | y12: 0 59 | y13: 0 60 | y14: 1 61 | y15: 0 62 | y16: 0 63 | y17: 0 64 | y18: 0 65 | y19: 1 66 | y20: 0 67 | y21: 0 68 | y22: 0 69 | y23: 0 70 | y24: 0 71 | y25: 1 72 | y26: 1 73 | y27: 0 74 | y28: 1 75 | y29: 0 76 | y30: 1 77 | y31: 0 78 | y32: 1 79 | y33: 1 80 | y34: 0 81 | y35: 1 82 | y36: 1 83 | y37: 1 84 | y38: 0 85 | y39: 1 86 | y40: 0 87 | y41: 1 88 | y42: 1 89 | y43: 0 90 | y44: 1 91 | 92 | x07 AND y07 -> ncs 93 | y24 AND x24 -> wrf 94 | x19 XOR y19 -> tsm 95 | x40 XOR y40 -> svt 96 | rjf OR src -> dfv 97 | fsf XOR fgs -> z44 98 | mhc AND jqd -> qgn 99 | nrr XOR sms -> kpp 100 | y20 AND x20 -> ngc 101 | y21 AND x21 -> hbc 102 | sgj OR ptb -> rqf 103 | hbc OR wdr -> gjn 104 | tks XOR sbg -> z23 105 | ddh AND tnm -> hgg 106 | hsf OR bjw -> vbb 107 | x15 XOR y15 -> vqs 108 | x10 AND y10 -> dtm 109 | vqs XOR vss -> z15 110 | x29 XOR y29 -> mgd 111 | srg OR cwb -> qtn 112 | nmb OR mbk -> z45 113 | dhs OR njq -> tng 114 | jfw OR jrf -> vpd 115 | x07 XOR y07 -> gck 116 | tdw XOR vrk -> z03 117 | y11 AND x11 -> ffw 118 | x16 XOR y16 -> bth 119 | x39 XOR y39 -> tnm 120 | cfg AND ngh -> jrq 121 | vpd AND mvn -> hbj 122 | rgp XOR bth -> z16 123 | qtn AND cjd -> vrv 124 | x14 AND y14 -> cgt 125 | dwh XOR hsk -> z24 126 | tgp XOR dkh -> z13 127 | y26 XOR x26 -> cfg 128 | cpc XOR nbm -> z42 129 | y42 XOR x42 -> cpc 130 | x17 XOR y17 -> cjd 131 | rqf XOR pqn -> z36 132 | x27 AND y27 -> nwg 133 | bcq AND hnk -> vjp 134 | tks AND sbg -> wqr 135 | wvr OR skq -> gmw 136 | cwm AND tpv -> pqw 137 | x41 AND y41 -> tqh 138 | jcw AND wpk -> sbr 139 | tgp AND dkh -> tbh 140 | wrg XOR nwq -> z38 141 | y32 XOR x32 -> gds 142 | bmn OR hbj -> msb 143 | wps XOR mtn -> z33 144 | ncs OR pjf -> sws 145 | wqr OR tqk -> dwh 146 | x31 AND y31 -> pwg 147 | y12 XOR x12 -> jcw 148 | nrr AND sms -> z31 149 | x38 AND y38 -> npd 150 | y02 AND x02 -> fwt 151 | y37 AND x37 -> rnc 152 | fwt OR vtm -> tdw 153 | x38 XOR y38 -> nwq 154 | gds AND ghr -> ckd 155 | ffw OR nfb -> wpk 156 | ctv XOR wht -> z05 157 | y11 XOR x11 -> cmg 158 | y05 XOR x05 -> ctv 159 | jhw XOR tcv -> z18 160 | wrf OR gnt -> rmw 161 | y01 AND x01 -> tnr 162 | x36 XOR y36 -> pqn 163 | gjq OR dfg -> skp 164 | x40 AND y40 -> ptg 165 | y39 AND x39 -> dqn 166 | bjb OR hjf -> sbg 167 | rrn OR rpt -> qfs 168 | ctv AND wht -> dhs 169 | sgs AND rsb -> ccw 170 | rmw XOR psg -> z25 171 | y24 XOR x24 -> hsk 172 | bgd XOR msb -> z10 173 | y17 AND x17 -> fvv 174 | y22 AND x22 -> kdh 175 | qfs AND rfv -> fgp 176 | wds AND fps -> rhr 177 | y18 XOR x18 -> tcv 178 | ttd AND nhg -> tfw 179 | bbc AND jkb -> ptb 180 | djn OR tnr -> cpb 181 | y35 XOR x35 -> bbc 182 | tfw OR cgt -> z14 183 | rgp AND bth -> srg 184 | dwh AND hsk -> gnt 185 | pqw OR ngc -> frt 186 | y25 XOR x25 -> psg 187 | y13 XOR x13 -> tgp 188 | x30 XOR y30 -> rbw 189 | vrv OR fvv -> jhw 190 | skp XOR mgd -> z29 191 | cmg XOR ntv -> z11 192 | vjr XOR vbb -> z04 193 | gkj XOR sws -> z08 194 | x20 XOR y20 -> tpv 195 | ntv AND cmg -> nfb 196 | x32 AND y32 -> tdk 197 | wmr AND cpb -> vtm 198 | x19 AND y19 -> jps 199 | jhw AND tcv -> rqv 200 | y27 XOR x27 -> bcq 201 | x34 AND y34 -> hdk 202 | wqc XOR qtf -> z01 203 | wgk OR sbr -> dkh 204 | x43 AND y43 -> kdb 205 | y04 XOR x04 -> vjr 206 | rmw AND psg -> fgg 207 | gkj AND sws -> jfw 208 | cwm XOR tpv -> z20 209 | cjd XOR qtn -> z17 210 | fsf AND fgs -> nmb 211 | wps AND mtn -> rpt 212 | x33 XOR y33 -> mtn 213 | bcq XOR hnk -> z27 214 | tbh OR wwk -> nhg 215 | twb XOR tsm -> z19 216 | frt AND mhw -> wdr 217 | y15 AND x15 -> pwr 218 | rbw XOR dfv -> z30 219 | vss AND vqs -> ctt 220 | x28 AND y28 -> gjq 221 | y28 XOR x28 -> dvf 222 | bbc XOR jkb -> sgj 223 | x43 XOR y43 -> fps 224 | y04 AND x04 -> khw 225 | pwg OR kpp -> ghr 226 | x31 XOR y31 -> nrr 227 | gmw XOR gck -> z07 228 | frt XOR mhw -> z21 229 | spb AND tng -> skq 230 | svt AND hkg -> knf 231 | gjn AND kdh -> bjb 232 | qfs XOR rfv -> z34 233 | cpc AND nbm -> jtt 234 | tqh OR ccw -> nbm 235 | jtt OR qkv -> wds 236 | gds XOR ghr -> z32 237 | rbw AND dfv -> qnv 238 | msb AND bgd -> gjr 239 | qnv OR bjd -> sms 240 | y18 AND x18 -> pns 241 | x41 XOR y41 -> rsb 242 | x26 AND y26 -> srh 243 | nvc OR npd -> ddh 244 | dtm OR gjr -> ntv 245 | x08 AND y08 -> jrf 246 | y14 XOR x14 -> ttd 247 | y06 AND x06 -> wvr 248 | y16 AND x16 -> cwb 249 | rnc OR qgn -> wrg 250 | y30 AND x30 -> bjd 251 | jqd XOR mhc -> z37 252 | ddh XOR tnm -> z39 253 | x12 AND y12 -> wgk 254 | cqh OR fgg -> ngh 255 | kdh XOR gjn -> z22 256 | x01 XOR y01 -> wqc 257 | khw OR djm -> wht 258 | ctt OR pwr -> rgp 259 | y21 XOR x21 -> mhw 260 | vjp OR nwg -> dhh 261 | x02 XOR y02 -> wmr 262 | gck AND gmw -> pjf 263 | rqv OR pns -> twb 264 | y00 AND x00 -> qtf 265 | y05 AND x05 -> njq 266 | y29 AND x29 -> rjf 267 | vrk AND tdw -> hsf 268 | y42 AND x42 -> qkv 269 | y10 XOR x10 -> bgd 270 | cfg XOR ngh -> z26 271 | tng XOR spb -> z06 272 | y00 XOR x00 -> z00 273 | cpb XOR wmr -> z02 274 | ckd OR tdk -> wps 275 | jrq OR srh -> hnk 276 | y22 XOR x22 -> hjf 277 | x03 AND y03 -> bjw 278 | nhg XOR ttd -> vss 279 | tsm AND twb -> rwg 280 | dqn OR hgg -> hkg 281 | y34 XOR x34 -> rfv 282 | y35 AND x35 -> z35 283 | x25 AND y25 -> cqh 284 | y33 AND x33 -> rrn 285 | wqc AND qtf -> djn 286 | sgs XOR rsb -> z41 287 | x08 XOR y08 -> gkj 288 | rwg OR jps -> cwm 289 | rqf AND pqn -> wbv 290 | x37 XOR y37 -> mhc 291 | dvf XOR dhh -> z28 292 | kdb OR rhr -> fgs 293 | knf OR ptg -> sgs 294 | svt XOR hkg -> z40 295 | y13 AND x13 -> wwk 296 | y23 AND x23 -> tqk 297 | fgp OR hdk -> jkb 298 | jcw XOR wpk -> z12 299 | y06 XOR x06 -> spb 300 | x23 XOR y23 -> tks 301 | y09 AND x09 -> bmn 302 | wds XOR fps -> z43 303 | dhh AND dvf -> dfg 304 | mgd AND skp -> src 305 | wrg AND nwq -> nvc 306 | y03 XOR x03 -> vrk 307 | y36 AND x36 -> kqk 308 | vjr AND vbb -> djm 309 | x44 XOR y44 -> fsf 310 | x44 AND y44 -> mbk 311 | kqk OR wbv -> jqd 312 | vpd XOR mvn -> z09 313 | y09 XOR x09 -> mvn 314 | -------------------------------------------------------------------------------- /day24/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import operator 5 | import os.path 6 | from collections.abc import Callable 7 | from typing import NamedTuple 8 | from typing import Self 9 | 10 | import pytest 11 | 12 | import support 13 | 14 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 15 | 16 | OPS = {'AND': operator.and_, 'OR': operator.or_, 'XOR': operator.xor} 17 | 18 | 19 | class Eq(NamedTuple): 20 | inputs: tuple[str, str] 21 | output: str 22 | operator: Callable[[int, int], int] 23 | 24 | @classmethod 25 | def parse(cls, s: str) -> Self: 26 | i1, op_s, i2, _, o = s.split() 27 | return cls((i1, i2), o, OPS[op_s]) 28 | 29 | def ready(self, inputs: dict[str, int]) -> bool: 30 | return all(i in inputs for i in self.inputs) 31 | 32 | def compute(self, inputs: dict[str, int]) -> int: 33 | return self.operator(*(inputs[i] for i in self.inputs)) 34 | 35 | 36 | def compute(s: str) -> int: 37 | values_s, computes_s = s.split('\n\n') 38 | values = {} 39 | for line in values_s.splitlines(): 40 | name, val_s = line.split(': ') 41 | values[name] = int(val_s) 42 | 43 | equations = [Eq.parse(line) for line in computes_s.splitlines()] 44 | while equations: 45 | for equation in equations: 46 | if equation.ready(values): 47 | values[equation.output] = equation.compute(values) 48 | equations.remove(equation) 49 | break 50 | else: 51 | raise AssertionError('no work done?') 52 | 53 | zs = sorted(((k, v) for k, v in values.items() if k.startswith('z'))) 54 | return int(''.join(str(bit) for _, bit in reversed(zs)), 2) 55 | 56 | 57 | INPUT_S = '''\ 58 | x00: 1 59 | x01: 0 60 | x02: 1 61 | x03: 1 62 | x04: 0 63 | y00: 1 64 | y01: 1 65 | y02: 1 66 | y03: 1 67 | y04: 1 68 | 69 | ntg XOR fgs -> mjb 70 | y02 OR x01 -> tnw 71 | kwq OR kpj -> z05 72 | x00 OR x03 -> fst 73 | tgd XOR rvg -> z01 74 | vdt OR tnw -> bfw 75 | bfw AND frj -> z10 76 | ffh OR nrd -> bqk 77 | y00 AND y03 -> djm 78 | y03 OR y00 -> psh 79 | bqk OR frj -> z08 80 | tnw OR fst -> frj 81 | gnj AND tgd -> z11 82 | bfw XOR mjb -> z00 83 | x03 OR x00 -> vdt 84 | gnj AND wpb -> z02 85 | x04 AND y00 -> kjc 86 | djm OR pbm -> qhw 87 | nrd AND vdt -> hwm 88 | kjc AND fst -> rvg 89 | y04 OR y02 -> fgs 90 | y01 AND x02 -> pbm 91 | ntg OR kjc -> kwq 92 | psh XOR fgs -> tgd 93 | qhw XOR tgd -> z09 94 | pbm OR djm -> kpj 95 | x03 XOR y03 -> ffh 96 | x00 XOR y04 -> ntg 97 | bfw OR bqk -> z06 98 | nrd XOR fgs -> wpb 99 | frj XOR qhw -> z04 100 | bqk OR frj -> z07 101 | y03 OR x01 -> nrd 102 | hwm AND bqk -> z03 103 | tgd XOR rvg -> z12 104 | tnw OR pbm -> gnj 105 | ''' 106 | EXPECTED = 2024 107 | 108 | 109 | @pytest.mark.parametrize( 110 | ('input_s', 'expected'), 111 | ( 112 | (INPUT_S, EXPECTED), 113 | ), 114 | ) 115 | def test(input_s: str, expected: int) -> None: 116 | assert compute(input_s) == expected 117 | 118 | 119 | def main() -> int: 120 | parser = argparse.ArgumentParser() 121 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 122 | args = parser.parse_args() 123 | 124 | with open(args.data_file) as f, support.timing(): 125 | print(compute(f.read())) 126 | 127 | return 0 128 | 129 | 130 | if __name__ == '__main__': 131 | raise SystemExit(main()) 132 | -------------------------------------------------------------------------------- /day24/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import operator 6 | import os.path 7 | from collections.abc import Callable 8 | from typing import NamedTuple 9 | from typing import Self 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | OPS = {'AND': operator.and_, 'OR': operator.or_, 'XOR': operator.xor} 16 | 17 | 18 | class Uncomputable(ValueError): 19 | pass 20 | 21 | 22 | class Eq(NamedTuple): 23 | inputs: tuple[str, str] 24 | output: str 25 | operator: Callable[[int, int], int] 26 | 27 | @classmethod 28 | def parse(cls, s: str) -> Self: 29 | i1, op_s, i2, _, o = s.split() 30 | return cls((i1, i2), o, OPS[op_s]) 31 | 32 | def ready(self, inputs: dict[str, int]) -> bool: 33 | return all(i in inputs for i in self.inputs) 34 | 35 | def compute(self, inputs: dict[str, int]) -> int: 36 | return self.operator(*(inputs[i] for i in self.inputs)) 37 | 38 | 39 | def _int_to_values(n: int, name: str) -> dict[str, int]: 40 | return { 41 | f'{name}{i:02}': int(bit) 42 | for i, bit in enumerate(reversed(f'{n:064b}')) 43 | } 44 | 45 | 46 | def _order_equations(all_equations: tuple[Eq, ...]) -> tuple[Eq, ...]: 47 | values = { 48 | **_int_to_values(0, 'y'), 49 | **_int_to_values(0, 'x'), 50 | } 51 | equations = sorted(all_equations) 52 | new_equations = [] 53 | while equations: 54 | for equation in equations: 55 | if equation.ready(values): 56 | values[equation.output] = equation.compute(values) 57 | equations.remove(equation) 58 | new_equations.append(equation) 59 | break 60 | else: 61 | raise Uncomputable 62 | 63 | return tuple(new_equations) 64 | 65 | 66 | def _run( 67 | all_equations: tuple[Eq, ...], 68 | x: int, 69 | y: int, 70 | swap: dict[str, str] | None = None, 71 | ) -> int: 72 | swap = swap or {} 73 | values = { 74 | **_int_to_values(x, 'x'), 75 | **_int_to_values(y, 'y'), 76 | } 77 | equations = list(all_equations) 78 | while equations: 79 | for equation in equations: 80 | if equation.ready(values): 81 | output = swap.get(equation.output, equation.output) 82 | values[output] = equation.compute(values) 83 | equations.remove(equation) 84 | break 85 | else: 86 | raise Uncomputable 87 | 88 | zs = sorted(((k, v) for k, v in values.items() if k.startswith('z'))) 89 | return int(''.join(str(bit) for _, bit in reversed(zs)), 2) 90 | 91 | 92 | def _test( 93 | equations: tuple[Eq, ...], 94 | bit: int, 95 | swap: dict[str, str] | None = None, 96 | ) -> bool: 97 | val = 1 << bit 98 | try: 99 | got = { 100 | _run(equations, val, 0, swap=swap), 101 | _run(equations, 0, val, swap=swap), 102 | _run(equations, val - 1, 1, swap=swap), 103 | _run(equations, 1, val - 1, swap=swap), 104 | } 105 | if bit > 1: 106 | got.add(_run(equations, val >> 1, val >> 1, swap=swap)) 107 | except Uncomputable: 108 | return False 109 | return got == {val} 110 | 111 | 112 | def _bad_bits( 113 | equations: tuple[Eq, ...], 114 | highest_bit: int, 115 | swap: dict[str, str], 116 | ) -> set[int]: 117 | return { 118 | i 119 | for i in range(highest_bit) 120 | if not _test(equations, i, swap=swap) 121 | } 122 | 123 | 124 | def compute(s: str, *, pairs: int = 4) -> str: 125 | _, computes_s = s.split('\n\n') 126 | equations = tuple(Eq.parse(line) for line in computes_s.splitlines()) 127 | 128 | equations = _order_equations(equations) 129 | 130 | edges = {eq.output: eq.inputs for eq in equations} 131 | 132 | @functools.cache 133 | def _trace(node: str) -> frozenset[str]: 134 | seen = {node} 135 | todo = [node] 136 | while todo: 137 | output = todo.pop() 138 | for k in edges[output]: 139 | if not k.startswith(('x', 'y')): 140 | seen.add(k) 141 | todo.append(k) 142 | return frozenset(seen) 143 | 144 | def _find_pair(i: int) -> tuple[str, str]: 145 | prev = _trace(f'z{(i - 1):02}') 146 | for k1 in _trace(f'z{i:02}') - prev: 147 | # XXX: inputs sort of worked out that it broke the next bit 148 | for k2 in _trace(f'z{i + 1:02}'): 149 | swap = {**found, k1: k2, k2: k1} 150 | if ( 151 | _test(equations, i, swap=swap) and 152 | _test(equations, i + 1, swap=swap) 153 | ): 154 | return (k1, k2) 155 | 156 | raise AssertionError('unreachable!') 157 | 158 | found: dict[str, str] = {} 159 | highest_z = max(eq.output for eq in equations if eq.output.startswith('z')) 160 | highest_bit = int(highest_z[1:]) 161 | for i in range(highest_bit): 162 | if not _test(equations, i, swap=found): 163 | k1, k2 = _find_pair(i) 164 | found[k1] = k2 165 | found[k2] = k1 166 | 167 | return ','.join(sorted(found)) 168 | 169 | 170 | def main() -> int: 171 | parser = argparse.ArgumentParser() 172 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 173 | args = parser.parse_args() 174 | 175 | with open(args.data_file) as f, support.timing(): 176 | print(compute(f.read())) 177 | 178 | return 0 179 | 180 | 181 | if __name__ == '__main__': 182 | raise SystemExit(main()) 183 | -------------------------------------------------------------------------------- /day25/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2024/5ea2ab9fbe6e8440cd0b43aca7b72f11931e7f7d/day25/__init__.py -------------------------------------------------------------------------------- /day25/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | things = [support.parse_coords_hash(part) for part in s.split('\n\n')] 15 | 16 | total = 0 17 | for i, thing in enumerate(things): 18 | for other in things[i + 1:]: 19 | if thing & other == set(): 20 | total += 1 21 | 22 | return total 23 | 24 | 25 | INPUT_S = '''\ 26 | ##### 27 | .#### 28 | .#### 29 | .#### 30 | .#.#. 31 | .#... 32 | ..... 33 | 34 | ##### 35 | ##.## 36 | .#.## 37 | ...## 38 | ...#. 39 | ...#. 40 | ..... 41 | 42 | ..... 43 | #.... 44 | #.... 45 | #...# 46 | #.#.# 47 | #.### 48 | ##### 49 | 50 | ..... 51 | ..... 52 | #.#.. 53 | ###.. 54 | ###.# 55 | ###.# 56 | ##### 57 | 58 | ..... 59 | ..... 60 | ..... 61 | #.... 62 | #.#.. 63 | #.#.# 64 | ##### 65 | ''' 66 | EXPECTED = 3 67 | 68 | 69 | @pytest.mark.parametrize( 70 | ('input_s', 'expected'), 71 | ( 72 | (INPUT_S, EXPECTED), 73 | ), 74 | ) 75 | def test(input_s: str, expected: int) -> None: 76 | assert compute(input_s) == expected 77 | 78 | 79 | def main() -> int: 80 | parser = argparse.ArgumentParser() 81 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 82 | args = parser.parse_args() 83 | 84 | with open(args.data_file) as f, support.timing(): 85 | print(compute(f.read())) 86 | 87 | return 0 88 | 89 | 90 | if __name__ == '__main__': 91 | raise SystemExit(main()) 92 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e support-src 2 | pytest 3 | z3-solver 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | mypy_path = support-src 3 | 4 | check_untyped_defs = true 5 | disallow_any_generics = true 6 | disallow_incomplete_defs = true 7 | disallow_untyped_defs = true 8 | warn_redundant_casts = true 9 | warn_unused_ignores = true 10 | -------------------------------------------------------------------------------- /support-src/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = support 3 | 4 | [options] 5 | py_modules = support 6 | 7 | [options.entry_points] 8 | console_scripts = 9 | aoc-download-input = support:download_input 10 | aoc-submit = support:submit_solution 11 | aoc-25-pt2 = support:submit_25_pt2 12 | -------------------------------------------------------------------------------- /support-src/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from setuptools import setup 4 | setup() 5 | -------------------------------------------------------------------------------- /support-src/support.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import contextlib 5 | import enum 6 | import os.path 7 | import re 8 | import sys 9 | import time 10 | import urllib.error 11 | import urllib.parse 12 | import urllib.request 13 | from collections.abc import Generator 14 | from collections.abc import Iterable 15 | from typing import NamedTuple 16 | 17 | HERE = os.path.dirname(os.path.abspath(__file__)) 18 | 19 | 20 | @contextlib.contextmanager 21 | def timing(name: str = '') -> Generator[None]: 22 | before = time.time() 23 | try: 24 | yield 25 | finally: 26 | after = time.time() 27 | t = (after - before) * 1000 28 | unit = 'ms' 29 | if t < 100: 30 | t *= 1000 31 | unit = 'μs' 32 | if name: 33 | name = f' ({name})' 34 | print(f'> {int(t)} {unit}{name}', file=sys.stderr, flush=True) 35 | 36 | 37 | def _get_cookie_headers() -> dict[str, str]: 38 | with open(os.path.join(HERE, '../.env')) as f: 39 | contents = f.read().strip() 40 | return {'Cookie': contents, 'User-Agent': 'anthonywritescode, hi eric'} 41 | 42 | 43 | def get_input(year: int, day: int) -> str: 44 | url = f'https://adventofcode.com/{year}/day/{day}/input' 45 | req = urllib.request.Request(url, headers=_get_cookie_headers()) 46 | return urllib.request.urlopen(req).read().decode() 47 | 48 | 49 | def get_year_day() -> tuple[int, int]: 50 | cwd = os.getcwd() 51 | day_s = os.path.basename(cwd) 52 | year_s = os.path.basename(os.path.dirname(cwd)) 53 | 54 | if not day_s.startswith('day') or not year_s.startswith('aoc'): 55 | raise AssertionError(f'unexpected working dir: {cwd}') 56 | 57 | return int(year_s[len('aoc'):]), int(day_s[len('day'):]) 58 | 59 | 60 | def download_input() -> int: 61 | parser = argparse.ArgumentParser() 62 | parser.parse_args() 63 | 64 | year, day = get_year_day() 65 | 66 | for i in range(5): 67 | try: 68 | s = get_input(year, day) 69 | except urllib.error.URLError as e: 70 | print(f'zzz: not ready yet: {e}') 71 | time.sleep(1) 72 | else: 73 | break 74 | else: 75 | raise SystemExit('timed out after attempting many times') 76 | 77 | with open('input.txt', 'w') as f: 78 | f.write(s) 79 | os.chmod('input.txt', 0o400) 80 | 81 | lines = s.splitlines() 82 | if len(lines) == 1 and len(lines[0]) < 80: 83 | print(lines[0]) 84 | elif len(lines) > 10: 85 | for line in lines[:10]: 86 | print(line) 87 | print('...') 88 | else: 89 | print(lines[0][:80]) 90 | print('...') 91 | 92 | return 0 93 | 94 | 95 | TOO_QUICK = re.compile('You gave an answer too recently.*to wait.') 96 | WRONG = re.compile(r"That's not the right answer.*?\.") 97 | RIGHT = "That's the right answer!" 98 | ALREADY_DONE = re.compile(r"You don't seem to be solving.*\?") 99 | 100 | 101 | def _post_answer(year: int, day: int, part: int, answer: int) -> str: 102 | params = urllib.parse.urlencode({'level': part, 'answer': answer}) 103 | req = urllib.request.Request( 104 | f'https://adventofcode.com/{year}/day/{day}/answer', 105 | method='POST', 106 | data=params.encode(), 107 | headers=_get_cookie_headers(), 108 | ) 109 | resp = urllib.request.urlopen(req) 110 | 111 | return resp.read().decode() 112 | 113 | 114 | def submit_solution() -> int: 115 | parser = argparse.ArgumentParser() 116 | parser.add_argument('--part', type=int, required=True) 117 | args = parser.parse_args() 118 | 119 | year, day = get_year_day() 120 | answer = int(sys.stdin.read()) 121 | 122 | print(f'answer: {answer}') 123 | 124 | contents = _post_answer(year, day, args.part, answer) 125 | 126 | for error_regex in (WRONG, TOO_QUICK, ALREADY_DONE): 127 | error_match = error_regex.search(contents) 128 | if error_match: 129 | print(f'\033[41m{error_match[0]}\033[m') 130 | return 1 131 | 132 | if RIGHT in contents: 133 | print(f'\033[42m{RIGHT}\033[m') 134 | return 0 135 | else: 136 | # unexpected output? 137 | print(contents) 138 | return 1 139 | 140 | 141 | def submit_25_pt2() -> int: 142 | parser = argparse.ArgumentParser() 143 | parser.parse_args() 144 | 145 | year, day = get_year_day() 146 | 147 | assert day == 25, day 148 | contents = _post_answer(year, day, part=2, answer=0) 149 | 150 | if 'Congratulations!' in contents: 151 | print('\033[42mCongratulations!\033[m') 152 | return 0 153 | else: 154 | print(contents) 155 | return 1 156 | 157 | 158 | def adjacent_4(x: int, y: int) -> Generator[tuple[int, int]]: 159 | yield x, y - 1 160 | yield x + 1, y 161 | yield x, y + 1 162 | yield x - 1, y 163 | 164 | 165 | def adjacent_8(x: int, y: int) -> Generator[tuple[int, int]]: 166 | for y_d in (-1, 0, 1): 167 | for x_d in (-1, 0, 1): 168 | if y_d == x_d == 0: 169 | continue 170 | yield x + x_d, y + y_d 171 | 172 | 173 | def parse_point_comma(s: str) -> tuple[int, int]: 174 | a_s, b_s = s.split(',') 175 | return int(a_s), int(b_s) 176 | 177 | 178 | def parse_coords_int(s: str) -> dict[tuple[int, int], int]: 179 | coords = {} 180 | for y, line in enumerate(s.splitlines()): 181 | for x, c in enumerate(line): 182 | coords[(x, y)] = int(c) 183 | return coords 184 | 185 | 186 | def parse_coords_hash(s: str, *, wall: str = '#') -> set[tuple[int, int]]: 187 | return { 188 | (x, y) 189 | for y, line in enumerate(s.splitlines()) 190 | for x, c in enumerate(line) 191 | if c == wall 192 | } 193 | 194 | 195 | def parse_numbers_split(s: str) -> list[int]: 196 | return [int(x) for x in s.split()] 197 | 198 | 199 | def parse_numbers_comma(s: str) -> list[int]: 200 | return [int(x) for x in s.strip().split(',')] 201 | 202 | 203 | class Bound(NamedTuple): 204 | min: int 205 | max: int 206 | 207 | @property 208 | def range(self) -> range: 209 | return range(self.min, self.max + 1) 210 | 211 | 212 | def bounds(points: Iterable[tuple[int, ...]]) -> tuple[Bound, ...]: 213 | return tuple(Bound(min(dim), max(dim)) for dim in zip(*points)) 214 | 215 | 216 | def in_bounds(pt: tuple[int, ...], bounds: tuple[Bound, ...]) -> bool: 217 | return all(n in b.range for n, b in zip(pt, bounds, strict=True)) 218 | 219 | 220 | def format_coords_hash(coords: set[tuple[int, int]]) -> str: 221 | bx, by = bounds(coords) 222 | return '\n'.join( 223 | ''.join('#' if (x, y) in coords else ' ' for x in bx.range) 224 | for y in by.range 225 | ) 226 | 227 | 228 | def print_coords_hash(coords: set[tuple[int, int]]) -> None: 229 | print(format_coords_hash(coords)) 230 | 231 | 232 | class Direction4(enum.Enum): 233 | UP = (0, -1) 234 | RIGHT = (1, 0) 235 | DOWN = (0, 1) 236 | LEFT = (-1, 0) 237 | 238 | def __init__(self, x: int, y: int) -> None: 239 | self.x, self.y = x, y 240 | 241 | def __lt__(self, other: object) -> bool: 242 | if not isinstance(other, Direction4): 243 | return NotImplemented 244 | else: 245 | return (self.x, self.y) < (other.x, other.y) 246 | 247 | @property 248 | def _vals(self) -> tuple[Direction4, ...]: 249 | return tuple(type(self).__members__.values()) 250 | 251 | @property 252 | def cw(self) -> Direction4: 253 | vals = self._vals 254 | return vals[(vals.index(self) + 1) % len(vals)] 255 | 256 | @property 257 | def ccw(self) -> Direction4: 258 | vals = self._vals 259 | return vals[(vals.index(self) - 1) % len(vals)] 260 | 261 | @property 262 | def opposite(self) -> Direction4: 263 | vals = self._vals 264 | return vals[(vals.index(self) + 2) % len(vals)] 265 | 266 | def apply(self, x: int, y: int, *, n: int = 1) -> tuple[int, int]: 267 | return self.x * n + x, self.y * n + y 268 | 269 | def as_c(self) -> str: 270 | return _DIRECTION4_C_REV[self] 271 | 272 | @staticmethod 273 | def from_c(c: str) -> Direction4: 274 | return _DIRECTION4_C[c] 275 | 276 | 277 | _DIRECTION4_C = { 278 | '<': Direction4.LEFT, 279 | '>': Direction4.RIGHT, 280 | '^': Direction4.UP, 281 | 'v': Direction4.DOWN, 282 | } 283 | _DIRECTION4_C_REV = {v: k for k, v in _DIRECTION4_C.items()} 284 | -------------------------------------------------------------------------------- /support-src/support_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import support 4 | 5 | 6 | def test_adjacent_4() -> None: 7 | pts = set(support.adjacent_4(1, 2)) 8 | assert pts == {(0, 2), (2, 2), (1, 3), (1, 1)} 9 | 10 | 11 | def test_adjacent_8() -> None: 12 | pts = set(support.adjacent_8(1, 2)) 13 | assert pts == { 14 | (0, 1), (1, 1), (2, 1), 15 | (0, 2), (2, 2), 16 | (0, 3), (1, 3), (2, 3), 17 | } 18 | 19 | 20 | def test_parse_coords_int() -> None: 21 | coords = support.parse_coords_int('123\n456') 22 | assert coords == { 23 | (0, 0): 1, 24 | (1, 0): 2, 25 | (2, 0): 3, 26 | (0, 1): 4, 27 | (1, 1): 5, 28 | (2, 1): 6, 29 | } 30 | 31 | 32 | def test_parse_coords_hash() -> None: 33 | coords = support.parse_coords_hash(' # \n# \n') 34 | assert coords == {(1, 0), (0, 1)} 35 | 36 | 37 | def test_parse_numbers_split() -> None: 38 | assert support.parse_numbers_split('1 2') == [1, 2] 39 | assert support.parse_numbers_split('1\n2\n') == [1, 2] 40 | 41 | 42 | def test_parse_numbers_comma() -> None: 43 | assert support.parse_numbers_comma('1,2,3') == [1, 2, 3] 44 | assert support.parse_numbers_comma('1,2,3\n') == [1, 2, 3] 45 | 46 | 47 | def test_format_coords_hash() -> None: 48 | assert support.format_coords_hash({(1, 0), (0, 1)}) == ' #\n# ' 49 | 50 | 51 | def test_direction4() -> None: 52 | assert support.Direction4.UP.cw is support.Direction4.RIGHT 53 | assert support.Direction4.UP.ccw is support.Direction4.LEFT 54 | assert support.Direction4.UP.opposite is support.Direction4.DOWN 55 | assert support.Direction4.UP.apply(0, 0) == (0, -1) 56 | --------------------------------------------------------------------------------