├── .activate.sh ├── .deactivate.sh ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── day00 ├── __init__.py ├── input.txt └── part1.py ├── day01 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day02 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day03 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day04 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day05 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day06 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day07 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day08 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day09 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day10 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day11 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day12 ├── __init__.py ├── input.txt ├── part1.py ├── part1.sqlite.sql ├── part2.py └── part2.sqlite.sql ├── day13 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day14 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day15 ├── __init__.py ├── 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 ├── 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 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: debug-statements 9 | - id: double-quote-string-fixer 10 | - id: name-tests-test 11 | - id: requirements-txt-fixer 12 | - repo: https://github.com/asottile/reorder_python_imports 13 | rev: v3.9.0 14 | hooks: 15 | - id: reorder-python-imports 16 | args: [--py37-plus, --add-import, 'from __future__ import annotations'] 17 | - repo: https://github.com/asottile/add-trailing-comma 18 | rev: v2.3.0 19 | hooks: 20 | - id: add-trailing-comma 21 | args: [--py36-plus] 22 | - repo: https://github.com/asottile/pyupgrade 23 | rev: v3.2.2 24 | hooks: 25 | - id: pyupgrade 26 | args: [--py37-plus] 27 | - repo: https://github.com/pre-commit/mirrors-autopep8 28 | rev: v2.0.0 29 | hooks: 30 | - id: autopep8 31 | - repo: https://github.com/PyCQA/flake8 32 | rev: 6.0.0 33 | hooks: 34 | - id: flake8 35 | - repo: https://github.com/pre-commit/mirrors-mypy 36 | rev: v0.991 37 | hooks: 38 | - id: mypy 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Anthony Sottile 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | advent of code 2022 2 | =================== 3 | 4 | https://adventofcode.com/2022 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 2022, I'm planning to implement in python 14 | 15 | ### timing 16 | 17 | - comparing to these numbers isn't necessarily useful 18 | - normalize your timing to day 1 part 1 and compare 19 | - alternate implementations are listed in parens 20 | - these timings are very non-scientific (sample size 1) 21 | 22 | ```console 23 | $ find -maxdepth 1 -type d -name 'day*' -not -name day00 | sort | xargs --replace bash -xc 'python {}/part1.py {}/input.txt; python {}/part2.py' 24 | + python day01/part1.py 25 | 68787 26 | > 7531 μs 27 | + python day01/part2.py 28 | 198041 29 | > 7689 μs 30 | + python day02/part1.py 31 | 13268 32 | > 6047 μs 33 | + python day02/part2.py 34 | 15508 35 | > 5889 μs 36 | + python day03/part1.py 37 | 8123 38 | > 1949 μs 39 | + python day03/part2.py 40 | 2620 41 | > 868 μs 42 | + python day04/part1.py 43 | 651 44 | > 4654 μs 45 | + python day04/part2.py 46 | 956 47 | > 4509 μs 48 | + python day05/part1.py 49 | QPJPLMNNR 50 | > 6565 μs 51 | + python day05/part2.py 52 | BQDNWJPVJ 53 | > 2408 μs 54 | + python day06/part1.py 55 | 1100 56 | > 5985 μs 57 | + python day06/part2.py 58 | 2421 59 | > 8177 μs 60 | + python day07/part1.py 61 | 1783610 62 | > 18417 μs 63 | + python day07/part2.py 64 | 4370655 65 | > 18053 μs 66 | + python day08/part1.py 67 | 1698 68 | > 27564 μs 69 | + python day08/part2.py 70 | 672280 71 | > 87745 μs 72 | + python day09/part1.py 73 | 6311 74 | > 61124 μs 75 | + python day09/part2.py 76 | 2482 77 | > 61482 μs 78 | + python day10/part1.py 79 | 15880 80 | > 386 μs 81 | + python day10/part2.py 82 | ###..#.....##..####.#..#..##..####..##. 83 | #..#.#....#..#.#....#.#..#..#....#.#..# 84 | #..#.#....#....###..##...#..#...#..#... 85 | ###..#....#.##.#....#.#..####..#...#.## 86 | #....#....#..#.#....#.#..#..#.#....#..# 87 | #....####..###.#....#..#.#..#.####..### 88 | > 1063 μs 89 | + python day11/part1.py 90 | 54036 91 | > 13501 μs 92 | + python day11/part2.py 93 | 13237873355 94 | > 181 ms 95 | + python day12/part1.py 96 | 408 97 | > 152 ms 98 | + python day12/part2.py 99 | 399 100 | > 141 ms 101 | + python day13/part1.py 102 | 6656 103 | > 62574 μs 104 | + python day13/part2.py 105 | 19716 106 | > 123 ms 107 | + python day14/part1.py 108 | 610 109 | > 28146 μs 110 | + python day14/part2.py 111 | 27194 112 | > 513 ms 113 | + python day15/part1.py 114 | 5256611 115 | > 3974 ms 116 | + python day15/part2.py 117 | 13337919186981 118 | > 246 ms (z3) 119 | 13337919186981 120 | > 6568 ms 121 | + python day16/part1.py 122 | 1595 123 | > 274 ms 124 | + python day16/part2.py 125 | 2189 126 | > 1399 ms 127 | + python day17/part1.py 128 | 3171 129 | > 90752 μs 130 | + python day17/part2.py 131 | 1586627906921 132 | > 3393 ms 133 | + python day18/part1.py 134 | 3494 135 | > 25009 μs 136 | + python day18/part2.py 137 | 2062 138 | > 61109 μs 139 | + python day19/part1.py 140 | 1404 141 | > 21075 ms 142 | + python day19/part2.py 143 | 5880 144 | > 52485 ms 145 | + python day20/part1.py 146 | 13522 147 | > 2833 ms 148 | + python day20/part2.py 149 | 17113168880158 150 | > 28497 ms 151 | + python day21/part1.py 152 | 194058098264286 153 | > 36037 μs 154 | + python day21/part2.py 155 | 3592056845086 156 | > 1240 ms (z3) 157 | Alexandra, please stop cheating 158 | 3592056845086 159 | > 84305 μs 160 | + python day22/part1.py 161 | 122082 162 | > 109 ms 163 | + python day22/part2.py 164 | 134076 165 | > 112 ms 166 | + python day23/part1.py 167 | Alexandra, call me 168 | 3940 169 | > 187 ms 170 | + python day23/part2.py 171 | 990 172 | > 4703 ms 173 | + python day24/part1.py 174 | 225 175 | > 3267 ms 176 | + python day24/part2.py 177 | 711 178 | > 58674 ms 179 | + python day25/part1.py 180 | 2=20---01==222=0=0-2 181 | > 338 ms (z3) 182 | 2=20---01==222=0=0-2 183 | > 2913 μs 184 | ``` 185 | -------------------------------------------------------------------------------- /day00/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day00/__init__.py -------------------------------------------------------------------------------- /day00/input.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/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/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/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 | return max( 15 | sum(int(line) for line in part.splitlines()) 16 | for part in s.split('\n\n') 17 | ) 18 | 19 | 20 | INPUT_S = '''\ 21 | 1000 22 | 2000 23 | 3000 24 | 25 | 4000 26 | 27 | 5000 28 | 6000 29 | 30 | 7000 31 | 8000 32 | 9000 33 | 34 | 10000 35 | ''' 36 | EXPECTED = 24000 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 | -------------------------------------------------------------------------------- /day01/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (n); 6 | WITH RECURSIVE 7 | nn (i, n, rest) 8 | AS ( 9 | SELECT 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 13 | WHEN '' THEN nn.i + 1 14 | ELSE nn.i 15 | END, 16 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 17 | WHEN '' THEN 0 18 | ELSE nn.n + SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 19 | END, 20 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 21 | FROM nn 22 | WHERE nn.rest != '' 23 | ) 24 | INSERT INTO vals 25 | SELECT MAX(nn.n) FROM nn GROUP BY nn.i; 26 | 27 | SELECT MAX(n) FROM vals; 28 | -------------------------------------------------------------------------------- /day01/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 | ns = sorted( 15 | sum(int(line) for line in part.splitlines()) 16 | for part in s.split('\n\n') 17 | ) 18 | return sum(ns[-3:]) 19 | 20 | 21 | INPUT_S = '''\ 22 | 1000 23 | 2000 24 | 3000 25 | 26 | 4000 27 | 28 | 5000 29 | 6000 30 | 31 | 7000 32 | 8000 33 | 9000 34 | 35 | 10000 36 | ''' 37 | EXPECTED = 45000 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ('input_s', 'expected'), 42 | ( 43 | (INPUT_S, EXPECTED), 44 | ), 45 | ) 46 | def test(input_s: str, expected: int) -> None: 47 | assert compute(input_s) == expected 48 | 49 | 50 | def main() -> int: 51 | parser = argparse.ArgumentParser() 52 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 53 | args = parser.parse_args() 54 | 55 | with open(args.data_file) as f, support.timing(): 56 | print(compute(f.read())) 57 | 58 | return 0 59 | 60 | 61 | if __name__ == '__main__': 62 | raise SystemExit(main()) 63 | -------------------------------------------------------------------------------- /day01/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (n); 6 | WITH RECURSIVE 7 | nn (i, n, rest) 8 | AS ( 9 | SELECT 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 13 | WHEN '' THEN nn.i + 1 14 | ELSE nn.i 15 | END, 16 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 17 | WHEN '' THEN 0 18 | ELSE nn.n + SUBSTR(nn.rest, 0, INSTR(nn.rest, char(10))) 19 | END, 20 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 21 | FROM nn 22 | WHERE nn.rest != '' 23 | ) 24 | INSERT INTO vals 25 | SELECT MAX(nn.n) FROM nn GROUP BY nn.i; 26 | 27 | SELECT SUM(n) FROM (SELECT * FROM VALS ORDER BY n DESC LIMIT 3); 28 | -------------------------------------------------------------------------------- /day02/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/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 | shape = {'R': 1, 'P': 2, 'S': 3} 13 | win = {'R': 'S', 'P': 'R', 'S': 'P'} 14 | trans = { 15 | 'A': 'R', 'B': 'P', 'C': 'S', 16 | 'X': 'R', 'Y': 'P', 'Z': 'S', 17 | } 18 | 19 | 20 | def compute(s: str) -> int: 21 | for k, v in trans.items(): 22 | s = s.replace(k, v) 23 | 24 | n = 0 25 | for line in s.splitlines(): 26 | a, b = line.split() 27 | if a == b: 28 | n += 3 29 | elif win[a] != b: 30 | n += 6 31 | else: 32 | pass 33 | n += shape[b] 34 | return n 35 | 36 | 37 | INPUT_S = '''\ 38 | A Y 39 | B X 40 | C Z 41 | ''' 42 | EXPECTED = 15 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/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE weights (choice, weight_val); 6 | INSERT INTO weights VALUES ('A', 1), ('B', 2), ('C', 3); 7 | 8 | CREATE TABLE vals (them, us); 9 | WITH RECURSIVE 10 | nn (them, us, rest) 11 | AS ( 12 | SELECT NULL, NULL, (SELECT s || char(10) FROM input) 13 | UNION ALL 14 | SELECT 15 | SUBSTR(nn.rest, 1, 1), 16 | REPLACE( 17 | REPLACE( 18 | REPLACE( 19 | SUBSTR(nn.rest, 3, 1), 20 | 'X', 'A' 21 | ), 22 | 'Y', 'B' 23 | ), 24 | 'Z', 'C' 25 | ), 26 | SUBSTR(nn.rest, 5) 27 | FROM nn 28 | WHERE nn.rest != '' 29 | ) 30 | INSERT INTO vals 31 | SELECT them, us FROM nn WHERE them IS NOT NULL; 32 | 33 | SELECT SUM(outcome + weight_val) FROM ( 34 | SELECT 35 | CASE 36 | WHEN them = us THEN 3 37 | WHEN them = 'A' AND us = 'B' THEN 6 38 | WHEN them = 'B' AND us = 'C' THEN 6 39 | WHEN them = 'C' AND us = 'A' THEN 6 40 | ELSE 0 41 | END AS outcome, 42 | weight_val 43 | FROM vals 44 | INNER JOIN weights ON vals.us = weights.choice 45 | ); 46 | -------------------------------------------------------------------------------- /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 | shape = {'R': 1, 'P': 2, 'S': 3} 13 | win = {'R': 'S', 'P': 'R', 'S': 'P'} 14 | lose = {v: k for k, v in win.items()} 15 | trans = {'A': 'R', 'B': 'P', 'C': 'S'} 16 | 17 | 18 | def compute(s: str) -> int: 19 | for k, v in trans.items(): 20 | s = s.replace(k, v) 21 | 22 | n = 0 23 | for line in s.splitlines(): 24 | a, b = line.split() 25 | if b == 'X': 26 | n += shape[win[a]] 27 | elif b == 'Y': 28 | n += shape[a] + 3 29 | else: 30 | n += shape[lose[a]] + 6 31 | return n 32 | 33 | 34 | INPUT_S = '''\ 35 | A Y 36 | B X 37 | C Z 38 | ''' 39 | EXPECTED = 12 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ('input_s', 'expected'), 44 | ( 45 | (INPUT_S, EXPECTED), 46 | ), 47 | ) 48 | def test(input_s: str, expected: int) -> None: 49 | assert compute(input_s) == expected 50 | 51 | 52 | def main() -> int: 53 | parser = argparse.ArgumentParser() 54 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 55 | args = parser.parse_args() 56 | 57 | with open(args.data_file) as f, support.timing(): 58 | print(compute(f.read())) 59 | 60 | return 0 61 | 62 | 63 | if __name__ == '__main__': 64 | raise SystemExit(main()) 65 | -------------------------------------------------------------------------------- /day02/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE weights (choice, weight_val); 6 | INSERT INTO weights VALUES ('A', 1), ('B', 2), ('C', 3); 7 | 8 | CREATE TABLE win (a, b); 9 | INSERT INTO win VALUES ('A', 'C'), ('B', 'A'), ('C', 'B'); 10 | 11 | CREATE TABLE vals (them, us); 12 | WITH RECURSIVE 13 | nn (them, us, rest) 14 | AS ( 15 | SELECT NULL, NULL, (SELECT s || char(10) FROM input) 16 | UNION ALL 17 | SELECT 18 | SUBSTR(nn.rest, 1, 1), 19 | CASE SUBSTR(nn.rest, 3, 1) 20 | WHEN 'X' THEN (SELECT b FROM win WHERE a = SUBSTR(nn.rest, 1, 1)) 21 | WHEN 'Y' THEN SUBSTR(nn.rest, 1, 1) 22 | WHEN 'Z' THEN (SELECT a FROM win WHERE b = SUBSTR(nn.rest, 1, 1)) 23 | END, 24 | SUBSTR(nn.rest, 5) 25 | FROM nn 26 | WHERE nn.rest != '' 27 | ) 28 | INSERT INTO vals 29 | SELECT them, us FROM nn WHERE them IS NOT NULL; 30 | 31 | SELECT SUM(outcome + weight_val) FROM ( 32 | SELECT 33 | CASE 34 | WHEN them = us THEN 3 35 | WHEN them = 'A' AND us = 'B' THEN 6 36 | WHEN them = 'B' AND us = 'C' THEN 6 37 | WHEN them = 'C' AND us = 'A' THEN 6 38 | ELSE 0 39 | END AS outcome, 40 | weight_val 41 | FROM vals 42 | INNER JOIN weights ON vals.us = weights.choice 43 | ); 44 | -------------------------------------------------------------------------------- /day03/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day03/__init__.py -------------------------------------------------------------------------------- /day03/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 | 16 | lines = s.splitlines() 17 | for line in lines: 18 | first_half = line[:len(line)//2] 19 | second_half = line[len(line)//2:] 20 | s, = set(first_half) & set(second_half) 21 | if s.islower(): 22 | total += 1 + (ord(s) - ord('a')) 23 | else: 24 | total += 27 + (ord(s) - ord('A')) 25 | return total 26 | 27 | 28 | INPUT_S = '''\ 29 | vJrwpWtwJgWrhcsFMMfFFhFp 30 | jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL 31 | PmmdzqPrVvPwwTWBwg 32 | wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn 33 | ttgJtRGJQctTZtZT 34 | CrZsJsPPZsGzwwsLwLmpwMDw 35 | ''' 36 | EXPECTED = 157 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 | -------------------------------------------------------------------------------- /day03/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE parts (i, l, r); 6 | WITH RECURSIVE 7 | nn (i, l, r, rest) 8 | AS ( 9 | SELECT -1, NULL, NULL, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | nn.i + 1, 13 | SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) / 2), 14 | SUBSTR(nn.rest, 1 + INSTR(nn.rest, char(10)) / 2, INSTR(nn.rest, char(10)) / 2), 15 | SUBSTR(nn.rest, 1 + INSTR(nn.rest, char(10))) 16 | FROM nn 17 | WHERE nn.rest != '' 18 | ) 19 | INSERT INTO parts 20 | SELECT i, l, r FROM nn WHERE l IS NOT NULL; 21 | 22 | CREATE TABLE folded(i, l, c); 23 | WITH RECURSIVE 24 | nn (i, l, c, rest) 25 | AS ( 26 | SELECT 27 | 0, 1, 28 | (SELECT SUBSTR(l, 1, 1) FROM parts WHERE i = 0), 29 | (SELECT SUBSTR(l, 2) FROM parts WHERE i = 0) 30 | UNION ALL 31 | SELECT 32 | CASE 33 | WHEN nn.rest = '' AND nn.l = 0 THEN nn.i + 1 34 | ELSE nn.i 35 | END, 36 | CASE 37 | WHEN nn.rest = '' THEN (NOT nn.l) 38 | ELSE nn.l 39 | END, 40 | CASE 41 | WHEN nn.rest = '' AND nn.l = 1 THEN 42 | (SELECT SUBSTR(r, 1, 1) FROM parts WHERE i = nn.i) 43 | WHEN nn.rest = '' AND nn.l = 0 THEN 44 | (SELECT SUBSTR(l, 1, 1) FROM parts WHERE i = nn.i + 1) 45 | ELSE SUBSTR(rest, 1, 1) 46 | END, 47 | CASE 48 | WHEN nn.rest = '' AND nn.l = 1 THEN 49 | (SELECT SUBSTR(r, 2) FROM parts WHERE i = nn.i) 50 | WHEN nn.rest = '' AND nn.l = 0 THEN 51 | (SELECT SUBSTR(l, 2) FROM parts WHERE i = nn.i + 1) 52 | ELSE SUBSTR(rest, 2) 53 | END 54 | FROM nn 55 | WHERE nn.i <= (SELECT MAX(i) FROM parts) 56 | ) 57 | INSERT INTO folded 58 | SELECT i, l, c FROM nn WHERE nn.i <= (SELECT MAX(i) FROM parts); 59 | 60 | 61 | SELECT SUM( 62 | CASE 63 | WHEN UNICODE(c) < 97 THEN 27 + UNICODE(c) - UNICODE('A') 64 | ELSE 1 + UNICODE(c) - UNICODE('a') 65 | END 66 | ) 67 | FROM ( 68 | SELECT folded.c 69 | FROM folded 70 | INNER JOIN folded folded2 71 | ON folded.i = folded2.i AND folded.l != folded2.l AND folded.c = folded2.c 72 | GROUP BY folded.i 73 | ); 74 | -------------------------------------------------------------------------------- /day03/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 | 16 | items = iter(s.splitlines()) 17 | while True: 18 | try: 19 | s, = set(next(items)) & set(next(items)) & set(next(items)) 20 | except StopIteration: 21 | break 22 | else: 23 | if s.islower(): 24 | total += 1 + (ord(s) - ord('a')) 25 | else: 26 | total += 27 + (ord(s) - ord('A')) 27 | return total 28 | 29 | 30 | INPUT_S = '''\ 31 | vJrwpWtwJgWrhcsFMMfFFhFp 32 | jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL 33 | PmmdzqPrVvPwwTWBwg 34 | wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn 35 | ttgJtRGJQctTZtZT 36 | CrZsJsPPZsGzwwsLwLmpwMDw 37 | ''' 38 | EXPECTED = 70 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 | -------------------------------------------------------------------------------- /day03/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE folded (g, i, c); 6 | WITH RECURSIVE 7 | nn (i, c, rest) 8 | AS ( 9 | SELECT 10 | 0, 11 | (SELECT SUBSTR(s, 1, 1) FROM input), 12 | (SELECT SUBSTR(s, 2) || char(10) FROM input) 13 | UNION ALL 14 | SELECT 15 | CASE 16 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN nn.i + 1 17 | ELSE nn.i 18 | END, 19 | CASE 20 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN SUBSTR(nn.rest, 2, 1) 21 | ELSE SUBSTR(nn.rest, 1, 1) 22 | END, 23 | CASE 24 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN SUBSTR(nn.rest, 3) 25 | ELSE SUBSTR(nn.rest, 2) 26 | END 27 | FROM nn 28 | WHERE nn.rest != char(10) 29 | ) 30 | INSERT INTO folded 31 | SELECT i / 3, i % 3, c FROM nn; 32 | 33 | SELECT SUM( 34 | CASE 35 | WHEN UNICODE(c) < 97 THEN 27 + UNICODE(c) - UNICODE('A') 36 | ELSE 1 + UNICODE(c) - UNICODE('a') 37 | END 38 | ) 39 | FROM ( 40 | SELECT folded.c 41 | FROM folded 42 | INNER JOIN folded folded2 ON folded.g = folded2.g AND folded.c = folded2.c 43 | INNER JOIN folded folded3 ON folded.g = folded3.g AND folded.c = folded3.c 44 | WHERE folded.i = 0 AND folded2.i = 1 AND folded3.i = 2 45 | GROUP BY folded.g 46 | ); 47 | -------------------------------------------------------------------------------- /day04/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/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 | n = 0 15 | for line in s.splitlines(): 16 | ab, cd = line.split(',') 17 | a_s, b_s = ab.split('-') 18 | c_s, d_s = cd.split('-') 19 | a, b = int(a_s), int(b_s) 20 | c, d = int(c_s), int(d_s) 21 | 22 | if a <= c <= d <= b: 23 | n += 1 24 | elif c <= a <= b <= d: 25 | n += 1 26 | 27 | return n 28 | 29 | 30 | INPUT_S = '''\ 31 | 2-4,6-8 32 | 2-3,4-5 33 | 5-7,7-9 34 | 2-8,3-7 35 | 6-6,4-6 36 | 2-6,4-8 37 | ''' 38 | EXPECTED = 2 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 | -------------------------------------------------------------------------------- /day04/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (a INT, b INT, c INT, d INT); 6 | WITH RECURSIVE 7 | nn (a, b, c, d, rest) 8 | AS ( 9 | SELECT -1, -1, -1, -1, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, '-') - 1), 13 | SUBSTR( 14 | nn.rest, 15 | 1 + INSTR(nn.rest, '-'), 16 | INSTR(nn.rest, ',') - INSTR(nn.rest, '-') - 1 17 | ), 18 | SUBSTR( 19 | nn.rest, 20 | 1 + INSTR(nn.rest, ','), 21 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-') - 1 22 | ), 23 | SUBSTR( 24 | nn.rest, 25 | 1 + INSTR(nn.rest, ',') + 26 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-'), 27 | INSTR(nn.rest, char(10)) - ( 28 | 1 + INSTR(nn.rest, ',') + 29 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-') 30 | ) 31 | ), 32 | SUBSTR(nn.rest, 1 + INSTR(nn.rest, char(10))) 33 | FROM nn 34 | WHERE nn.rest != '' 35 | ) 36 | INSERT INTO vals 37 | SELECT a, b, c, d FROM nn WHERE a != -1; 38 | 39 | SELECT 40 | SUM((a <= c AND d <= b) OR (c <= a AND b <= d)) 41 | FROM vals; 42 | -------------------------------------------------------------------------------- /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 | n = 0 15 | for line in s.splitlines(): 16 | ab, cd = line.split(',') 17 | a_s, b_s = ab.split('-') 18 | c_s, d_s = cd.split('-') 19 | a, b = int(a_s), int(b_s) 20 | c, d = int(c_s), int(d_s) 21 | 22 | if a <= c <= b: 23 | n += 1 24 | elif c <= a <= d: 25 | n += 1 26 | 27 | return n 28 | 29 | 30 | INPUT_S = '''\ 31 | 2-4,6-8 32 | 2-3,4-5 33 | 5-7,7-9 34 | 2-8,3-7 35 | 6-6,4-6 36 | 2-6,4-8 37 | ''' 38 | EXPECTED = 4 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 | -------------------------------------------------------------------------------- /day04/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (a INT, b INT, c INT, d INT); 6 | WITH RECURSIVE 7 | nn (a, b, c, d, rest) 8 | AS ( 9 | SELECT -1, -1, -1, -1, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, '-') - 1), 13 | SUBSTR( 14 | nn.rest, 15 | 1 + INSTR(nn.rest, '-'), 16 | INSTR(nn.rest, ',') - INSTR(nn.rest, '-') - 1 17 | ), 18 | SUBSTR( 19 | nn.rest, 20 | 1 + INSTR(nn.rest, ','), 21 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-') - 1 22 | ), 23 | SUBSTR( 24 | nn.rest, 25 | 1 + INSTR(nn.rest, ',') + 26 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-'), 27 | INSTR(nn.rest, char(10)) - ( 28 | 1 + INSTR(nn.rest, ',') + 29 | INSTR(SUBSTR(nn.rest, 1 + INSTR(nn.rest, ',')), '-') 30 | ) 31 | ), 32 | SUBSTR(nn.rest, 1 + INSTR(nn.rest, char(10))) 33 | FROM nn 34 | WHERE nn.rest != '' 35 | ) 36 | INSERT INTO vals 37 | SELECT a, b, c, d FROM nn WHERE a != -1; 38 | 39 | SELECT 40 | SUM((a <= c AND c <= b) OR (c <= a AND a <= d)) 41 | FROM vals; 42 | -------------------------------------------------------------------------------- /day05/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day05/__init__.py -------------------------------------------------------------------------------- /day05/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 | first, rest = s.split('\n\n') 15 | 16 | lastline_len = len(first.splitlines()[-1].rstrip()) 17 | stacks: list[list[str]] 18 | stacks = [[] for _ in range((lastline_len + 2) // 4)] 19 | 20 | for line in first.splitlines(): 21 | for i, c in enumerate(line[1::4]): 22 | if not c.isspace(): 23 | stacks[i].append(c) 24 | 25 | for stack in stacks: 26 | stack.reverse() 27 | 28 | for instr in rest.splitlines(): 29 | _, n_s, _, f_s, _, d_s = instr.split() 30 | n, f, d = int(n_s), int(f_s), int(d_s) 31 | 32 | for _ in range(n): 33 | stacks[d - 1].append(stacks[f - 1].pop()) 34 | 35 | return ''.join(stack[-1] if stack else '' for stack in stacks) 36 | 37 | 38 | INPUT_S = '''\ 39 | [D] 40 | [N] [C] 41 | [Z] [M] [P] 42 | 1 2 3 43 | 44 | move 1 from 2 to 1 45 | move 3 from 1 to 3 46 | move 2 from 2 to 1 47 | move 1 from 1 to 2 48 | ''' 49 | EXPECTED = 'CMZ' 50 | 51 | 52 | @pytest.mark.parametrize( 53 | ('input_s', 'expected'), 54 | ( 55 | (INPUT_S, EXPECTED), 56 | ), 57 | ) 58 | def test(input_s: str, expected: int) -> None: 59 | assert compute(input_s) == expected 60 | 61 | 62 | def main() -> int: 63 | parser = argparse.ArgumentParser() 64 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 65 | args = parser.parse_args() 66 | 67 | with open(args.data_file) as f, support.timing(): 68 | print(compute(f.read())) 69 | 70 | return 0 71 | 72 | 73 | if __name__ == '__main__': 74 | raise SystemExit(main()) 75 | -------------------------------------------------------------------------------- /day05/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE input2 (map STRING, instr STRING); 6 | INSERT INTO input2 7 | SELECT 8 | SUBSTR(s, 1, INSTR(s, char(10) || char(10))), 9 | SUBSTR(s, INSTR(s, char(10) || char(10)) + 2) || char(10) 10 | FROM input; 11 | 12 | CREATE TABLE stacks (col INT, idx INT NULL, c VARCHAR); 13 | CREATE TRIGGER t_stacks AFTER INSERT ON stacks FOR EACH ROW BEGIN 14 | UPDATE stacks 15 | SET idx = (SELECT COUNT(1) FROM stacks WHERE col = NEW.col) - 1 16 | WHERE col = NEW.col AND idx IS NULL; 17 | END; 18 | 19 | WITH RECURSIVE 20 | nn (rid, just_finished_line, idx, c, rest) 21 | AS ( 22 | SELECT 0, 1, -1, ' ', (SELECT map FROM input2) 23 | UNION ALL 24 | SELECT 25 | nn.rid + 1, 26 | CASE WHEN INSTR(nn.rest, char(10)) < 5 THEN 1 ELSE 0 END, 27 | CASE WHEN nn.just_finished_line THEN 1 ELSE nn.idx + 1 END, 28 | SUBSTR(nn.rest, 2, 1), 29 | CASE 30 | WHEN INSTR(nn.rest, char(10)) < 5 THEN 31 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 32 | ELSE 33 | SUBSTR(nn.rest, 5) 34 | END 35 | FROM nn 36 | WHERE nn.rest != '' 37 | ) 38 | INSERT INTO stacks 39 | SELECT idx, NULL, c FROM nn WHERE c != ' ' ORDER BY nn.rid DESC; 40 | 41 | CREATE TABLE raw_instructions (rid INT, n INT, src INT, dest INT); 42 | WITH RECURSIVE 43 | nn (rid, n, src, dest, rest) 44 | AS ( 45 | SELECT -1, -1, -1, -1, (SELECT instr FROM input2) 46 | UNION ALL 47 | SELECT 48 | nn.rid + 1, 49 | SUBSTR(nn.rest, 6, INSTR(nn.rest, " from ") - 6), 50 | SUBSTR( 51 | nn.rest, 52 | INSTR(nn.rest, " from ") + 6, 53 | INSTR(nn.rest, " to ") - (INSTR(nn.rest, " from ") + 6) 54 | ), 55 | SUBSTR( 56 | nn.rest, 57 | INSTR(nn.rest, " to ") + 4, 58 | INSTR(nn.rest, char(10)) - (INSTR(nn.rest, " to ") + 4) 59 | ), 60 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 61 | FROM nn 62 | WHERE nn.rest != '' 63 | ) 64 | INSERT INTO raw_instructions 65 | SELECT nn.rid, n, src, dest FROM nn WHERE n > 0; 66 | 67 | CREATE TABLE instructions (src INT, dest INT); 68 | CREATE TRIGGER t_instructions AFTER INSERT ON instructions FOR EACH ROW BEGIN 69 | UPDATE stacks 70 | SET 71 | col = NEW.dest, 72 | idx = (SELECT MAX(idx) + 1 FROM stacks WHERE col = NEW.dest) 73 | WHERE 74 | col = NEW.src AND 75 | idx = (SELECT MAX(idx) FROM stacks WHERE col = NEW.src); 76 | END; 77 | 78 | WITH RECURSIVE 79 | nn (rid, n, src, dest) 80 | AS ( 81 | SELECT rid, n, src, dest FROM raw_instructions WHERE rid = 0 82 | UNION ALL 83 | SELECT 84 | CASE WHEN nn.n = 1 THEN nn.rid + 1 ELSE nn.rid END, 85 | CASE 86 | WHEN nn.n = 1 THEN 87 | ( 88 | SELECT raw_instructions.n 89 | FROM raw_instructions 90 | WHERE rid = nn.rid + 1 91 | ) 92 | ELSE 93 | nn.n - 1 94 | END, 95 | CASE 96 | WHEN nn.n = 1 THEN 97 | ( 98 | SELECT raw_instructions.src 99 | FROM raw_instructions 100 | WHERE rid = nn.rid + 1 101 | ) 102 | ELSE 103 | nn.src 104 | END, 105 | CASE 106 | WHEN nn.n = 1 THEN 107 | ( 108 | SELECT raw_instructions.dest 109 | FROM raw_instructions 110 | WHERE rid = nn.rid + 1 111 | ) 112 | ELSE 113 | nn.dest 114 | END 115 | FROM nn 116 | WHERE nn.rid < (SELECT MAX(rid) FROM raw_instructions) 117 | ) 118 | INSERT INTO instructions 119 | SELECT src, dest FROM nn; 120 | 121 | CREATE TABLE answer(c); 122 | INSERT INTO answer 123 | SELECT c FROM stacks 124 | WHERE idx = (SELECT MAX(idx) FROM stacks s2 WHERE s2.col = stacks.col) 125 | ORDER BY col ASC; 126 | 127 | WITH RECURSIVE 128 | nn (n, s) 129 | AS ( 130 | SELECT 0, '' 131 | UNION ALL 132 | SELECT nn.n + 1, nn.s || (SELECT c FROM answer WHERE ROWID = nn.n + 1) 133 | FROM nn 134 | WHERE nn.n < (SELECT MAX(ROWID) FROM answer) 135 | ) 136 | SELECT nn.s FROM nn ORDER BY nn.n DESC LIMIT 1; 137 | -------------------------------------------------------------------------------- /day05/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) -> str: 14 | first, rest = s.split('\n\n') 15 | 16 | lastline_len = len(first.splitlines()[-1].rstrip()) 17 | stacks: list[list[str]] 18 | stacks = [[] for _ in range((lastline_len + 2) // 4)] 19 | 20 | for line in first.splitlines(): 21 | for i, c in enumerate(line[1::4]): 22 | if not c.isspace(): 23 | stacks[i].append(c) 24 | 25 | for stack in stacks: 26 | stack.reverse() 27 | 28 | for instr in rest.splitlines(): 29 | _, n_s, _, f_s, _, d_s = instr.split() 30 | n, f, d = int(n_s), int(f_s), int(d_s) 31 | 32 | victims = stacks[f - 1][-n:] 33 | del stacks[f - 1][-n:] 34 | 35 | stacks[d - 1].extend(victims) 36 | 37 | return ''.join(stack[-1] if stack else '' for stack in stacks) 38 | 39 | 40 | INPUT_S = '''\ 41 | [D] 42 | [N] [C] 43 | [Z] [M] [P] 44 | 1 2 3 45 | 46 | move 1 from 2 to 1 47 | move 3 from 1 to 3 48 | move 2 from 2 to 1 49 | move 1 from 1 to 2 50 | ''' 51 | EXPECTED = 'MCD' 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 | -------------------------------------------------------------------------------- /day05/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE input2 (map STRING, instr STRING); 6 | INSERT INTO input2 7 | SELECT 8 | SUBSTR(s, 1, INSTR(s, char(10) || char(10))), 9 | SUBSTR(s, INSTR(s, char(10) || char(10)) + 2) || char(10) 10 | FROM input; 11 | 12 | CREATE TABLE stacks (col INT, idx INT NULL, c VARCHAR); 13 | CREATE TRIGGER t_stacks AFTER INSERT ON stacks FOR EACH ROW BEGIN 14 | UPDATE stacks 15 | SET idx = (SELECT COUNT(1) FROM stacks WHERE col = NEW.col) - 1 16 | WHERE col = NEW.col AND idx IS NULL; 17 | END; 18 | 19 | WITH RECURSIVE 20 | nn (rid, just_finished_line, idx, c, rest) 21 | AS ( 22 | SELECT 0, 1, -1, ' ', (SELECT map FROM input2) 23 | UNION ALL 24 | SELECT 25 | nn.rid + 1, 26 | CASE WHEN INSTR(nn.rest, char(10)) < 5 THEN 1 ELSE 0 END, 27 | CASE WHEN nn.just_finished_line THEN 1 ELSE nn.idx + 1 END, 28 | SUBSTR(nn.rest, 2, 1), 29 | CASE 30 | WHEN INSTR(nn.rest, char(10)) < 5 THEN 31 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 32 | ELSE 33 | SUBSTR(nn.rest, 5) 34 | END 35 | FROM nn 36 | WHERE nn.rest != '' 37 | ) 38 | INSERT INTO stacks 39 | SELECT idx, NULL, c FROM nn WHERE c != ' ' ORDER BY nn.rid DESC; 40 | 41 | INSERT INTO stacks VALUES (99999, NULL, ''); 42 | 43 | CREATE TABLE raw_instructions (rid INT, n INT, src INT, dest INT); 44 | WITH RECURSIVE 45 | nn (rid, n, src, dest, rest) 46 | AS ( 47 | SELECT -2, -1, -1, -1, (SELECT instr FROM input2) 48 | UNION ALL 49 | SELECT 50 | nn.rid + 2, 51 | SUBSTR(nn.rest, 6, INSTR(nn.rest, " from ") - 6), 52 | SUBSTR( 53 | nn.rest, 54 | INSTR(nn.rest, " from ") + 6, 55 | INSTR(nn.rest, " to ") - (INSTR(nn.rest, " from ") + 6) 56 | ), 57 | SUBSTR( 58 | nn.rest, 59 | INSTR(nn.rest, " to ") + 4, 60 | INSTR(nn.rest, char(10)) - (INSTR(nn.rest, " to ") + 4) 61 | ), 62 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 63 | FROM nn 64 | WHERE nn.rest != '' 65 | ) 66 | INSERT INTO raw_instructions 67 | SELECT 68 | CASE z WHEN 0 THEN nn.rid ELSE nn.rid + 1 END, 69 | nn.n, 70 | CASE z WHEN 0 THEN nn.src ELSE 99999 END, 71 | CASE z WHEN 0 THEN 99999 ELSE nn.dest END 72 | FROM nn 73 | INNER JOIN (SELECT 0 AS z UNION ALL SELECT 1 AS z) 74 | WHERE n > 0 75 | ORDER BY rid; 76 | 77 | CREATE TABLE instructions (src INT, dest INT); 78 | CREATE TRIGGER t_instructions AFTER INSERT ON instructions FOR EACH ROW BEGIN 79 | UPDATE stacks 80 | SET 81 | col = NEW.dest, 82 | idx = (SELECT MAX(idx) + 1 FROM stacks WHERE col = NEW.dest) 83 | WHERE 84 | col = NEW.src AND 85 | idx = (SELECT MAX(idx) FROM stacks WHERE col = NEW.src); 86 | END; 87 | 88 | WITH RECURSIVE 89 | nn (rid, n, src, dest) 90 | AS ( 91 | SELECT rid, n, src, dest FROM raw_instructions WHERE rid = 0 92 | UNION ALL 93 | SELECT 94 | CASE WHEN nn.n = 1 THEN nn.rid + 1 ELSE nn.rid END, 95 | CASE 96 | WHEN nn.n = 1 THEN 97 | ( 98 | SELECT raw_instructions.n 99 | FROM raw_instructions 100 | WHERE rid = nn.rid + 1 101 | ) 102 | ELSE 103 | nn.n - 1 104 | END, 105 | CASE 106 | WHEN nn.n = 1 THEN 107 | ( 108 | SELECT raw_instructions.src 109 | FROM raw_instructions 110 | WHERE rid = nn.rid + 1 111 | ) 112 | ELSE 113 | nn.src 114 | END, 115 | CASE 116 | WHEN nn.n = 1 THEN 117 | ( 118 | SELECT raw_instructions.dest 119 | FROM raw_instructions 120 | WHERE rid = nn.rid + 1 121 | ) 122 | ELSE 123 | nn.dest 124 | END 125 | FROM nn 126 | WHERE nn.rid < (SELECT MAX(rid) FROM raw_instructions) 127 | ) 128 | INSERT INTO instructions 129 | SELECT src, dest FROM nn; 130 | 131 | CREATE TABLE answer(c); 132 | INSERT INTO answer 133 | SELECT c FROM stacks 134 | WHERE idx = (SELECT MAX(idx) FROM stacks s2 WHERE s2.col = stacks.col) 135 | ORDER BY col ASC; 136 | 137 | WITH RECURSIVE 138 | nn (n, s) 139 | AS ( 140 | SELECT 0, '' 141 | UNION ALL 142 | SELECT nn.n + 1, nn.s || (SELECT c FROM answer WHERE ROWID = nn.n + 1) 143 | FROM nn 144 | WHERE nn.n < (SELECT MAX(ROWID) FROM answer) 145 | ) 146 | SELECT nn.s FROM nn ORDER BY nn.n DESC LIMIT 1; 147 | -------------------------------------------------------------------------------- /day06/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day06/__init__.py -------------------------------------------------------------------------------- /day06/input.txt: -------------------------------------------------------------------------------- 1 | rgffbnnqvqhhmtmzzmmpmllcggsdswwvvwzzpptbppgngsnncwcnwcwnwgwrwrrnqnlqlccwggrcgrccjgcgwghgffjgjgrgmmsrrhchfcfdccjwwzdzcdcbcjjtfjjltlvvtstvttszsvsmmfccwcjwwzmzhhjvjpvjpppcwppdtdvtdvtvztzvzffdfqqgbgffrgrpgphhbcctssncsnncfcppvnpvnvfnfvnnfggtpplggwsggldllzvzrznncbbjpplbbrrnsrssmrsrhhmqmnqqhpqqnrqnrrtrnnrwrwhhpjhphlhvvgbgzztqtvvmssdshhqnhnjjvhjhqqbrqrrmrlrdllvrlvlccfvcffhfssgvsswhwqqfwfwhhcffjmmjrrbgrgjgrrvsrvvwmwsmmszzvmzvmmwmtthvvfbfllpjpwpwjpwpnpjpqqwtqtwwqpwqqrsqqcpqqssrgsrrjwwqmwmgwgrggmvmgmfgmffjfhjfjttcztccmtmmccnznhhcgcffmnfmmqjjhwwtccbzbhhwnhnsnmsnmnqqchhhnggbzbjzjzszsfsjfjvffnlffvrvddbhhqccdnnzwnznjnpnmppthhqddjhddqjqlqplqqjpjwppqttswsvwvfwflfplffnbfbwfwhhlhtlhtltzzmlmslmlwwgzwzrrwhhlnnrfnnmpppjpgpbbhpbhppbrppbhhzgzczqcqvqggwppcnnhnznvznzhhcpctptpjtppdhhprhrcrpcprpplrprzrnrfnrnlrlvrrfqqnnsqsqrssnrnvvvghvvbzzqppjsjnnrprwrrmqqtfqfwwrdrsddprddbdzbdbndbdjdljjrtjrrnzrzjrzzpssftfmfbflbbcpcsppwrppntnhthlthtbbsmsbmsmggpssnhnjjtsjjwssdjdwwppcbpcclmccwzzrggmgmnnwjnnqppjttvllmhhvcctptbpbdpdhdqqgggbnnqcchlhssltstllvqlvqvdvjdvvqvsvfvhfhlfftqffztzdttfvvzmvzznvnsnjjvqjvjljplpvvzlzhzghgddbbzbmbnztgthrnpsqhhdvprtpdftmqfvgjzgdvqwmvgwbczvbschqfhdvqcfnmbgmtqmlmsqcbfhshrzzbrtpgnwwtzgnjghzrlwhntprqhvshjcfvlnchccbtnswfnmpccdppqrqrhngvlwrpplbnpgzbzwtzwrsptmsmlndcfmbnqgvnqvzwpvrtfsrwfvfwdvplrfdddwcnwhzchmwfjsfvbtbrjchmgqwfvvmpzhqcbzhrcmjrzmgrtnzrcqdqdqnpwjctlhrjcphbbcvhvqnhtwjrvrbzfzfzzplshvrcchvtgtjcnhlzsttwdhcmdvrdlgsngvtzqsjrcptwwbgvfbsvgnmfhmvgqtfzbhhmdznjlsghhnlwzhvplfvlqzbrsjhdvrshjbnfqgpscbpzmnmmcsdbtwbwmsvfjwdvtctslcqfssrhczdptlrjbfzjctqrcbppcfcbqqzhhftdjchtscwgwcnpvrrjvnnwrqtrqmbgdfcpqhddnvdnmlmqcgsndwbcfvfrzsrflsnqrpmszqrdlshlcsfrmnsqmtrvjwqcllftscwrtmvcjsmrlqvdfjzgfdtswqzldqgjvhczpqrfbclnbwcjprjncvhgfmjhgfgnvfzfnvbbstgsgtspdhtfsncttlwmllbbvqftjtshfjlqtjwlvrbwmzfhdmcbhtqdzqtzmdljqprwhqwcvbtfqbpjwztgqrvlrqdwmqrqzvptsgjdqnqdfwfpjdglwgpdrcfrrzmpjmtbwwrqqcsnmphcqtthlnlzlfftrjmtjwwqrldcfjjclzrsqvltsfchfggcwbzbtpqcjfvgpwnwwqrdbdvjplgsgctdhqvttmtpmmjfcqdslhjgtdppbmzbrfbrrjncfdhlmjgdwjrmbgpcgctghbvphpgfvvfvtplqrhnjjhqntjvbsggvrvjgrwptcgqgrmjtprvhnvjsdsfqrqrrltlhvfsjsqpbwndbsnjzplcfqtfbdqdzdvnljcsmjnrmwwjzqwsjbdlclsmccbqwnlpltqhqhmthhrdjwlqwrbltghsvblqntvjqzpmmpqwrrwvhmtfqzhrmjfglwfpthhgbhdmrtprtqgqbdcfqlbbvmzmchzglmpzdvhpqchhclbcvrcmhtzqfzrplbvmdhghsttzgdjbgmmtvlqjsstgwlwchhwmflwbgljgszghvfdsngbtbcnhzmmdmsjqfbcsmgpjwjqgwqdlrgznnmhphbrzcgnqczpcbljjmhnnrmzlpqbswwcjpjqcrtdmgprpmmbprwlmpzcmwlnbzqjsgftrncvmjmjwvqgszsrmrczhjnwlghgndhbthwlhfbncrwqfgnshrmdfhwmfvllhvbmmllwfbgrnrttpzffzvcfjfbrmdqfcfrdqsltpbbttnqnbncfhwghfbgwrltbhwmmlpczsvvdnwhchgfplnjsttsqvsznwvlwzgfnhfrrgplsfwrwnbfhblwtjpldjmzrglhppgsjsgzssbbgrrbdmpwhgjjmlfnpdbvtntcwrvltgfnrgwttvvjhqljjvvnmztwgzcclgbjnjspzwnpgtgvhhhqbzrnvmdcljcdncvpnpdhrhqwvlhllbsrmjbzlgczwjsrvqrqfqmtjrnpbtdhsrfbhrgtgnrmlgcwcmrvrdfcqbfcqczjchgrjpzqfqqmcpvnvbrssfrfsrjmlngplqqsnclgvpfhjbzdppjftlgbmcgdbrvqrclvtdnzhhcmzlrdrgbrqrlvfcnvznnqwswvhdpbbjmsqzcrmjngzmdftjzsjvgfdtcvdwhnwwjlcgcmflzzpzpgzrvmptbjgrdjbzscrglhcjppdjdshnvvbsnnddtbwsdrpnbnlrtppcrzbbjrfzhbwgmjqtmgjvmrjgfdndlmqvhgwprsjnhcmrnfdfdzggjjzcccnsnwwbjzmsfjjfhclptfcwqjhnphpwzszjmsrtnbqpqsvcfcvrrgclwlbrqngdcdlzrdfpvbgtznjzdvjngrgswhjnrpgswtqflpgvnpbsvcrtgtzcbhdjpbjwnpdzcmprtzlppbqvpzrrlbssmwhhvqqpmwggnzwcfdnzccjrrncnvjqgbpstctwpqvhdsbcsjrpbzbdlwwvjngtbnvpthppglgsnrbwnvpzldhsgwqhclgwrdsvcfdclvdsbrhcfnbgrtqswstnjfmfrdgphpvgjfqzcwnpcghdhtflgdnhhnnnwrpnmppszgqdcqjqpnccfsqjsmwtqvzwfqvtwtthwswmcqqcqjwcgwgzgmnpzbfpzrqbvqhbhtsvvbppbmqzfnsmwppzvsctgcncztsqdfhnlwjjtvwcchvnphwpnpzqsjhlrcmnbthtqlfclnsfdtpwfzljdpfszvhgzdpzhbjtphbfbmwgfrfplntfqhgpbjzghmblzzhdcbptngqwrtfmvtbhphpvdwpswbscrwzhnvqfwlbwwqqgstgmbqlllspbhbpjmmrfhmjwzdnsdwmpvlrspgzmdjwqwcjpbgwspsghjdvrbbsbtgwptqdvvdhbhqbdhpbzdsdwsjdbjnztdqqrdhwhtpvcbblbmjgmbpqghdsfthzjffrdvldfsnpcsmnzwzcqlnvcrcbqnfcdrfbffrvhqsbqjnnbzsqvqqdqwmsvbqvtgmnnlthpngfsljqnrdhhzpmwsnqvrgdnvlbgnndfcpjfgmzqssvnnrwbmslcqpnhnnwzggsjvqcsqpfzjcmcppntmsdtfggzdncqbfjsqvbzgnnlsdbgjqffsmvnbqlsrjwdmcjbrsrpchnnhdtlcdhfdltmlvtrwjpzphtbhzzrlrwbbhhpntgbcfmphnbrjdhrhvmvhfglrncngjdsvbfrqtqzsgvlzjqzcqnwdcfgvnpsqpphwfsdvpgnchjnnjhsqgvlqcvhzzzcrsqcvrbsbjbdbgddlbb 2 | -------------------------------------------------------------------------------- /day06/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 compute(s: str) -> int: 15 | d: collections.deque[str] = collections.deque(maxlen=4) 16 | 17 | for i, c in enumerate(s.strip()): 18 | d.append(c) 19 | 20 | if len(d) == d.maxlen and len(set(d)) == d.maxlen: 21 | return i + 1 22 | 23 | raise NotImplementedError('wat') 24 | 25 | 26 | INPUT_S = '''\ 27 | bvwbjplbgvbhsrlpgdmjqwftvncz 28 | ''' 29 | EXPECTED = 5 30 | 31 | 32 | @pytest.mark.parametrize( 33 | ('input_s', 'expected'), 34 | ( 35 | (INPUT_S, EXPECTED), 36 | ), 37 | ) 38 | def test(input_s: str, expected: int) -> None: 39 | assert compute(input_s) == expected 40 | 41 | 42 | def main() -> int: 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 45 | args = parser.parse_args() 46 | 47 | with open(args.data_file) as f, support.timing(): 48 | print(compute(f.read())) 49 | 50 | return 0 51 | 52 | 53 | if __name__ == '__main__': 54 | raise SystemExit(main()) 55 | -------------------------------------------------------------------------------- /day06/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE answers (n INT); 6 | CREATE TABLE chars (c VARCHAR); 7 | CREATE TRIGGER t_chars AFTER INSERT ON chars FOR EACH ROW BEGIN 8 | INSERT INTO answers 9 | SELECT (SELECT COUNT(1) FROM chars) 10 | WHERE ( 11 | ( 12 | SELECT COUNT(DISTINCT c) FROM ( 13 | SELECT c FROM chars 14 | ORDER BY ROWID DESC 15 | LIMIT 4 16 | ) 17 | ) = 4 18 | ); 19 | END; 20 | 21 | WITH RECURSIVE 22 | nn (c, rest) 23 | AS ( 24 | SELECT '', (SELECT s FROM input) 25 | UNION ALL 26 | SELECT 27 | SUBSTR(nn.rest, 1, 1), 28 | SUBSTR(nn.rest, 2) 29 | FROM nn 30 | WHERE nn.rest != '' 31 | ) 32 | INSERT INTO chars 33 | SELECT nn.c FROM nn WHERE c != ''; 34 | 35 | SELECT MIN(n) FROM answers; 36 | -------------------------------------------------------------------------------- /day06/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 | d: collections.deque[str] = collections.deque(maxlen=14) 16 | 17 | for i, c in enumerate(s.strip()): 18 | d.append(c) 19 | 20 | if len(d) == d.maxlen and len(set(d)) == d.maxlen: 21 | return i + 1 22 | 23 | raise NotImplementedError('wat') 24 | 25 | 26 | INPUT_S = '''\ 27 | mjqjpqmgbljsphdztnvjfqwrcgsmlb 28 | ''' 29 | EXPECTED = 19 30 | 31 | 32 | @pytest.mark.parametrize( 33 | ('input_s', 'expected'), 34 | ( 35 | (INPUT_S, EXPECTED), 36 | ), 37 | ) 38 | def test(input_s: str, expected: int) -> None: 39 | assert compute(input_s) == expected 40 | 41 | 42 | def main() -> int: 43 | parser = argparse.ArgumentParser() 44 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 45 | args = parser.parse_args() 46 | 47 | with open(args.data_file) as f, support.timing(): 48 | print(compute(f.read())) 49 | 50 | return 0 51 | 52 | 53 | if __name__ == '__main__': 54 | raise SystemExit(main()) 55 | -------------------------------------------------------------------------------- /day06/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE answers (n INT); 6 | CREATE TABLE chars (c VARCHAR); 7 | CREATE TRIGGER t_chars AFTER INSERT ON chars FOR EACH ROW BEGIN 8 | INSERT INTO answers 9 | SELECT (SELECT COUNT(1) FROM chars) 10 | WHERE ( 11 | ( 12 | SELECT COUNT(DISTINCT c) FROM ( 13 | SELECT c FROM chars 14 | ORDER BY ROWID DESC 15 | LIMIT 14 16 | ) 17 | ) = 14 18 | ); 19 | END; 20 | 21 | WITH RECURSIVE 22 | nn (c, rest) 23 | AS ( 24 | SELECT '', (SELECT s FROM input) 25 | UNION ALL 26 | SELECT 27 | SUBSTR(nn.rest, 1, 1), 28 | SUBSTR(nn.rest, 2) 29 | FROM nn 30 | WHERE nn.rest != '' 31 | ) 32 | INSERT INTO chars 33 | SELECT nn.c FROM nn WHERE c != ''; 34 | 35 | SELECT MIN(n) FROM answers; 36 | -------------------------------------------------------------------------------- /day07/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day07/__init__.py -------------------------------------------------------------------------------- /day07/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 | MAX = 100000 13 | 14 | 15 | def compute(s: str) -> int: 16 | files = {} 17 | dirs = {'/'} 18 | 19 | pwd = '/' 20 | for line in s.splitlines()[1:]: 21 | if line == '$ cd ..': 22 | pwd = os.path.dirname(pwd) or '/' 23 | elif line.startswith('$ cd'): 24 | pwd = os.path.join(pwd, line.split(' ', 2)[-1]) 25 | dirs.add(pwd) 26 | elif line.startswith(('$ ls', 'dir ')): 27 | continue 28 | else: 29 | sz, filename = line.split(' ', 1) 30 | files[os.path.join(pwd, filename)] = int(sz) 31 | 32 | def size(dirname: str) -> int: 33 | sz = 0 34 | for k, v in files.items(): 35 | if k.startswith(f'{dirname}/'): 36 | sz += v 37 | 38 | if sz > MAX: 39 | return 0 40 | else: 41 | return sz 42 | 43 | root = sum(files.values()) 44 | if root > MAX: 45 | root = 0 46 | 47 | return root + sum(size(dirname) for dirname in dirs) 48 | 49 | 50 | INPUT_S = '''\ 51 | $ cd / 52 | $ ls 53 | dir a 54 | 14848514 b.txt 55 | 8504156 c.dat 56 | dir d 57 | $ cd a 58 | $ ls 59 | dir e 60 | 29116 f 61 | 2557 g 62 | 62596 h.lst 63 | $ cd e 64 | $ ls 65 | 584 i 66 | $ cd .. 67 | $ cd .. 68 | $ cd d 69 | $ ls 70 | 4060174 j 71 | 8033020 d.log 72 | 5626152 d.ext 73 | 7214296 k 74 | ''' 75 | EXPECTED = 95437 76 | 77 | 78 | @pytest.mark.parametrize( 79 | ('input_s', 'expected'), 80 | ( 81 | (INPUT_S, EXPECTED), 82 | ), 83 | ) 84 | def test(input_s: str, expected: int) -> None: 85 | assert compute(input_s) == expected 86 | 87 | 88 | def main() -> int: 89 | parser = argparse.ArgumentParser() 90 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 91 | args = parser.parse_args() 92 | 93 | with open(args.data_file) as f, support.timing(): 94 | print(compute(f.read())) 95 | 96 | return 0 97 | 98 | 99 | if __name__ == '__main__': 100 | raise SystemExit(main()) 101 | -------------------------------------------------------------------------------- /day07/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE fs (p VARCHAR, is_dir BIT, size INT); 6 | INSERT INTO fs VALUES ('', 1, 0); 7 | 8 | WITH RECURSIVE 9 | nn (pwd, in_ls, p, is_dir, size, rest) 10 | AS ( 11 | SELECT 12 | '/', 13 | 0, 14 | '/', 15 | 0, 16 | 0, 17 | (SELECT SUBSTR(s, INSTR(s, char(10)) + 1) || char(10) FROM input) 18 | UNION ALL 19 | SELECT 20 | CASE 21 | WHEN SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1) = '$ cd ..' THEN 22 | CASE nn.pwd 23 | WHEN '/' THEN '/' 24 | ELSE RTRIM(SUBSTR(nn.pwd, INSTR(nn.pwd, '/') + 1), '/') || '/' 25 | END 26 | WHEN nn.rest LIKE '$ cd %' THEN 27 | SUBSTR(nn.rest, 6, INSTR(nn.rest, char(10)) - 6) || '/' || 28 | LTRIM(nn.pwd, '/') 29 | ELSE 30 | nn.pwd 31 | END, 32 | CASE 33 | WHEN SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1) = '$ ls' THEN 1 34 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' THEN 1 35 | ELSE 0 36 | END, 37 | CASE 38 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' THEN 39 | SUBSTR( 40 | nn.rest, 41 | INSTR(nn.rest, ' ') + 1, 42 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') - 1 43 | ) || '/' || 44 | LTRIM(nn.pwd, '/') 45 | ELSE '' 46 | END, 47 | nn.rest LIKE 'dir %', 48 | CASE 49 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' AND nn.rest NOT LIKE 'dir %' THEN 50 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ' ') - 1) 51 | ELSE 0 52 | END, 53 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 54 | FROM nn 55 | WHERE nn.rest != '' 56 | ) 57 | INSERT INTO fs 58 | SELECT nn.p, nn.is_dir, nn.size FROM nn WHERE nn.is_dir OR nn.size > 0; 59 | 60 | SELECT SUM(sz) FROM ( 61 | SELECT SUM(fs2.size) AS sz 62 | FROM fs 63 | INNER JOIN fs fs2 ON NOT fs2.is_dir AND fs2.p LIKE '%/' || fs.p 64 | WHERE fs.is_dir 65 | GROUP BY fs.p 66 | HAVING SUM(fs2.size) < 100000 67 | ); 68 | -------------------------------------------------------------------------------- /day07/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 | TOTAL = 70000000 13 | NEED = 30000000 14 | 15 | 16 | def compute(s: str) -> int: 17 | files = {} 18 | dirs = {'/'} 19 | 20 | pwd = '/' 21 | for line in s.splitlines()[1:]: 22 | if line == '$ cd ..': 23 | pwd = os.path.dirname(pwd) or '/' 24 | elif line.startswith('$ cd'): 25 | pwd = os.path.join(pwd, line.split(' ', 2)[-1]) 26 | dirs.add(pwd) 27 | elif line.startswith(('$ ls', 'dir ')): 28 | continue 29 | else: 30 | sz, filename = line.split(' ', 1) 31 | files[os.path.join(pwd, filename)] = int(sz) 32 | 33 | def size(dirname: str) -> int: 34 | sz = 0 35 | for k, v in files.items(): 36 | if k.startswith(f'{dirname}/'): 37 | sz += v 38 | 39 | return sz 40 | 41 | root = sum(files.values()) 42 | sizes = [root] + [size(dirname) for dirname in dirs] 43 | sizes.sort() 44 | 45 | need_to_delete = NEED - (TOTAL - root) 46 | sizes = [size for size in sizes if size >= need_to_delete] 47 | sizes.sort() 48 | return sizes[0] 49 | 50 | 51 | INPUT_S = '''\ 52 | $ cd / 53 | $ ls 54 | dir a 55 | 14848514 b.txt 56 | 8504156 c.dat 57 | dir d 58 | $ cd a 59 | $ ls 60 | dir e 61 | 29116 f 62 | 2557 g 63 | 62596 h.lst 64 | $ cd e 65 | $ ls 66 | 584 i 67 | $ cd .. 68 | $ cd .. 69 | $ cd d 70 | $ ls 71 | 4060174 j 72 | 8033020 d.log 73 | 5626152 d.ext 74 | 7214296 k 75 | ''' 76 | EXPECTED = 24933642 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/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE fs (p VARCHAR, is_dir BIT, size INT); 6 | INSERT INTO fs VALUES ('', 1, 0); 7 | 8 | WITH RECURSIVE 9 | nn (pwd, in_ls, p, is_dir, size, rest) 10 | AS ( 11 | SELECT 12 | '/', 13 | 0, 14 | '/', 15 | 0, 16 | 0, 17 | (SELECT SUBSTR(s, INSTR(s, char(10)) + 1) || char(10) FROM input) 18 | UNION ALL 19 | SELECT 20 | CASE 21 | WHEN SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1) = '$ cd ..' THEN 22 | CASE nn.pwd 23 | WHEN '/' THEN '/' 24 | ELSE RTRIM(SUBSTR(nn.pwd, INSTR(nn.pwd, '/') + 1), '/') || '/' 25 | END 26 | WHEN nn.rest LIKE '$ cd %' THEN 27 | SUBSTR(nn.rest, 6, INSTR(nn.rest, char(10)) - 6) || '/' || 28 | LTRIM(nn.pwd, '/') 29 | ELSE 30 | nn.pwd 31 | END, 32 | CASE 33 | WHEN SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1) = '$ ls' THEN 1 34 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' THEN 1 35 | ELSE 0 36 | END, 37 | CASE 38 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' THEN 39 | SUBSTR( 40 | nn.rest, 41 | INSTR(nn.rest, ' ') + 1, 42 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') - 1 43 | ) || '/' || 44 | LTRIM(nn.pwd, '/') 45 | ELSE '' 46 | END, 47 | nn.rest LIKE 'dir %', 48 | CASE 49 | WHEN nn.in_ls AND nn.rest NOT LIKE '$ %' AND nn.rest NOT LIKE 'dir %' THEN 50 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ' ') - 1) 51 | ELSE 0 52 | END, 53 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 54 | FROM nn 55 | WHERE nn.rest != '' 56 | ) 57 | INSERT INTO fs 58 | SELECT nn.p, nn.is_dir, nn.size FROM nn WHERE nn.is_dir OR nn.size > 0; 59 | 60 | SELECT sz FROM ( 61 | SELECT SUM(fs2.size) AS sz 62 | FROM fs 63 | INNER JOIN fs fs2 ON NOT fs2.is_dir AND fs2.p LIKE '%/' || fs.p 64 | WHERE fs.is_dir 65 | GROUP BY fs.p 66 | ) 67 | WHERE sz >= 30000000 - (70000000 - (SELECT SUM(fs.size) FROM fs)) 68 | ORDER BY sz ASC 69 | LIMIT 1; 70 | -------------------------------------------------------------------------------- /day08/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day08/__init__.py -------------------------------------------------------------------------------- /day08/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 | coords = support.parse_coords_int(s) 15 | visible = set() 16 | 17 | bx, by = support.bounds(coords) 18 | 19 | for y in by.range: 20 | # down 21 | val = coords[(y, bx.min)] 22 | visible.add((y, bx.min)) 23 | for x in range(bx.min + 1, bx.max + 1): 24 | cand = (y, x) 25 | if coords[cand] > val: 26 | visible.add(cand) 27 | val = coords[cand] 28 | 29 | # up 30 | val = coords[(y, bx.max)] 31 | visible.add((y, bx.max)) 32 | for x in range(bx.max, -1, -1): 33 | cand = (y, x) 34 | if coords[cand] > val: 35 | visible.add(cand) 36 | val = coords[cand] 37 | 38 | for x in bx.range: 39 | # right 40 | val = coords[(by.min, x)] 41 | visible.add((by.min, x)) 42 | for y in range(by.min + 1, by.max + 1): 43 | cand = (y, x) 44 | if coords[cand] > val: 45 | visible.add(cand) 46 | val = coords[cand] 47 | 48 | # left 49 | val = coords[(by.max, x)] 50 | visible.add((by.max, x)) 51 | for y in range(by.max, -1, -1): 52 | cand = (y, x) 53 | if coords[cand] > val: 54 | visible.add(cand) 55 | val = coords[cand] 56 | 57 | return len(visible) 58 | 59 | 60 | INPUT_S = '''\ 61 | 30373 62 | 25512 63 | 65332 64 | 33549 65 | 35390 66 | ''' 67 | EXPECTED = 21 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/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE coords (y INT, x INT, val INT); 6 | WITH RECURSIVE 7 | nn (y, x, v, rest) 8 | AS ( 9 | SELECT 10 | 0, 11 | 0, 12 | (SELECT SUBSTR(s, 1, 1) FROM input), 13 | (SELECT SUBSTR(s, 2) || char(10) FROM input) 14 | UNION ALL 15 | SELECT 16 | CASE SUBSTR(nn.rest, 1, 1) 17 | WHEN char(10) THEN nn.y + 1 18 | ELSE nn.y 19 | END, 20 | CASE SUBSTR(nn.rest, 1, 1) 21 | WHEN char(10) THEN -1 22 | ELSE nn.x + 1 23 | END, 24 | CASE SUBSTR(nn.rest, 1, 1) 25 | WHEN char(10) THEN -1 26 | ELSE SUBSTR(nn.rest, 1, 1) 27 | END, 28 | SUBSTR(nn.rest, 2) 29 | FROM nn 30 | WHERE nn.rest != '' 31 | ) 32 | INSERT INTO coords 33 | SELECT nn.y, nn.x, nn.v FROM nn WHERE nn.x != -1; 34 | 35 | SELECT COUNT(1) FROM ( 36 | SELECT y, x FROM ( 37 | SELECT coords.y, c2.x 38 | FROM (SELECT DISTINCT y FROM coords) coords 39 | INNER JOIN coords c2 ON 40 | c2.y = coords.y AND ( 41 | c2.x = 0 OR 42 | c2.val > ( 43 | SELECT MAX(c3.val) 44 | FROM coords c3 45 | WHERE c3.y = coords.y AND c3.x < c2.x 46 | ) 47 | ) 48 | 49 | UNION ALL 50 | 51 | SELECT coords.y, c2.x 52 | FROM (SELECT DISTINCT y FROM coords) coords 53 | INNER JOIN coords c2 ON 54 | c2.y = coords.y AND ( 55 | c2.x = (SELECT MAX(x) FROM coords) OR 56 | c2.val > ( 57 | SELECT MAX(c3.val) 58 | FROM coords c3 59 | WHERE c3.y = coords.y AND c3.x > c2.x 60 | ) 61 | ) 62 | 63 | UNION ALL 64 | 65 | SELECT c2.y, coords.x 66 | FROM (SELECT DISTINCT x FROM coords) coords 67 | INNER JOIN coords c2 ON 68 | c2.x = coords.x AND ( 69 | c2.y = 0 OR 70 | c2.val > ( 71 | SELECT MAX(c3.val) 72 | FROM coords c3 73 | WHERE c3.x = coords.x AND c3.y < c2.y 74 | ) 75 | ) 76 | 77 | UNION ALL 78 | 79 | SELECT c2.y, coords.x 80 | FROM (SELECT DISTINCT x FROM coords) coords 81 | INNER JOIN coords c2 ON 82 | c2.x = coords.x AND ( 83 | c2.y = (SELECT MAX(y) FROM coords) OR 84 | c2.val > ( 85 | SELECT MAX(c3.val) 86 | FROM coords c3 87 | WHERE c3.x = coords.x AND c3.y > c2.y 88 | ) 89 | ) 90 | ) 91 | GROUP BY y, x 92 | ); 93 | -------------------------------------------------------------------------------- /day08/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 | coords = support.parse_coords_int(s) 15 | bx, by = support.bounds(coords) 16 | 17 | val = -1 18 | 19 | def compute(x: int, y: int) -> int: 20 | n = coords[(y, x)] 21 | 22 | up = 0 23 | for cand_y in range(y - 1, by.min - 1, -1): 24 | up += 1 25 | if coords[(cand_y, x)] >= n: 26 | break 27 | 28 | down = 0 29 | for cand_y in range(y + 1, by.max + 1): 30 | down += 1 31 | if coords[(cand_y, x)] >= n: 32 | break 33 | 34 | left = 0 35 | for cand_x in range(x - 1, bx.min - 1, -1): 36 | left += 1 37 | if coords[(y, cand_x)] >= n: 38 | break 39 | 40 | right = 0 41 | for cand_x in range(x + 1, bx.max + 1): 42 | right += 1 43 | if coords[(y, cand_x)] >= n: 44 | break 45 | 46 | return up * down * left * right 47 | 48 | for y in by.range: 49 | for x in bx.range: 50 | val = max(compute(x, y), val) 51 | 52 | return val 53 | 54 | 55 | INPUT_S = '''\ 56 | 30373 57 | 25512 58 | 65332 59 | 33549 60 | 35390 61 | ''' 62 | EXPECTED = 8 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 | -------------------------------------------------------------------------------- /day08/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE coords (y INT, x INT, val INT); 6 | WITH RECURSIVE 7 | nn (y, x, v, rest) 8 | AS ( 9 | SELECT 10 | 0, 11 | 0, 12 | (SELECT SUBSTR(s, 1, 1) FROM input), 13 | (SELECT SUBSTR(s, 2) || char(10) FROM input) 14 | UNION ALL 15 | SELECT 16 | CASE SUBSTR(nn.rest, 1, 1) 17 | WHEN char(10) THEN nn.y + 1 18 | ELSE nn.y 19 | END, 20 | CASE SUBSTR(nn.rest, 1, 1) 21 | WHEN char(10) THEN -1 22 | ELSE nn.x + 1 23 | END, 24 | CASE SUBSTR(nn.rest, 1, 1) 25 | WHEN char(10) THEN -1 26 | ELSE SUBSTR(nn.rest, 1, 1) 27 | END, 28 | SUBSTR(nn.rest, 2) 29 | FROM nn 30 | WHERE nn.rest != '' 31 | ) 32 | INSERT INTO coords 33 | SELECT nn.y, nn.x, nn.v FROM nn WHERE nn.x != -1; 34 | 35 | SELECT 36 | MAX ( 37 | ( 38 | WITH RECURSIVE nn (y) AS ( 39 | SELECT coords.y 40 | UNION ALL 41 | SELECT nn.y - 1 42 | FROM nn 43 | WHERE 44 | nn.y > 0 AND ( 45 | nn.y = coords.y OR 46 | ( 47 | SELECT c2.val 48 | FROM coords c2 49 | WHERE c2.y = nn.y AND c2.x = coords.x 50 | ) < coords.val 51 | ) 52 | ) 53 | SELECT COUNT(1) - 1 FROM nn 54 | ) * ( 55 | WITH RECURSIVE nn (y) AS ( 56 | SELECT coords.y 57 | UNION ALL 58 | SELECT nn.y + 1 59 | FROM nn 60 | WHERE 61 | nn.y < (SELECT MAX(y) FROM coords) AND ( 62 | nn.y = coords.y OR 63 | ( 64 | SELECT c2.val 65 | FROM coords c2 66 | WHERE c2.y = nn.y AND c2.x = coords.x 67 | ) < coords.val 68 | ) 69 | ) 70 | SELECT COUNT(1) - 1 FROM nn 71 | ) * ( 72 | WITH RECURSIVE nn (x) AS ( 73 | SELECT coords.x 74 | UNION ALL 75 | SELECT nn.x - 1 76 | FROM nn 77 | WHERE 78 | nn.x > 0 AND ( 79 | nn.x = coords.x OR 80 | ( 81 | SELECT c2.val 82 | FROM coords c2 83 | WHERE c2.x = nn.x AND c2.y = coords.y 84 | ) < coords.val 85 | ) 86 | ) 87 | SELECT COUNT(1) - 1 FROM nn 88 | ) * ( 89 | WITH RECURSIVE nn (x) AS ( 90 | SELECT coords.x 91 | UNION ALL 92 | SELECT nn.x + 1 93 | FROM nn 94 | WHERE 95 | nn.x < (SELECT MAX(x) FROM coords) AND ( 96 | nn.x = coords.x OR 97 | ( 98 | SELECT c2.val 99 | FROM coords c2 100 | WHERE c2.x = nn.x AND c2.y = coords.y 101 | ) < coords.val 102 | ) 103 | ) 104 | SELECT COUNT(1) - 1 FROM nn 105 | ) 106 | ) 107 | FROM coords; 108 | -------------------------------------------------------------------------------- /day09/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day09/__init__.py -------------------------------------------------------------------------------- /day09/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 | D = { 14 | 'R': support.Direction4.RIGHT, 15 | 'U': support.Direction4.UP, 16 | 'L': support.Direction4.LEFT, 17 | 'D': support.Direction4.DOWN, 18 | } 19 | 20 | 21 | def compute(s: str) -> int: 22 | head = tail = (0, 0) 23 | seen = {tail} 24 | 25 | for line in s.splitlines(): 26 | dir_s, n_s = line.split() 27 | move = D[dir_s] 28 | n = int(n_s) 29 | 30 | for _ in range(n): 31 | head = move.apply(*head) 32 | if abs(head[0] - tail[0]) >= 2 or abs(head[1] - tail[1]) >= 2: 33 | tail = move.opposite.apply(*head) 34 | seen.add(tail) 35 | 36 | return len(seen) 37 | 38 | 39 | INPUT_S = '''\ 40 | R 4 41 | U 4 42 | L 3 43 | D 1 44 | R 4 45 | D 1 46 | L 5 47 | R 2 48 | ''' 49 | EXPECTED = 13 50 | 51 | 52 | @pytest.mark.parametrize( 53 | ('input_s', 'expected'), 54 | ( 55 | (INPUT_S, EXPECTED), 56 | ), 57 | ) 58 | def test(input_s: str, expected: int) -> None: 59 | assert compute(input_s) == expected 60 | 61 | 62 | def main() -> int: 63 | parser = argparse.ArgumentParser() 64 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 65 | args = parser.parse_args() 66 | 67 | with open(args.data_file) as f, support.timing(): 68 | print(compute(f.read())) 69 | 70 | return 0 71 | 72 | 73 | if __name__ == '__main__': 74 | raise SystemExit(main()) 75 | -------------------------------------------------------------------------------- /day09/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | -- oh no 6 | PRAGMA recursive_triggers = ON; 7 | 8 | CREATE TABLE points (i INT, x INT, y INT); 9 | INSERT INTO points VALUES 10 | (0, 0, 0), 11 | (1, 0, 0), 12 | (2, 0, 0), 13 | (3, 0, 0), 14 | (4, 0, 0), 15 | (5, 0, 0), 16 | (6, 0, 0), 17 | (7, 0, 0), 18 | (8, 0, 0), 19 | (9, 0, 0); 20 | 21 | CREATE TABLE visited(i INT, x INT, y INT, PRIMARY KEY (i, x, y)); 22 | INSERT INTO visited SELECT * FROM points; 23 | 24 | CREATE TRIGGER t_points AFTER UPDATE ON points FOR EACH ROW BEGIN 25 | INSERT OR REPLACE INTO visited VALUES (NEW.i, NEW.x, NEW.y) ON CONFLICT DO NOTHING; 26 | 27 | UPDATE points 28 | SET 29 | x = ( 30 | CASE 31 | WHEN ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 32 | (NEW.x + (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) / 2 33 | WHEN ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 34 | NEW.x 35 | ELSE 36 | x 37 | END 38 | ), 39 | y = ( 40 | CASE 41 | WHEN ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 42 | (NEW.y + (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) / 2 43 | WHEN ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 44 | NEW.y 45 | ELSE 46 | y 47 | END 48 | ) 49 | WHERE 50 | points.i = NEW.i + 1 AND ( 51 | ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 OR 52 | ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 53 | ); 54 | END; 55 | 56 | CREATE TABLE updates (dx INT, dy INT); 57 | CREATE TRIGGER t_updates AFTER INSERT ON updates BEGIN 58 | UPDATE points SET x = x + NEW.dx, y = y + new.dy WHERE i = 0; 59 | END; 60 | 61 | WITH RECURSIVE 62 | nn (n, dx, dy, rest) 63 | AS ( 64 | SELECT 0, 0, 0, (SELECT s || char(10) FROM input) 65 | UNION ALL 66 | SELECT 67 | CASE 68 | WHEN nn.n = 0 THEN 69 | SUBSTR(nn.rest, 3, INSTR(nn.rest, char(10)) - 3) 70 | ELSE 71 | nn.n - 1 72 | END, 73 | CASE 74 | WHEN nn.n = 0 THEN 75 | CASE SUBSTR(nn.rest, 1, 1) 76 | WHEN 'L' THEN -1 77 | WHEN 'R' THEN 1 78 | ELSE 0 79 | END 80 | ELSE nn.dx 81 | END, 82 | CASE 83 | WHEN nn.n = 0 THEN 84 | CASE SUBSTR(nn.rest, 1, 1) 85 | WHEN 'U' THEN -1 86 | WHEN 'D' THEN 1 87 | ELSE 0 88 | END 89 | ELSE nn.dy 90 | END, 91 | CASE 92 | WHEN nn.n = 0 THEN 93 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 94 | ELSE 95 | nn.rest 96 | END 97 | FROM nn WHERE nn.n > 0 OR nn.rest != '' 98 | ) 99 | INSERT INTO updates 100 | SELECT dx, dy FROM nn WHERE nn.n != 0; 101 | 102 | SELECT COUNT(1) FROM visited WHERE i = 1; 103 | -------------------------------------------------------------------------------- /day09/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 | D = { 14 | 'R': support.Direction4.RIGHT, 15 | 'U': support.Direction4.UP, 16 | 'L': support.Direction4.LEFT, 17 | 'D': support.Direction4.DOWN, 18 | } 19 | 20 | 21 | def fixup(head: tuple[int, int], tail: tuple[int, int]) -> tuple[int, int]: 22 | hx, hy = head 23 | tx, ty = tail 24 | 25 | if abs(hy - ty) == 2 and abs(hx - tx) == 2: 26 | return ((hx + tx) // 2, (hy + ty) // 2) 27 | if abs(hy - ty) == 2: 28 | return (hx, (ty + hy) // 2) 29 | elif abs(hx - tx) == 2: 30 | return ((tx + hx) // 2, hy) 31 | else: 32 | return tail 33 | 34 | 35 | def compute(s: str) -> int: 36 | positions = [(0, 0)] * 10 37 | seen = {positions[0]} 38 | 39 | for line in s.splitlines(): 40 | dir_s, n_s = line.split() 41 | move = D[dir_s] 42 | n = int(n_s) 43 | 44 | for _ in range(n): 45 | positions[0] = move.apply(*positions[0]) 46 | 47 | prev = positions[0] 48 | for i in range(1, len(positions)): 49 | positions[i] = fixup(prev, positions[i]) 50 | prev = positions[i] 51 | 52 | seen.add(positions[-1]) 53 | 54 | return len(seen) 55 | 56 | 57 | INPUT_S = '''\ 58 | R 5 59 | U 8 60 | L 8 61 | D 3 62 | R 17 63 | D 10 64 | L 25 65 | U 20 66 | ''' 67 | EXPECTED = 36 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 | -------------------------------------------------------------------------------- /day09/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | -- oh no 6 | PRAGMA recursive_triggers = ON; 7 | 8 | CREATE TABLE points (i INT, x INT, y INT); 9 | INSERT INTO points VALUES 10 | (0, 0, 0), 11 | (1, 0, 0), 12 | (2, 0, 0), 13 | (3, 0, 0), 14 | (4, 0, 0), 15 | (5, 0, 0), 16 | (6, 0, 0), 17 | (7, 0, 0), 18 | (8, 0, 0), 19 | (9, 0, 0); 20 | 21 | CREATE TABLE visited(i INT, x INT, y INT, PRIMARY KEY (i, x, y)); 22 | INSERT INTO visited SELECT * FROM points; 23 | 24 | CREATE TRIGGER t_points AFTER UPDATE ON points FOR EACH ROW BEGIN 25 | INSERT OR REPLACE INTO visited VALUES (NEW.i, NEW.x, NEW.y) ON CONFLICT DO NOTHING; 26 | 27 | UPDATE points 28 | SET 29 | x = ( 30 | CASE 31 | WHEN ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 32 | (NEW.x + (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) / 2 33 | WHEN ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 34 | NEW.x 35 | ELSE 36 | x 37 | END 38 | ), 39 | y = ( 40 | CASE 41 | WHEN ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 42 | (NEW.y + (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) / 2 43 | WHEN ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 THEN 44 | NEW.y 45 | ELSE 46 | y 47 | END 48 | ) 49 | WHERE 50 | points.i = NEW.i + 1 AND ( 51 | ABS(NEW.x - (SELECT points.x FROM points WHERE points.i = NEW.i + 1)) = 2 OR 52 | ABS(NEW.y - (SELECT points.y FROM points WHERE points.i = NEW.i + 1)) = 2 53 | ); 54 | END; 55 | 56 | CREATE TABLE updates (dx INT, dy INT); 57 | CREATE TRIGGER t_updates AFTER INSERT ON updates BEGIN 58 | UPDATE points SET x = x + NEW.dx, y = y + new.dy WHERE i = 0; 59 | END; 60 | 61 | WITH RECURSIVE 62 | nn (n, dx, dy, rest) 63 | AS ( 64 | SELECT 0, 0, 0, (SELECT s || char(10) FROM input) 65 | UNION ALL 66 | SELECT 67 | CASE 68 | WHEN nn.n = 0 THEN 69 | SUBSTR(nn.rest, 3, INSTR(nn.rest, char(10)) - 3) 70 | ELSE 71 | nn.n - 1 72 | END, 73 | CASE 74 | WHEN nn.n = 0 THEN 75 | CASE SUBSTR(nn.rest, 1, 1) 76 | WHEN 'L' THEN -1 77 | WHEN 'R' THEN 1 78 | ELSE 0 79 | END 80 | ELSE nn.dx 81 | END, 82 | CASE 83 | WHEN nn.n = 0 THEN 84 | CASE SUBSTR(nn.rest, 1, 1) 85 | WHEN 'U' THEN -1 86 | WHEN 'D' THEN 1 87 | ELSE 0 88 | END 89 | ELSE nn.dy 90 | END, 91 | CASE 92 | WHEN nn.n = 0 THEN 93 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 94 | ELSE 95 | nn.rest 96 | END 97 | FROM nn WHERE nn.n > 0 OR nn.rest != '' 98 | ) 99 | INSERT INTO updates 100 | SELECT dx, dy FROM nn WHERE nn.n != 0; 101 | 102 | SELECT COUNT(1) FROM visited WHERE i = 9; 103 | -------------------------------------------------------------------------------- /day10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day10/__init__.py -------------------------------------------------------------------------------- /day10/input.txt: -------------------------------------------------------------------------------- 1 | noop 2 | noop 3 | noop 4 | addx 3 5 | addx 20 6 | noop 7 | addx -12 8 | noop 9 | addx 4 10 | noop 11 | noop 12 | noop 13 | addx 1 14 | addx 2 15 | addx 5 16 | addx 16 17 | addx -14 18 | addx -25 19 | addx 30 20 | addx 1 21 | noop 22 | addx 5 23 | noop 24 | addx -38 25 | noop 26 | noop 27 | noop 28 | addx 3 29 | addx 2 30 | noop 31 | noop 32 | noop 33 | addx 5 34 | addx 5 35 | addx 2 36 | addx 13 37 | addx 6 38 | addx -16 39 | addx 2 40 | addx 5 41 | addx -15 42 | addx 16 43 | addx 7 44 | noop 45 | addx -2 46 | addx 2 47 | addx 5 48 | addx -39 49 | addx 4 50 | addx -2 51 | addx 2 52 | addx 7 53 | noop 54 | addx -2 55 | addx 17 56 | addx -10 57 | noop 58 | noop 59 | addx 5 60 | addx -1 61 | addx 6 62 | noop 63 | addx -2 64 | addx 5 65 | addx -8 66 | addx 12 67 | addx 3 68 | addx -2 69 | addx -19 70 | addx -16 71 | addx 2 72 | addx 5 73 | noop 74 | addx 25 75 | addx 7 76 | addx -29 77 | addx 3 78 | addx 4 79 | addx -4 80 | addx 9 81 | noop 82 | addx 2 83 | addx -20 84 | addx 23 85 | addx 1 86 | noop 87 | addx 5 88 | addx -10 89 | addx 14 90 | addx 2 91 | addx -1 92 | addx -38 93 | noop 94 | addx 20 95 | addx -15 96 | noop 97 | addx 7 98 | noop 99 | addx 26 100 | addx -25 101 | addx 2 102 | addx 7 103 | noop 104 | noop 105 | addx 2 106 | addx -5 107 | addx 6 108 | addx 5 109 | addx 2 110 | addx 8 111 | addx -3 112 | noop 113 | addx 3 114 | addx -2 115 | addx -38 116 | addx 13 117 | addx -6 118 | noop 119 | addx 1 120 | addx 5 121 | noop 122 | noop 123 | noop 124 | noop 125 | addx 2 126 | noop 127 | noop 128 | addx 7 129 | addx 3 130 | addx -2 131 | addx 2 132 | addx 5 133 | addx 2 134 | noop 135 | addx 1 136 | addx 5 137 | noop 138 | noop 139 | noop 140 | noop 141 | noop 142 | noop 143 | -------------------------------------------------------------------------------- /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 | INTERESTING = {20, 60, 100, 140, 180, 220} 13 | 14 | 15 | def compute(s: str) -> int: 16 | interesting_sums = 0 17 | 18 | x = 1 19 | lines = iter(s.splitlines()) 20 | instr_n = 0 21 | instr = 'noop' 22 | 23 | for i in range(1, 220 + 1): 24 | if instr_n == 0: 25 | instr = next(lines) 26 | if instr == 'noop': 27 | instr_n = 1 28 | elif instr.startswith('addx '): 29 | instr_n = 2 30 | 31 | instr_n -= 1 32 | 33 | if i in INTERESTING: 34 | interesting_sums += x * i 35 | 36 | if instr_n == 0: 37 | if instr.startswith('addx '): 38 | _, n_s = instr.split() 39 | x += int(n_s) 40 | 41 | return interesting_sums 42 | 43 | 44 | INPUT_S = '''\ 45 | addx 15 46 | addx -11 47 | addx 6 48 | addx -3 49 | addx 5 50 | addx -1 51 | addx -8 52 | addx 13 53 | addx 4 54 | noop 55 | addx -1 56 | addx 5 57 | addx -1 58 | addx 5 59 | addx -1 60 | addx 5 61 | addx -1 62 | addx 5 63 | addx -1 64 | addx -35 65 | addx 1 66 | addx 24 67 | addx -19 68 | addx 1 69 | addx 16 70 | addx -11 71 | noop 72 | noop 73 | addx 21 74 | addx -15 75 | noop 76 | noop 77 | addx -3 78 | addx 9 79 | addx 1 80 | addx -3 81 | addx 8 82 | addx 1 83 | addx 5 84 | noop 85 | noop 86 | noop 87 | noop 88 | noop 89 | addx -36 90 | noop 91 | addx 1 92 | addx 7 93 | noop 94 | noop 95 | noop 96 | addx 2 97 | addx 6 98 | noop 99 | noop 100 | noop 101 | noop 102 | noop 103 | addx 1 104 | noop 105 | noop 106 | addx 7 107 | addx 1 108 | noop 109 | addx -13 110 | addx 13 111 | addx 7 112 | noop 113 | addx 1 114 | addx -33 115 | noop 116 | noop 117 | noop 118 | addx 2 119 | noop 120 | noop 121 | noop 122 | addx 8 123 | noop 124 | addx -1 125 | addx 2 126 | addx 1 127 | noop 128 | addx 17 129 | addx -9 130 | addx 1 131 | addx 1 132 | addx -3 133 | addx 11 134 | noop 135 | noop 136 | addx 1 137 | noop 138 | addx 1 139 | noop 140 | noop 141 | addx -13 142 | addx -19 143 | addx 1 144 | addx 3 145 | addx 26 146 | addx -30 147 | addx 12 148 | addx -1 149 | addx 3 150 | addx 1 151 | noop 152 | noop 153 | noop 154 | addx -9 155 | addx 18 156 | addx 1 157 | addx 2 158 | noop 159 | noop 160 | addx 9 161 | noop 162 | noop 163 | noop 164 | addx -1 165 | addx 2 166 | addx -37 167 | addx 1 168 | addx 3 169 | noop 170 | addx 15 171 | addx -21 172 | addx 22 173 | addx -6 174 | addx 1 175 | noop 176 | addx 2 177 | addx 1 178 | noop 179 | addx -10 180 | noop 181 | noop 182 | addx 20 183 | addx 1 184 | addx 2 185 | addx 2 186 | addx -6 187 | addx -11 188 | noop 189 | noop 190 | noop 191 | ''' 192 | EXPECTED = 13140 193 | 194 | 195 | @pytest.mark.parametrize( 196 | ('input_s', 'expected'), 197 | ( 198 | (INPUT_S, EXPECTED), 199 | ), 200 | ) 201 | def test(input_s: str, expected: int) -> None: 202 | assert compute(input_s) == expected 203 | 204 | 205 | def main() -> int: 206 | parser = argparse.ArgumentParser() 207 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 208 | args = parser.parse_args() 209 | 210 | with open(args.data_file) as f, support.timing(): 211 | print(compute(f.read())) 212 | 213 | return 0 214 | 215 | 216 | if __name__ == '__main__': 217 | raise SystemExit(main()) 218 | -------------------------------------------------------------------------------- /day10/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (i INT, x INT); 6 | WITH RECURSIVE 7 | nn (i, x, val, n, rest) 8 | AS ( 9 | SELECT 0, 1, 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | nn.i + 1, 13 | CASE WHEN nn.n = 0 THEN nn.x + nn.val ELSE nn.x END, 14 | CASE 15 | WHEN nn.n = 0 THEN 16 | CASE 17 | WHEN nn.rest LIKE 'noop%' THEN 0 18 | ELSE SUBSTR(nn.rest, 6, INSTR(nn.rest, char(10)) - 6) 19 | END 20 | ELSE nn.val 21 | END, 22 | CASE 23 | WHEN nn.n = 0 THEN 24 | CASE 25 | WHEN nn.rest LIKE 'noop%' THEN 0 26 | ELSE 1 27 | END 28 | ELSE 0 29 | END, 30 | CASE 31 | WHEN nn.n = 0 THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 32 | ELSE nn.rest 33 | END 34 | FROM nn WHERE nn.rest != '' 35 | ) 36 | INSERT INTO vals 37 | SELECT i, x FROM nn; 38 | 39 | SELECT SUM(i * x) FROM vals WHERE i IN (20, 60, 100, 140, 180, 220); 40 | -------------------------------------------------------------------------------- /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) -> str: 14 | pixels = set() 15 | 16 | x = 1 17 | lines = iter(s.splitlines()) 18 | instr_n = 0 19 | instr = 'noop' 20 | 21 | for i in range(1, 240 + 1): 22 | if instr_n == 0: 23 | instr = next(lines) 24 | if instr == 'noop': 25 | instr_n = 1 26 | elif instr.startswith('addx '): 27 | instr_n = 2 28 | 29 | instr_n -= 1 30 | 31 | if x - 1 <= ((i - 1) % 40) <= x + 1: 32 | pixels.add(((i - 1) % 40, (i - 1) // 40)) 33 | 34 | if instr_n == 0: 35 | if instr.startswith('addx '): 36 | _, n_s = instr.split() 37 | x += int(n_s) 38 | 39 | return support.format_coords_hash(pixels).replace(' ', '.') 40 | 41 | 42 | INPUT_S = '''\ 43 | addx 15 44 | addx -11 45 | addx 6 46 | addx -3 47 | addx 5 48 | addx -1 49 | addx -8 50 | addx 13 51 | addx 4 52 | noop 53 | addx -1 54 | addx 5 55 | addx -1 56 | addx 5 57 | addx -1 58 | addx 5 59 | addx -1 60 | addx 5 61 | addx -1 62 | addx -35 63 | addx 1 64 | addx 24 65 | addx -19 66 | addx 1 67 | addx 16 68 | addx -11 69 | noop 70 | noop 71 | addx 21 72 | addx -15 73 | noop 74 | noop 75 | addx -3 76 | addx 9 77 | addx 1 78 | addx -3 79 | addx 8 80 | addx 1 81 | addx 5 82 | noop 83 | noop 84 | noop 85 | noop 86 | noop 87 | addx -36 88 | noop 89 | addx 1 90 | addx 7 91 | noop 92 | noop 93 | noop 94 | addx 2 95 | addx 6 96 | noop 97 | noop 98 | noop 99 | noop 100 | noop 101 | addx 1 102 | noop 103 | noop 104 | addx 7 105 | addx 1 106 | noop 107 | addx -13 108 | addx 13 109 | addx 7 110 | noop 111 | addx 1 112 | addx -33 113 | noop 114 | noop 115 | noop 116 | addx 2 117 | noop 118 | noop 119 | noop 120 | addx 8 121 | noop 122 | addx -1 123 | addx 2 124 | addx 1 125 | noop 126 | addx 17 127 | addx -9 128 | addx 1 129 | addx 1 130 | addx -3 131 | addx 11 132 | noop 133 | noop 134 | addx 1 135 | noop 136 | addx 1 137 | noop 138 | noop 139 | addx -13 140 | addx -19 141 | addx 1 142 | addx 3 143 | addx 26 144 | addx -30 145 | addx 12 146 | addx -1 147 | addx 3 148 | addx 1 149 | noop 150 | noop 151 | noop 152 | addx -9 153 | addx 18 154 | addx 1 155 | addx 2 156 | noop 157 | noop 158 | addx 9 159 | noop 160 | noop 161 | noop 162 | addx -1 163 | addx 2 164 | addx -37 165 | addx 1 166 | addx 3 167 | noop 168 | addx 15 169 | addx -21 170 | addx 22 171 | addx -6 172 | addx 1 173 | noop 174 | addx 2 175 | addx 1 176 | noop 177 | addx -10 178 | noop 179 | noop 180 | addx 20 181 | addx 1 182 | addx 2 183 | addx 2 184 | addx -6 185 | addx -11 186 | noop 187 | noop 188 | noop 189 | ''' 190 | EXPECTED = '''\ 191 | ##..##..##..##..##..##..##..##..##..##.. 192 | ###...###...###...###...###...###...###. 193 | ####....####....####....####....####.... 194 | #####.....#####.....#####.....#####..... 195 | ######......######......######......#### 196 | #######.......#######.......#######..... 197 | '''.rstrip() 198 | 199 | 200 | @pytest.mark.parametrize( 201 | ('input_s', 'expected'), 202 | ( 203 | (INPUT_S, EXPECTED), 204 | ), 205 | ) 206 | def test(input_s: str, expected: int) -> None: 207 | assert compute(input_s) == expected 208 | 209 | 210 | def main() -> int: 211 | parser = argparse.ArgumentParser() 212 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 213 | args = parser.parse_args() 214 | 215 | with open(args.data_file) as f, support.timing(): 216 | print(compute(f.read())) 217 | 218 | return 0 219 | 220 | 221 | if __name__ == '__main__': 222 | raise SystemExit(main()) 223 | -------------------------------------------------------------------------------- /day10/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE vals (i INT, x INT); 6 | WITH RECURSIVE 7 | nn (i, x, val, n, rest) 8 | AS ( 9 | SELECT 0, 1, 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | nn.i + 1, 13 | CASE WHEN nn.n = 0 THEN nn.x + nn.val ELSE nn.x END, 14 | CASE 15 | WHEN nn.n = 0 THEN 16 | CASE 17 | WHEN nn.rest LIKE 'noop%' THEN 0 18 | ELSE SUBSTR(nn.rest, 6, INSTR(nn.rest, char(10)) - 6) 19 | END 20 | ELSE nn.val 21 | END, 22 | CASE 23 | WHEN nn.n = 0 THEN 24 | CASE 25 | WHEN nn.rest LIKE 'noop%' THEN 0 26 | ELSE 1 27 | END 28 | ELSE 0 29 | END, 30 | CASE 31 | WHEN nn.n = 0 THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 32 | ELSE nn.rest 33 | END 34 | FROM nn WHERE nn.rest != '' 35 | ) 36 | INSERT INTO vals 37 | SELECT i, x FROM nn; 38 | 39 | WITH RECURSIVE 40 | nn (i, s) 41 | AS ( 42 | SELECT 0, '' 43 | UNION ALL 44 | SELECT 45 | nn.i + 1, 46 | nn.s || ( 47 | CASE (SELECT x - 1 <= ((i - 1) % 40) AND ((i - 1) % 40) <= x + 1 FROM vals WHERE vals.i == nn.i + 1) 48 | WHEN 1 THEN '#' 49 | WHEN 0 THEN '.' 50 | END 51 | ) || ( 52 | CASE 53 | WHEN (nn.i + 1) % 40 == 0 AND nn.i < 239 THEN char(10) 54 | ELSE '' 55 | END 56 | ) 57 | FROM nn 58 | WHERE nn.i <= (SELECT MAX(i) FROM vals) 59 | ) 60 | SELECT MAX(s) FROM nn; 61 | -------------------------------------------------------------------------------- /day11/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day11/__init__.py -------------------------------------------------------------------------------- /day11/input.txt: -------------------------------------------------------------------------------- 1 | Monkey 0: 2 | Starting items: 98, 97, 98, 55, 56, 72 3 | Operation: new = old * 13 4 | Test: divisible by 11 5 | If true: throw to monkey 4 6 | If false: throw to monkey 7 7 | 8 | Monkey 1: 9 | Starting items: 73, 99, 55, 54, 88, 50, 55 10 | Operation: new = old + 4 11 | Test: divisible by 17 12 | If true: throw to monkey 2 13 | If false: throw to monkey 6 14 | 15 | Monkey 2: 16 | Starting items: 67, 98 17 | Operation: new = old * 11 18 | Test: divisible by 5 19 | If true: throw to monkey 6 20 | If false: throw to monkey 5 21 | 22 | Monkey 3: 23 | Starting items: 82, 91, 92, 53, 99 24 | Operation: new = old + 8 25 | Test: divisible by 13 26 | If true: throw to monkey 1 27 | If false: throw to monkey 2 28 | 29 | Monkey 4: 30 | Starting items: 52, 62, 94, 96, 52, 87, 53, 60 31 | Operation: new = old * old 32 | Test: divisible by 19 33 | If true: throw to monkey 3 34 | If false: throw to monkey 1 35 | 36 | Monkey 5: 37 | Starting items: 94, 80, 84, 79 38 | Operation: new = old + 5 39 | Test: divisible by 2 40 | If true: throw to monkey 7 41 | If false: throw to monkey 0 42 | 43 | Monkey 6: 44 | Starting items: 89 45 | Operation: new = old + 1 46 | Test: divisible by 3 47 | If true: throw to monkey 0 48 | If false: throw to monkey 5 49 | 50 | Monkey 7: 51 | Starting items: 70, 59, 63 52 | Operation: new = old + 3 53 | Test: divisible by 7 54 | If true: throw to monkey 4 55 | If false: throw to monkey 3 56 | -------------------------------------------------------------------------------- /day11/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | from typing import Callable 7 | from typing import NamedTuple 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 add(val: int, n: int) -> int: 17 | return val + n 18 | 19 | 20 | def mult(val: int, n: int) -> int: 21 | return val * n 22 | 23 | 24 | def square(val: int) -> int: 25 | return val * val 26 | 27 | 28 | class Monk(NamedTuple): 29 | items: list[int] 30 | fn: Callable[[int], int] 31 | mod: int 32 | true_target: int 33 | false_target: int 34 | 35 | 36 | def compute(s: str) -> int: 37 | monks = [] 38 | for part in s.split('\n\n'): 39 | lines = part.splitlines() 40 | starting = [int(s) for s in lines[1].split(': ')[1].split(', ')] 41 | if 'old * old' in lines[2]: 42 | fn = square 43 | elif ' + ' in lines[2]: 44 | fn = functools.partial(add, n=int(lines[2].split()[-1])) 45 | elif ' * ' in lines[2]: 46 | fn = functools.partial(mult, n=int(lines[2].split()[-1])) 47 | else: 48 | raise AssertionError(lines[2]) 49 | mod = int(lines[3].split()[-1]) 50 | true_target = int(lines[4].split()[-1]) 51 | false_target = int(lines[5].split()[-1]) 52 | monks.append(Monk(starting, fn, mod, true_target, false_target)) 53 | 54 | seen = [0] * len(monks) 55 | 56 | for _ in range(20): 57 | for i, monk in enumerate(monks): 58 | for item in monk.items: 59 | seen[i] += 1 60 | 61 | item = monk.fn(item) // 3 62 | if item % monk.mod == 0: 63 | monks[monk.true_target].items.append(item) 64 | else: 65 | monks[monk.false_target].items.append(item) 66 | 67 | monk.items.clear() 68 | 69 | seen.sort() 70 | return seen[-1] * seen[-2] 71 | 72 | 73 | INPUT_S = '''\ 74 | Monkey 0: 75 | Starting items: 79, 98 76 | Operation: new = old * 19 77 | Test: divisible by 23 78 | If true: throw to monkey 2 79 | If false: throw to monkey 3 80 | 81 | Monkey 1: 82 | Starting items: 54, 65, 75, 74 83 | Operation: new = old + 6 84 | Test: divisible by 19 85 | If true: throw to monkey 2 86 | If false: throw to monkey 0 87 | 88 | Monkey 2: 89 | Starting items: 79, 60, 97 90 | Operation: new = old * old 91 | Test: divisible by 13 92 | If true: throw to monkey 1 93 | If false: throw to monkey 3 94 | 95 | Monkey 3: 96 | Starting items: 74 97 | Operation: new = old + 3 98 | Test: divisible by 17 99 | If true: throw to monkey 0 100 | If false: throw to monkey 1 101 | ''' 102 | EXPECTED = 10605 103 | 104 | 105 | @pytest.mark.parametrize( 106 | ('input_s', 'expected'), 107 | ( 108 | (INPUT_S, EXPECTED), 109 | ), 110 | ) 111 | def test(input_s: str, expected: int) -> None: 112 | assert compute(input_s) == expected 113 | 114 | 115 | def main() -> int: 116 | parser = argparse.ArgumentParser() 117 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 118 | args = parser.parse_args() 119 | 120 | with open(args.data_file) as f, support.timing(): 121 | print(compute(f.read())) 122 | 123 | return 0 124 | 125 | 126 | if __name__ == '__main__': 127 | raise SystemExit(main()) 128 | -------------------------------------------------------------------------------- /day11/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE lines (s); 6 | WITH RECURSIVE 7 | nn (s, rest) 8 | AS ( 9 | SELECT NULL, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1), 13 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 14 | FROM nn 15 | WHERE nn.rest != '' 16 | ) 17 | INSERT INTO lines 18 | SELECT s FROM nn WHERE s IS NOT NULL; 19 | 20 | CREATE TABLE items_lines (i INT, s VARCHAR); 21 | INSERT INTO items_lines 22 | SELECT (ROWID - 2) / 7, SUBSTR(s, INSTR(s, ': ') + 2) || ', ' 23 | FROM lines WHERE s LIKE '%Starting items%'; 24 | 25 | CREATE TABLE items (monk INT, val INT); 26 | WITH RECURSIVE 27 | nn (i, n, s) 28 | AS ( 29 | SELECT 0, NULL, (SELECT s FROM items_lines WHERE i = 0) 30 | UNION ALL 31 | SELECT 32 | CASE WHEN nn.s == '' THEN nn.i + 1 ELSE nn.i END, 33 | CASE 34 | WHEN nn.s == '' THEN NULL 35 | ELSE SUBSTR(nn.s, 1, INSTR(nn.s, ', ') - 1) 36 | END, 37 | CASE 38 | WHEN nn.s == '' THEN (SELECT s FROM items_lines WHERE i = nn.i + 1) 39 | ELSE SUBSTR(nn.s, INSTR(nn.s, ', ') + 2) 40 | END 41 | FROM nn 42 | WHERE nn.i <= (SELECT MAX(i) FROM items_lines) 43 | ) 44 | INSERT INTO items 45 | SELECT i, n FROM nn WHERE nn.n IS NOT NULL; 46 | 47 | CREATE TABLE monk (id INT, op VARCHAR, op_n INT NULL, mod INT, true_monk INT, false_monk INT); 48 | 49 | INSERT INTO monk 50 | SELECT 51 | (lines.ROWID - 3) / 7, 52 | CASE 53 | WHEN lines.s LIKE '%new = old * old%' THEN 'square' 54 | WHEN lines.s LIKE '%new = old +%' THEN 'add' 55 | WHEN lines.s LIKE '%new = old *%' THEN 'mult' 56 | ELSE NULL 57 | END, 58 | CASE 59 | WHEN lines.s LIKE '%new = old * old%' THEN NULL 60 | ELSE SUBSTR(lines.s, 26) 61 | END, 62 | SUBSTR(lines2.s, 22), 63 | SUBSTR(lines3.s, 30), 64 | SUBSTR(lines4.s, 31) 65 | FROM lines 66 | INNER JOIN lines lines2 ON lines2.ROWID = lines.ROWID + 1 67 | INNER JOIN lines lines3 ON lines3.ROWID = lines.ROWID + 2 68 | INNER JOIN lines lines4 ON lines4.ROWID = lines.ROWID + 3 69 | WHERE lines.s LIKE '%Operation: %'; 70 | 71 | CREATE TABLE seen(monk INT, n INT); 72 | INSERT INTO seen SELECT monk.id, 0 FROM monk; 73 | 74 | CREATE TABLE work (monk INT); 75 | CREATE TRIGGER t_work AFTER INSERT ON work FOR EACH ROW BEGIN 76 | UPDATE seen 77 | SET n = n + (SELECT COUNT(1) FROM items WHERE items.monk = NEW.monk) 78 | WHERE monk = NEW.monk; 79 | 80 | UPDATE items 81 | SET val = ( 82 | CASE (SELECT monk.op FROM monk WHERE monk.id = NEW.monk) 83 | WHEN 'square' THEN val * val 84 | WHEN 'add' THEN val + (SELECT monk.op_n FROM monk WHERE monk.id = NEW.monk) 85 | WHEN 'mult' THEN val * (SELECT monk.op_n FROM monk WHERE monk.id = NEW.monk) 86 | ELSE '?????' 87 | END 88 | ) 89 | WHERE monk = NEW.monk; 90 | 91 | UPDATE items 92 | SET val = val / 3 93 | WHERE monk = NEW.monk; 94 | 95 | UPDATE items 96 | SET monk = ( 97 | CASE 98 | WHEN val % (SELECT monk.mod FROM monk WHERE monk.id = NEW.monk) = 0 THEN 99 | (SELECT monk.true_monk FROM monk WHERE monk.id = NEW.monk) 100 | ELSE 101 | (SELECT monk.false_monk FROM monk WHERE monk.id = NEW.monk) 102 | END 103 | ) 104 | WHERE monk = NEW.monk; 105 | END; 106 | 107 | WITH RECURSIVE 108 | nn (i, monk) 109 | AS ( 110 | SELECT 0, 0 111 | UNION ALL 112 | SELECT 113 | CASE 114 | WHEN nn.monk = (SELECT MAX(id) FROM monk) THEN nn.i + 1 115 | ELSE nn.i 116 | END, 117 | CASE 118 | WHEN nn.monk = (SELECT MAX(id) FROM monk) THEN 0 119 | ELSE nn.monk + 1 120 | END 121 | FROM nn 122 | WHERE i < 20 123 | ) 124 | INSERT INTO work 125 | SELECT monk FROM nn WHERE i < 20; 126 | 127 | SELECT 128 | (SELECT n FROM seen ORDER BY n DESC LIMIT 1 OFFSET 0) * 129 | (SELECT n FROM seen ORDER BY n DESC LIMIT 1 OFFSET 1); 130 | -------------------------------------------------------------------------------- /day11/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import math 6 | import os.path 7 | from typing import Callable 8 | from typing import NamedTuple 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 add(val: int, n: int) -> int: 18 | return val + n 19 | 20 | 21 | def mult(val: int, n: int) -> int: 22 | return val * n 23 | 24 | 25 | def square(val: int) -> int: 26 | return val * val 27 | 28 | 29 | class Monk(NamedTuple): 30 | items: list[int] 31 | fn: Callable[[int], int] 32 | mod: int 33 | true_target: int 34 | false_target: int 35 | 36 | 37 | def compute(s: str) -> int: 38 | monks = [] 39 | for part in s.split('\n\n'): 40 | lines = part.splitlines() 41 | starting = [int(s) for s in lines[1].split(': ')[1].split(', ')] 42 | if 'old * old' in lines[2]: 43 | fn = square 44 | elif ' + ' in lines[2]: 45 | fn = functools.partial(add, n=int(lines[2].split()[-1])) 46 | elif ' * ' in lines[2]: 47 | fn = functools.partial(mult, n=int(lines[2].split()[-1])) 48 | else: 49 | raise AssertionError(lines[2]) 50 | mod = int(lines[3].split()[-1]) 51 | true_target = int(lines[4].split()[-1]) 52 | false_target = int(lines[5].split()[-1]) 53 | monks.append(Monk(starting, fn, mod, true_target, false_target)) 54 | 55 | fac = math.prod(monk.mod for monk in monks) 56 | 57 | seen = [0] * len(monks) 58 | 59 | for j in range(10000): 60 | for i, monk in enumerate(monks): 61 | for item in monk.items: 62 | seen[i] += 1 63 | 64 | item = monk.fn(item) % fac 65 | if item % monk.mod == 0: 66 | monks[monk.true_target].items.append(item) 67 | else: 68 | monks[monk.false_target].items.append(item) 69 | 70 | monk.items.clear() 71 | 72 | seen.sort() 73 | return seen[-1] * seen[-2] 74 | 75 | 76 | INPUT_S = '''\ 77 | Monkey 0: 78 | Starting items: 79, 98 79 | Operation: new = old * 19 80 | Test: divisible by 23 81 | If true: throw to monkey 2 82 | If false: throw to monkey 3 83 | 84 | Monkey 1: 85 | Starting items: 54, 65, 75, 74 86 | Operation: new = old + 6 87 | Test: divisible by 19 88 | If true: throw to monkey 2 89 | If false: throw to monkey 0 90 | 91 | Monkey 2: 92 | Starting items: 79, 60, 97 93 | Operation: new = old * old 94 | Test: divisible by 13 95 | If true: throw to monkey 1 96 | If false: throw to monkey 3 97 | 98 | Monkey 3: 99 | Starting items: 74 100 | Operation: new = old + 3 101 | Test: divisible by 17 102 | If true: throw to monkey 0 103 | If false: throw to monkey 1 104 | ''' 105 | EXPECTED = 2713310158 106 | 107 | 108 | @pytest.mark.parametrize( 109 | ('input_s', 'expected'), 110 | ( 111 | (INPUT_S, EXPECTED), 112 | ), 113 | ) 114 | def test(input_s: str, expected: int) -> None: 115 | assert compute(input_s) == expected 116 | 117 | 118 | def main() -> int: 119 | parser = argparse.ArgumentParser() 120 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 121 | args = parser.parse_args() 122 | 123 | with open(args.data_file) as f, support.timing(): 124 | print(compute(f.read())) 125 | 126 | return 0 127 | 128 | 129 | if __name__ == '__main__': 130 | raise SystemExit(main()) 131 | -------------------------------------------------------------------------------- /day11/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE lines (s); 6 | WITH RECURSIVE 7 | nn (s, rest) 8 | AS ( 9 | SELECT NULL, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1), 13 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 14 | FROM nn 15 | WHERE nn.rest != '' 16 | ) 17 | INSERT INTO lines 18 | SELECT s FROM nn WHERE s IS NOT NULL; 19 | 20 | CREATE TABLE items_lines (i INT, s VARCHAR); 21 | INSERT INTO items_lines 22 | SELECT (ROWID - 2) / 7, SUBSTR(s, INSTR(s, ': ') + 2) || ', ' 23 | FROM lines WHERE s LIKE '%Starting items%'; 24 | 25 | CREATE TABLE items (monk INT, val INT); 26 | WITH RECURSIVE 27 | nn (i, n, s) 28 | AS ( 29 | SELECT 0, NULL, (SELECT s FROM items_lines WHERE i = 0) 30 | UNION ALL 31 | SELECT 32 | CASE WHEN nn.s == '' THEN nn.i + 1 ELSE nn.i END, 33 | CASE 34 | WHEN nn.s == '' THEN NULL 35 | ELSE SUBSTR(nn.s, 1, INSTR(nn.s, ', ') - 1) 36 | END, 37 | CASE 38 | WHEN nn.s == '' THEN (SELECT s FROM items_lines WHERE i = nn.i + 1) 39 | ELSE SUBSTR(nn.s, INSTR(nn.s, ', ') + 2) 40 | END 41 | FROM nn 42 | WHERE nn.i <= (SELECT MAX(i) FROM items_lines) 43 | ) 44 | INSERT INTO items 45 | SELECT i, n FROM nn WHERE nn.n IS NOT NULL; 46 | 47 | CREATE TABLE monk (id INT, op VARCHAR, op_n INT NULL, mod INT, true_monk INT, false_monk INT); 48 | 49 | INSERT INTO monk 50 | SELECT 51 | (lines.ROWID - 3) / 7, 52 | CASE 53 | WHEN lines.s LIKE '%new = old * old%' THEN 'square' 54 | WHEN lines.s LIKE '%new = old +%' THEN 'add' 55 | WHEN lines.s LIKE '%new = old *%' THEN 'mult' 56 | ELSE NULL 57 | END, 58 | CASE 59 | WHEN lines.s LIKE '%new = old * old%' THEN NULL 60 | ELSE SUBSTR(lines.s, 26) 61 | END, 62 | SUBSTR(lines2.s, 22), 63 | SUBSTR(lines3.s, 30), 64 | SUBSTR(lines4.s, 31) 65 | FROM lines 66 | INNER JOIN lines lines2 ON lines2.ROWID = lines.ROWID + 1 67 | INNER JOIN lines lines3 ON lines3.ROWID = lines.ROWID + 2 68 | INNER JOIN lines lines4 ON lines4.ROWID = lines.ROWID + 3 69 | WHERE lines.s LIKE '%Operation: %'; 70 | 71 | CREATE TABLE modulus (n); 72 | WITH RECURSIVE 73 | nn (i, n) 74 | AS ( 75 | SELECT -1, 1 76 | UNION ALL 77 | SELECT 78 | nn.i + 1, 79 | nn.n * (SELECT monk.mod FROM monk WHERE monk.id = (nn.i + 1)) 80 | FROM nn WHERE nn.i <= (SELECT MAX(monk.id) FROM monk) 81 | ) 82 | INSERT INTO modulus 83 | SELECT MAX(n) FROM nn; 84 | 85 | CREATE TABLE seen(monk INT, n INT); 86 | INSERT INTO seen SELECT monk.id, 0 FROM monk; 87 | 88 | CREATE TABLE work (monk INT); 89 | CREATE TRIGGER t_work AFTER INSERT ON work FOR EACH ROW BEGIN 90 | UPDATE seen 91 | SET n = n + (SELECT COUNT(1) FROM items WHERE items.monk = NEW.monk) 92 | WHERE monk = NEW.monk; 93 | 94 | UPDATE items 95 | SET val = ( 96 | CASE (SELECT monk.op FROM monk WHERE monk.id = NEW.monk) 97 | WHEN 'square' THEN val * val 98 | WHEN 'add' THEN val + (SELECT monk.op_n FROM monk WHERE monk.id = NEW.monk) 99 | WHEN 'mult' THEN val * (SELECT monk.op_n FROM monk WHERE monk.id = NEW.monk) 100 | ELSE '?????' 101 | END 102 | ) 103 | WHERE monk = NEW.monk; 104 | 105 | UPDATE items 106 | SET val = val % (SELECT n FROM modulus) 107 | WHERE monk = NEW.monk; 108 | 109 | UPDATE items 110 | SET monk = ( 111 | CASE 112 | WHEN val % (SELECT monk.mod FROM monk WHERE monk.id = NEW.monk) = 0 THEN 113 | (SELECT monk.true_monk FROM monk WHERE monk.id = NEW.monk) 114 | ELSE 115 | (SELECT monk.false_monk FROM monk WHERE monk.id = NEW.monk) 116 | END 117 | ) 118 | WHERE monk = NEW.monk; 119 | END; 120 | 121 | WITH RECURSIVE 122 | nn (i, monk) 123 | AS ( 124 | SELECT 0, 0 125 | UNION ALL 126 | SELECT 127 | CASE 128 | WHEN nn.monk = (SELECT MAX(id) FROM monk) THEN nn.i + 1 129 | ELSE nn.i 130 | END, 131 | CASE 132 | WHEN nn.monk = (SELECT MAX(id) FROM monk) THEN 0 133 | ELSE nn.monk + 1 134 | END 135 | FROM nn 136 | WHERE i < 10000 137 | ) 138 | INSERT INTO work 139 | SELECT monk FROM nn WHERE i < 10000; 140 | 141 | SELECT 142 | (SELECT n FROM seen ORDER BY n DESC LIMIT 1 OFFSET 0) * 143 | (SELECT n FROM seen ORDER BY n DESC LIMIT 1 OFFSET 1); 144 | -------------------------------------------------------------------------------- /day12/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day12/__init__.py -------------------------------------------------------------------------------- /day12/input.txt: -------------------------------------------------------------------------------- 1 | abaaaaaccccccccccccccccccaaaaaaaaaaaaaccccaaaaaaaccccccccccccccccccccccccccccaaaaaa 2 | abaaaaaaccaaaacccccccccccaaaaaaaaacaaaacaaaaaaaaaacccccccccccccccccccccccccccaaaaaa 3 | abaaaaaacaaaaaccccccccccaaaaaaaaaaaaaaacaaaaaaaaaacccccccccccccaacccccccccccccaaaaa 4 | abaaaaaacaaaaaacccccccccaaaaaaaaaaaaaaccaaacaaaccccccccccccccccaacccccccccccccccaaa 5 | abccaaaccaaaaaacccaaaaccaaaaaaaaaaaaaccccaacaaacccccccccaacaccccacccccccccccccccaaa 6 | abcccccccaaaaaccccaaaacccccaaaaacccaaaccaaaaaaccccccccccaaaaccccccccccccccccccccaac 7 | abcccccccccaaaccccaaaacccccaaaaacccccccccaaaaaccccccccccklllllccccccccccccccccccccc 8 | abcccccccccccccccccaaccccccccaaccccccccaaaaaaaccccccccckklllllllcccccddccccaacccccc 9 | abaccccccccccccccccccccccccccaaccccccccaaaaaaaaccccccckkkklslllllcccddddddaaacccccc 10 | abacccccccccccccccccccccccccccccccaaaccaaaaaaaaccccccckkkssssslllllcddddddddacccccc 11 | abaccccccccccccccccccccccccccccccccaaaaccaaacaccccccckkksssssssslllmmmmmdddddaacccc 12 | abcccccccccccccccaaacccccccccccccaaaaaaccaacccccccccckkkssssusssslmmmmmmmdddddacccc 13 | abcccccccaaccccaaaaacccccccccccccaaaaaccccccaaaaaccckkkrssuuuussssqmmmmmmmmdddccccc 14 | abcccccccaaccccaaaaaacccccccaaccccaaaaacccccaaaaacckkkkrruuuuuussqqqqqqmmmmdddccccc 15 | abccccaaaaaaaacaaaaaacccccccaaaaccaaccaccccaaaaaacjkkkrrruuuxuuusqqqqqqqmmmmeeccccc 16 | abcaaaaaaaaaaacaaaaaccccccaaaaaacccccaaccccaaaaajjjjrrrrruuuxxuvvvvvvvqqqmmmeeccccc 17 | abcaacccaaaaccccaaaaaaacccaaaaacccacaaaccccaaaajjjjrrrrruuuxxxxvvvvvvvqqqmmeeeccccc 18 | abaaaaccaaaaacccccccaaaccccaaaaacaaaaaaaacccaajjjjrrrrtuuuuxxxyvyyyvvvqqqnneeeccccc 19 | abaaaaaaaaaaacccaaaaaaaccccaacaacaaaaaaaacccccjjjrrrttttuxxxxxyyyyyvvvqqnnneeeccccc 20 | abaaaaaaaccaacccaaaaaaaaacccccccccaaaaaaccccccjjjrrrtttxxxxxxxyyyyyvvvqqnnneeeccccc 21 | SbaaaaaacccccccccaaaaaaaaaccccccccaaaaacccccccjjjrrrtttxxxEzzzzyyyvvrrrnnneeecccccc 22 | abaaaaacccccccccccaaaaaaacccccccccaaaaaaccccccjjjqqqtttxxxxxyyyyyvvvrrrnnneeecccccc 23 | abaaacccccccccccaaaaaaaccaaccccccccccaaccaaaaajjjqqqttttxxxxyyyyyyvvrrrnnneeecccccc 24 | abaaacccccccccccaaaaaaaccaaacaaacccccccccaaaaajjjjqqqtttttxxyywyyyywvrrnnnfeecccccc 25 | abcaaacccccccaaaaaaaaaaacaaaaaaaccccccccaaaaaaciiiiqqqqtttxwyywwyywwwrrrnnfffcccccc 26 | abcccccccccccaaaaaaaaaaccaaaaaacccccccccaaaaaacciiiiqqqqttwwywwwwwwwwrrrnnfffcccccc 27 | abccccccccccccaaaaaacccaaaaaaaacccccccccaaaaaaccciiiiqqqttwwwwwswwwwrrrrnnfffcccccc 28 | abccccccccccccaaaaaacccaaaaaaaaacccccccccaaacccccciiiqqqtswwwwssssrrrrrroofffcccccc 29 | abccccccaaaaacaaaaaacccaaaaaaaaaaccccccccccccccccciiiqqqssswsssssssrrrrooofffaccccc 30 | abccccccaaaaacaaccaaccccccaaacaaacccccccccccccccccciiiqqssssssspoorrrooooofffaacccc 31 | abcccccaaaaaacccccccccccccaaacccccccccccccccccccccciiiqppssssspppooooooooffffaacccc 32 | abcccccaaaaaacccccccccccccaacccccccccccccccccccccccciipppppppppppoooooooffffaaccccc 33 | abcccccaaaaaaccccccccccccccccccccccccccccccccccccccciihppppppppgggggggggfffaaaccccc 34 | abccccccaaacccccccccccccccccccccccaccccccccccccccccchhhhpppppphggggggggggfaaaaccccc 35 | abaaaccccccccccccccccccccccaccccaaacccccccccccccccccchhhhhhhhhhgggggggggcaacccccccc 36 | abaaccaaaccaccccccccccccccaaacccaaacaacccaaaaacccccccchhhhhhhhhgaaccccccccccccccccc 37 | abaaacaaacaacccccccccaaaccaaaacaaaaaaaaccaaaaaccccccccchhhhhhaaaaacccccccccccccccca 38 | abaaaccaaaaaccccccccccaaacaaaaaaaacaaaaccaaaaaaccccccccccaaacccaaaacccccccccccaccca 39 | abcccaaaaaaccccccccccaaaaaaaaaaaaacaaaaccaaaaaaccccccccccaaaccccaaaccccccccccaaaaaa 40 | abcccaaaaaaaacccccccaaaaaaaaaaaaaaaaaccccaaaaaacccccccccccccccccccccccccccccccaaaaa 41 | abcccaacaaaaaccccccaaaaaaaaaaaaaaaaaaacccccaacccccccccccccccccccccccccccccccccaaaaa 42 | -------------------------------------------------------------------------------- /day12/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 | MAPPED = { 14 | 'S': 'a', 15 | 'E': 'z', 16 | } 17 | 18 | 19 | def compute(s: str) -> int: 20 | coords = {} 21 | start = end = None 22 | for y, line in enumerate(s.splitlines()): 23 | for x, c in enumerate(line): 24 | coords[(y, x)] = c 25 | if c == 'S': 26 | start = (y, x) 27 | elif c == 'E': 28 | end = (y, x) 29 | 30 | assert start is not None 31 | assert end is not None 32 | 33 | seen = set() 34 | todo = [(0, start)] 35 | 36 | while todo: 37 | cost, pos = heapq.heappop(todo) 38 | 39 | if pos == end: 40 | return cost 41 | elif pos in seen: 42 | continue 43 | else: 44 | seen.add(pos) 45 | 46 | for cand in support.adjacent_4(*pos): 47 | if cand in coords: 48 | current_c = MAPPED.get(coords[pos], coords[pos]) 49 | cand_c = MAPPED.get(coords[cand], coords[cand]) 50 | if ord(cand_c) - ord(current_c) <= 1: 51 | heapq.heappush(todo, (cost + 1, cand)) 52 | 53 | raise AssertionError('wat') 54 | 55 | 56 | INPUT_S = '''\ 57 | Sabqponm 58 | abcryxxl 59 | accszExk 60 | acctuvwj 61 | abdefghi 62 | ''' 63 | EXPECTED = 31 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 | -------------------------------------------------------------------------------- /day12/part1.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE coords (y INT, x INT, c VARCHAR, beginning BIT, ending BIT); 6 | WITH RECURSIVE 7 | nn (y, x, c, rest) 8 | AS ( 9 | SELECT 0, -1, NULL, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN nn.y + 1 ELSE nn.y END, 13 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN -1 ELSE nn.x + 1 END, 14 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN NULL ELSE SUBSTR(nn.rest, 1, 1) END, 15 | SUBSTR(nn.rest, 2) 16 | FROM nn 17 | WHERE nn.rest != '' 18 | ) 19 | INSERT INTO coords 20 | SELECT y, x, c, 0, 0 FROM nn WHERE c IS NOT NULL; 21 | 22 | UPDATE coords SET c = 'a', beginning = 1 WHERE c = 'S'; 23 | UPDATE coords SET c = 'z', ending = 1 WHERE c = 'E'; 24 | 25 | CREATE TABLE deltas (dy INT, dx INT); 26 | INSERT INTO deltas VALUES (1, 0), (-1, 0), (0, 1), (0, -1); 27 | 28 | WITH RECURSIVE 29 | nn (it, todo, seen) 30 | AS ( 31 | SELECT 0, json_array(json_array(y, x, 0)), json_array() 32 | FROM coords WHERE beginning = 1 33 | UNION 34 | SELECT 35 | nn.it + 1, 36 | ( 37 | SELECT json_group_array(json(value)) 38 | FROM ( 39 | SELECT value, json_extract(value, '$[2]') FROM ( 40 | -- remove lowest 41 | SELECT j.value FROM json_each(nn.todo) j WHERE j.key > 0 42 | UNION ALL 43 | -- extend rest 44 | SELECT json_array( 45 | json_extract(nn.todo, '$[0][0]') + deltas.dy, 46 | json_extract(nn.todo, '$[0][1]') + deltas.dx, 47 | json_extract(nn.todo, '$[0][2]') + 1 48 | ) 49 | FROM deltas 50 | INNER JOIN coords cand ON 51 | json_extract(nn.todo, '$[0][0]') + deltas.dy = cand.y AND 52 | json_extract(nn.todo, '$[0][1]') + deltas.dx = cand.x 53 | INNER JOIN coords curr ON 54 | json_extract(nn.todo, '$[0][0]') = curr.y AND 55 | json_extract(nn.todo, '$[0][1]') = curr.x AND 56 | (UNICODE(cand.c) - UNICODE(curr.c)) <= 1 57 | WHERE 58 | ( 59 | SELECT COUNT(1) FROM json_each(nn.seen) j 60 | WHERE j.value = json_array( 61 | json_extract(nn.todo, '$[0][0]'), 62 | json_extract(nn.todo, '$[0][1]') 63 | ) 64 | ) = 0 65 | ) 66 | ORDER BY json_extract(value, '$[2]') ASC 67 | ) 68 | ), 69 | ( 70 | SELECT json_group_array(value) FROM ( 71 | SELECT j.value FROM json_each(nn.seen) j 72 | UNION 73 | SELECT json_array( 74 | json_extract(nn.todo, '$[0][0]'), 75 | json_extract(nn.todo, '$[0][1]') 76 | ) 77 | ) 78 | ) 79 | FROM nn 80 | WHERE 81 | json_array( 82 | json_extract(nn.todo, '$[0][0]'), 83 | json_extract(nn.todo, '$[0][1]') 84 | ) != ( 85 | SELECT json_array(coords.y, coords.x) 86 | FROM coords WHERE coords.ending 87 | ) 88 | ) 89 | SELECT json_extract(nn.todo, '$[0][2]') FROM nn ORDER BY nn.it DESC LIMIT 1; 90 | -------------------------------------------------------------------------------- /day12/part2.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 | MAPPED = { 14 | 'S': 'a', 15 | 'E': 'z', 16 | } 17 | 18 | 19 | def compute(s: str) -> int: 20 | coords = {} 21 | for y, line in enumerate(s.splitlines()): 22 | for x, c in enumerate(line): 23 | coords[(y, x)] = c 24 | if c == 'E': 25 | end = (y, x) 26 | 27 | assert end is not None 28 | 29 | seen = set() 30 | todo = [(0, end)] 31 | 32 | while todo: 33 | cost, pos = heapq.heappop(todo) 34 | 35 | if coords[pos] == 'a': 36 | return cost 37 | elif pos in seen: 38 | continue 39 | else: 40 | seen.add(pos) 41 | 42 | for cand in support.adjacent_4(*pos): 43 | if cand in coords: 44 | current_c = MAPPED.get(coords[pos], coords[pos]) 45 | cand_c = MAPPED.get(coords[cand], coords[cand]) 46 | if ord(cand_c) - ord(current_c) >= -1: 47 | heapq.heappush(todo, (cost + 1, cand)) 48 | 49 | raise AssertionError('wat') 50 | 51 | 52 | INPUT_S = '''\ 53 | Sabqponm 54 | abcryxxl 55 | accszExk 56 | acctuvwj 57 | abdefghi 58 | ''' 59 | EXPECTED = 29 60 | 61 | 62 | @pytest.mark.parametrize( 63 | ('input_s', 'expected'), 64 | ( 65 | (INPUT_S, EXPECTED), 66 | ), 67 | ) 68 | def test(input_s: str, expected: int) -> None: 69 | assert compute(input_s) == expected 70 | 71 | 72 | def main() -> int: 73 | parser = argparse.ArgumentParser() 74 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 75 | args = parser.parse_args() 76 | 77 | with open(args.data_file) as f, support.timing(): 78 | print(compute(f.read())) 79 | 80 | return 0 81 | 82 | 83 | if __name__ == '__main__': 84 | raise SystemExit(main()) 85 | -------------------------------------------------------------------------------- /day12/part2.sqlite.sql: -------------------------------------------------------------------------------- 1 | -- our puzzle input 2 | CREATE TABLE input (s STRING); 3 | INSERT INTO input VALUES (TRIM(readfile('input.txt'), char(10))); 4 | 5 | CREATE TABLE coords (y INT, x INT, c VARCHAR, beginning BIT, ending BIT); 6 | WITH RECURSIVE 7 | nn (y, x, c, rest) 8 | AS ( 9 | SELECT 0, -1, NULL, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN nn.y + 1 ELSE nn.y END, 13 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN -1 ELSE nn.x + 1 END, 14 | CASE WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN NULL ELSE SUBSTR(nn.rest, 1, 1) END, 15 | SUBSTR(nn.rest, 2) 16 | FROM nn 17 | WHERE nn.rest != '' 18 | ) 19 | INSERT INTO coords 20 | SELECT y, x, c, 0, 0 FROM nn WHERE c IS NOT NULL; 21 | 22 | UPDATE coords SET c = 'a', beginning = 1 WHERE c = 'S'; 23 | UPDATE coords SET c = 'z', ending = 1 WHERE c = 'E'; 24 | 25 | CREATE TABLE deltas (dy INT, dx INT); 26 | INSERT INTO deltas VALUES (1, 0), (-1, 0), (0, 1), (0, -1); 27 | 28 | WITH RECURSIVE 29 | nn (it, todo, seen) 30 | AS ( 31 | SELECT 0, json_array(json_array(y, x, 0)), json_array() 32 | FROM coords WHERE ending = 1 33 | UNION 34 | SELECT 35 | nn.it + 1, 36 | ( 37 | SELECT json_group_array(json(value)) 38 | FROM ( 39 | SELECT value, json_extract(value, '$[2]') FROM ( 40 | -- remove lowest 41 | SELECT j.value FROM json_each(nn.todo) j WHERE j.key > 0 42 | UNION ALL 43 | -- extend rest 44 | SELECT json_array( 45 | json_extract(nn.todo, '$[0][0]') + deltas.dy, 46 | json_extract(nn.todo, '$[0][1]') + deltas.dx, 47 | json_extract(nn.todo, '$[0][2]') + 1 48 | ) 49 | FROM deltas 50 | INNER JOIN coords cand ON 51 | json_extract(nn.todo, '$[0][0]') + deltas.dy = cand.y AND 52 | json_extract(nn.todo, '$[0][1]') + deltas.dx = cand.x 53 | INNER JOIN coords curr ON 54 | json_extract(nn.todo, '$[0][0]') = curr.y AND 55 | json_extract(nn.todo, '$[0][1]') = curr.x AND 56 | (UNICODE(cand.c) - UNICODE(curr.c)) >= -1 57 | WHERE 58 | ( 59 | SELECT COUNT(1) FROM json_each(nn.seen) j 60 | WHERE j.value = json_array( 61 | json_extract(nn.todo, '$[0][0]'), 62 | json_extract(nn.todo, '$[0][1]') 63 | ) 64 | ) = 0 65 | ) 66 | ORDER BY json_extract(value, '$[2]') ASC 67 | ) 68 | ), 69 | ( 70 | SELECT json_group_array(value) FROM ( 71 | SELECT j.value FROM json_each(nn.seen) j 72 | UNION 73 | SELECT json_array( 74 | json_extract(nn.todo, '$[0][0]'), 75 | json_extract(nn.todo, '$[0][1]') 76 | ) 77 | ) 78 | ) 79 | FROM nn 80 | WHERE 81 | ( 82 | SELECT coords.c FROM coords WHERE 83 | coords.y = json_extract(nn.todo, '$[0][0]') AND 84 | coords.x = json_extract(nn.todo, '$[0][1]') 85 | ) != 'a' 86 | ) 87 | SELECT json_extract(nn.todo, '$[0][2]') FROM nn ORDER BY nn.it DESC LIMIT 1; 88 | -------------------------------------------------------------------------------- /day13/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day13/__init__.py -------------------------------------------------------------------------------- /day13/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import ast 5 | import itertools 6 | import os.path 7 | from typing import TYPE_CHECKING 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | if TYPE_CHECKING: 14 | from typing import TypeAlias 15 | 16 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 17 | 18 | ListLike: TypeAlias = "int | list['ListLike']" 19 | 20 | 21 | def compare(lhs: ListLike, rhs: ListLike) -> int: 22 | if isinstance(lhs, int) and not isinstance(rhs, int): 23 | lhs = [lhs] 24 | elif not isinstance(lhs, int) and isinstance(rhs, int): 25 | rhs = [rhs] 26 | 27 | if isinstance(lhs, int) and isinstance(rhs, int): 28 | return lhs - rhs 29 | elif isinstance(lhs, list) and isinstance(rhs, list): 30 | for a, b in itertools.zip_longest(lhs, rhs): 31 | if a is None: 32 | return -1 33 | elif b is None: 34 | return 1 35 | 36 | compared = compare(a, b) 37 | if compared != 0: 38 | return compared 39 | else: 40 | return 0 41 | else: 42 | raise AssertionError('unreachable') 43 | 44 | 45 | def compute(s: str) -> int: 46 | ret = 0 47 | 48 | for i, pair in enumerate(s.split('\n\n'), 1): 49 | l1_s, l2_s = pair.splitlines() 50 | l1 = ast.literal_eval(l1_s) 51 | l2 = ast.literal_eval(l2_s) 52 | 53 | if compare(l1, l2) <= 0: 54 | ret += i 55 | 56 | return ret 57 | 58 | 59 | INPUT_S = '''\ 60 | [1,1,3,1,1] 61 | [1,1,5,1,1] 62 | 63 | [[1],[2,3,4]] 64 | [[1],4] 65 | 66 | [9] 67 | [[8,7,6]] 68 | 69 | [[4,4],4,4] 70 | [[4,4],4,4,4] 71 | 72 | [7,7,7,7] 73 | [7,7,7] 74 | 75 | [] 76 | [3] 77 | 78 | [[[]]] 79 | [[]] 80 | 81 | [1,[2,[3,[4,[5,6,7]]]],8,9] 82 | [1,[2,[3,[4,[5,6,0]]]],8,9] 83 | ''' 84 | EXPECTED = 13 85 | 86 | 87 | @pytest.mark.parametrize( 88 | ('input_s', 'expected'), 89 | ( 90 | (INPUT_S, EXPECTED), 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 | -------------------------------------------------------------------------------- /day13/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import ast 5 | import functools 6 | import itertools 7 | import os.path 8 | from typing import TYPE_CHECKING 9 | 10 | import pytest 11 | 12 | import support 13 | 14 | if TYPE_CHECKING: 15 | from typing import TypeAlias 16 | 17 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 18 | 19 | ListLike: TypeAlias = "int | list['ListLike']" 20 | 21 | 22 | def compare(lhs: ListLike, rhs: ListLike) -> int: 23 | if isinstance(lhs, int) and not isinstance(rhs, int): 24 | lhs = [lhs] 25 | elif not isinstance(lhs, int) and isinstance(rhs, int): 26 | rhs = [rhs] 27 | 28 | if isinstance(lhs, int) and isinstance(rhs, int): 29 | return lhs - rhs 30 | elif isinstance(lhs, list) and isinstance(rhs, list): 31 | for a, b in itertools.zip_longest(lhs, rhs): 32 | if a is None: 33 | return -1 34 | elif b is None: 35 | return 1 36 | 37 | compared = compare(a, b) 38 | if compared != 0: 39 | return compared 40 | else: 41 | return 0 42 | else: 43 | raise AssertionError('unreachable') 44 | 45 | 46 | def compute(s: str) -> int: 47 | s = s.replace('\n\n', '\n') 48 | lists = [ast.literal_eval(line) for line in s.splitlines()] 49 | 50 | lists.extend(([[2]], [[6]])) 51 | lists.sort(key=functools.cmp_to_key(compare)) 52 | 53 | return (lists.index([[2]]) + 1) * (lists.index([[6]]) + 1) 54 | 55 | 56 | INPUT_S = '''\ 57 | [1,1,3,1,1] 58 | [1,1,5,1,1] 59 | 60 | [[1],[2,3,4]] 61 | [[1],4] 62 | 63 | [9] 64 | [[8,7,6]] 65 | 66 | [[4,4],4,4] 67 | [[4,4],4,4,4] 68 | 69 | [7,7,7,7] 70 | [7,7,7] 71 | 72 | [] 73 | [3] 74 | 75 | [[[]]] 76 | [[]] 77 | 78 | [1,[2,[3,[4,[5,6,7]]]],8,9] 79 | [1,[2,[3,[4,[5,6,0]]]],8,9] 80 | ''' 81 | EXPECTED = 140 82 | 83 | 84 | @pytest.mark.parametrize( 85 | ('input_s', 'expected'), 86 | ( 87 | (INPUT_S, EXPECTED), 88 | ), 89 | ) 90 | def test(input_s: str, expected: int) -> None: 91 | assert compute(input_s) == expected 92 | 93 | 94 | def main() -> int: 95 | parser = argparse.ArgumentParser() 96 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 97 | args = parser.parse_args() 98 | 99 | with open(args.data_file) as f, support.timing(): 100 | print(compute(f.read())) 101 | 102 | return 0 103 | 104 | 105 | if __name__ == '__main__': 106 | raise SystemExit(main()) 107 | -------------------------------------------------------------------------------- /day14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day14/__init__.py -------------------------------------------------------------------------------- /day14/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 | coords = set() 15 | for line in s.splitlines(): 16 | points = line.split(' -> ') 17 | prev_x, prev_y = support.parse_point_comma(points[0]) 18 | for point in points[1:]: 19 | cand_x, cand_y = support.parse_point_comma(point) 20 | if cand_x == prev_x: 21 | for y in range(min(cand_y, prev_y), max(cand_y, prev_y) + 1): 22 | coords.add((cand_x, y)) 23 | else: 24 | for x in range(min(cand_x, prev_x), max(cand_x, prev_x) + 1): 25 | coords.add((x, cand_y)) 26 | prev_x, prev_y = cand_x, cand_y 27 | 28 | _, by = support.bounds(coords) 29 | 30 | i = 0 31 | 32 | while True: 33 | px, py = 500, 0 34 | while True: 35 | if (px, py + 1) not in coords: 36 | py += 1 37 | elif (px - 1, py + 1) not in coords: 38 | px -= 1 39 | py += 1 40 | elif (px + 1, py + 1) not in coords: 41 | px += 1 42 | py += 1 43 | else: 44 | coords.add((px, py)) 45 | break 46 | 47 | if py > by.max: 48 | return i 49 | 50 | i += 1 51 | 52 | raise AssertionError('unreachable') 53 | 54 | 55 | INPUT_S = '''\ 56 | 498,4 -> 498,6 -> 496,6 57 | 503,4 -> 502,4 -> 502,9 -> 494,9 58 | ''' 59 | EXPECTED = 24 60 | 61 | 62 | @pytest.mark.parametrize( 63 | ('input_s', 'expected'), 64 | ( 65 | (INPUT_S, EXPECTED), 66 | ), 67 | ) 68 | def test(input_s: str, expected: int) -> None: 69 | assert compute(input_s) == expected 70 | 71 | 72 | def main() -> int: 73 | parser = argparse.ArgumentParser() 74 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 75 | args = parser.parse_args() 76 | 77 | with open(args.data_file) as f, support.timing(): 78 | print(compute(f.read())) 79 | 80 | return 0 81 | 82 | 83 | if __name__ == '__main__': 84 | raise SystemExit(main()) 85 | -------------------------------------------------------------------------------- /day14/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 | coords = set() 15 | for line in s.splitlines(): 16 | points = line.split(' -> ') 17 | prev_x, prev_y = support.parse_point_comma(points[0]) 18 | for point in points[1:]: 19 | cand_x, cand_y = support.parse_point_comma(point) 20 | if cand_x == prev_x: 21 | for y in range(min(cand_y, prev_y), max(cand_y, prev_y) + 1): 22 | coords.add((cand_x, y)) 23 | else: 24 | for x in range(min(cand_x, prev_x), max(cand_x, prev_x) + 1): 25 | coords.add((x, cand_y)) 26 | prev_x, prev_y = cand_x, cand_y 27 | 28 | _, by = support.bounds(coords) 29 | 30 | i = 0 31 | 32 | while True: 33 | px, py = 500, 0 34 | while True: 35 | if (px, py) in coords: 36 | return i 37 | elif py == by.max + 1: 38 | coords.add((px, py)) 39 | break 40 | elif (px, py + 1) not in coords: 41 | py += 1 42 | elif (px - 1, py + 1) not in coords: 43 | px -= 1 44 | py += 1 45 | elif (px + 1, py + 1) not in coords: 46 | px += 1 47 | py += 1 48 | else: 49 | coords.add((px, py)) 50 | break 51 | 52 | i += 1 53 | 54 | raise AssertionError('unreachable') 55 | 56 | 57 | INPUT_S = '''\ 58 | 498,4 -> 498,6 -> 496,6 59 | 503,4 -> 502,4 -> 502,9 -> 494,9 60 | ''' 61 | EXPECTED = 93 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 | -------------------------------------------------------------------------------- /day15/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day15/__init__.py -------------------------------------------------------------------------------- /day15/input.txt: -------------------------------------------------------------------------------- 1 | Sensor at x=1384790, y=3850432: closest beacon is at x=2674241, y=4192888 2 | Sensor at x=2825953, y=288046: closest beacon is at x=2154954, y=-342775 3 | Sensor at x=3553843, y=2822363: closest beacon is at x=3444765, y=2347460 4 | Sensor at x=2495377, y=3130491: closest beacon is at x=2761496, y=2831113 5 | Sensor at x=1329263, y=1778185: closest beacon is at x=2729595, y=2000000 6 | Sensor at x=2882039, y=2206085: closest beacon is at x=2729595, y=2000000 7 | Sensor at x=3903141, y=2510440: closest beacon is at x=4006219, y=3011198 8 | Sensor at x=3403454, y=3996578: closest beacon is at x=3754119, y=4475047 9 | Sensor at x=3630476, y=1048796: closest beacon is at x=3444765, y=2347460 10 | Sensor at x=16252, y=2089672: closest beacon is at x=-276514, y=2995794 11 | Sensor at x=428672, y=1150723: closest beacon is at x=-281319, y=668868 12 | Sensor at x=2939101, y=3624676: closest beacon is at x=2674241, y=4192888 13 | Sensor at x=3166958, y=2890076: closest beacon is at x=2761496, y=2831113 14 | Sensor at x=3758241, y=3546895: closest beacon is at x=4006219, y=3011198 15 | Sensor at x=218942, y=3011070: closest beacon is at x=-276514, y=2995794 16 | Sensor at x=52656, y=3484635: closest beacon is at x=-276514, y=2995794 17 | Sensor at x=2057106, y=405314: closest beacon is at x=2154954, y=-342775 18 | Sensor at x=1966905, y=2495701: closest beacon is at x=2761496, y=2831113 19 | Sensor at x=511976, y=2696731: closest beacon is at x=-276514, y=2995794 20 | Sensor at x=3094465, y=2478570: closest beacon is at x=3444765, y=2347460 21 | Sensor at x=806671, y=228252: closest beacon is at x=-281319, y=668868 22 | Sensor at x=3011731, y=1976307: closest beacon is at x=2729595, y=2000000 23 | -------------------------------------------------------------------------------- /day15/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 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 | reg = re.compile( 15 | r'^Sensor at x=(-?\d+), y=(-?\d+): ' 16 | r'closest beacon is at x=(-?\d+), y=(-?\d+)$', 17 | ) 18 | 19 | 20 | class Sensor(NamedTuple): 21 | x: int 22 | y: int 23 | beacon_x: int 24 | beacon_y: int 25 | 26 | @property 27 | def distance(self) -> int: 28 | return abs(self.x - self.beacon_x) + abs(self.y - self.beacon_y) 29 | 30 | 31 | def compute(s: str, y: int = 2000000) -> int: 32 | beacons = set() 33 | coords = set() 34 | 35 | for line in s.splitlines(): 36 | match = reg.match(line) 37 | assert match is not None 38 | sensor = Sensor( 39 | int(match[1]), int(match[2]), 40 | int(match[3]), int(match[4]), 41 | ) 42 | beacons.add((sensor.beacon_x, sensor.beacon_y)) 43 | dist = sensor.distance 44 | 45 | right_term = dist - abs(y - sensor.y) 46 | for x in range(sensor.x - right_term, sensor.x + right_term + 1): 47 | coords.add((x, y)) 48 | 49 | return len(coords - beacons) 50 | 51 | 52 | INPUT_S = '''\ 53 | Sensor at x=2, y=18: closest beacon is at x=-2, y=15 54 | Sensor at x=9, y=16: closest beacon is at x=10, y=16 55 | Sensor at x=13, y=2: closest beacon is at x=15, y=3 56 | Sensor at x=12, y=14: closest beacon is at x=10, y=16 57 | Sensor at x=10, y=20: closest beacon is at x=10, y=16 58 | Sensor at x=14, y=17: closest beacon is at x=10, y=16 59 | Sensor at x=8, y=7: closest beacon is at x=2, y=10 60 | Sensor at x=2, y=0: closest beacon is at x=2, y=10 61 | Sensor at x=0, y=11: closest beacon is at x=2, y=10 62 | Sensor at x=20, y=14: closest beacon is at x=25, y=17 63 | Sensor at x=17, y=20: closest beacon is at x=21, y=22 64 | Sensor at x=16, y=7: closest beacon is at x=15, y=3 65 | Sensor at x=14, y=3: closest beacon is at x=15, y=3 66 | Sensor at x=20, y=1: closest beacon is at x=15, y=3 67 | ''' 68 | EXPECTED = 26 69 | 70 | 71 | @pytest.mark.parametrize( 72 | ('input_s', 'expected'), 73 | ( 74 | (INPUT_S, EXPECTED), 75 | ), 76 | ) 77 | def test(input_s: str, expected: int) -> None: 78 | assert compute(input_s, y=10) == expected 79 | 80 | 81 | def main() -> int: 82 | parser = argparse.ArgumentParser() 83 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 84 | args = parser.parse_args() 85 | 86 | with open(args.data_file) as f, support.timing(): 87 | print(compute(f.read())) 88 | 89 | return 0 90 | 91 | 92 | if __name__ == '__main__': 93 | raise SystemExit(main()) 94 | -------------------------------------------------------------------------------- /day15/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import re 6 | import sys 7 | from typing import Any 8 | from typing import NamedTuple 9 | 10 | import pytest 11 | from z3 import If 12 | from z3 import Int 13 | from z3 import Optimize 14 | from z3 import sat 15 | 16 | import support 17 | 18 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 19 | 20 | reg = re.compile( 21 | r'^Sensor at x=(-?\d+), y=(-?\d+): ' 22 | r'closest beacon is at x=(-?\d+), y=(-?\d+)$', 23 | ) 24 | 25 | 26 | class Sensor(NamedTuple): 27 | x: int 28 | y: int 29 | beacon_x: int 30 | beacon_y: int 31 | 32 | def c_dist(self, x: int, y: int) -> int: 33 | return abs(self.x - x) + abs(self.y - y) 34 | 35 | @property 36 | def distance(self) -> int: 37 | return abs(self.x - self.beacon_x) + abs(self.y - self.beacon_y) 38 | 39 | 40 | def zabs(expr: Any) -> If: 41 | return If(expr > 0, expr, -expr) 42 | 43 | 44 | def compute_z3(s: str, m: int = 4000000) -> int: 45 | o = Optimize() 46 | X = Int('X') 47 | Y = Int('Y') 48 | o.add(0 <= X) 49 | o.add(0 <= Y) 50 | o.add(X <= m) 51 | o.add(Y <= m) 52 | 53 | for line in s.splitlines(): 54 | match = reg.match(line) 55 | assert match is not None 56 | sensor = Sensor( 57 | int(match[1]), int(match[2]), 58 | int(match[3]), int(match[4]), 59 | ) 60 | 61 | o.add((zabs(sensor.x - X) + zabs(sensor.y - Y)) > sensor.distance) 62 | 63 | assert o.check() == sat 64 | res = o.model() 65 | return res[X].as_long() * 4000000 + res[Y].as_long() 66 | 67 | 68 | def compute(s: str, m: int = 4000000) -> int: 69 | beacons = set() 70 | 71 | sensors = [] 72 | for line in s.splitlines(): 73 | match = reg.match(line) 74 | assert match is not None 75 | sensor = Sensor( 76 | int(match[1]), int(match[2]), 77 | int(match[3]), int(match[4]), 78 | ) 79 | sensors.append(sensor) 80 | beacons.add((sensor.beacon_x, sensor.beacon_y)) 81 | 82 | for sensor in sensors: 83 | top_y = sensor.y + sensor.distance - 1 84 | bottom_y = sensor.y - sensor.distance - 1 85 | 86 | for i in range(sensor.distance): 87 | for x, y in ( 88 | (sensor.x + i, top_y - i), 89 | (sensor.x - i, top_y - i), 90 | (sensor.x + i, bottom_y + i), 91 | (sensor.x - i, bottom_y + i), 92 | ): 93 | if x < 0 or y < 0 or x > m or y > m: 94 | continue 95 | elif (x, y) in beacons: 96 | continue 97 | 98 | for sensor_2 in sensors: 99 | if sensor_2.c_dist(x, y) <= sensor_2.distance: 100 | break 101 | else: 102 | return x * 4000000 + y 103 | 104 | raise AssertionError('unreachable') 105 | 106 | 107 | INPUT_S = '''\ 108 | Sensor at x=2, y=18: closest beacon is at x=-2, y=15 109 | Sensor at x=9, y=16: closest beacon is at x=10, y=16 110 | Sensor at x=13, y=2: closest beacon is at x=15, y=3 111 | Sensor at x=12, y=14: closest beacon is at x=10, y=16 112 | Sensor at x=10, y=20: closest beacon is at x=10, y=16 113 | Sensor at x=14, y=17: closest beacon is at x=10, y=16 114 | Sensor at x=8, y=7: closest beacon is at x=2, y=10 115 | Sensor at x=2, y=0: closest beacon is at x=2, y=10 116 | Sensor at x=0, y=11: closest beacon is at x=2, y=10 117 | Sensor at x=20, y=14: closest beacon is at x=25, y=17 118 | Sensor at x=17, y=20: closest beacon is at x=21, y=22 119 | Sensor at x=16, y=7: closest beacon is at x=15, y=3 120 | Sensor at x=14, y=3: closest beacon is at x=15, y=3 121 | Sensor at x=20, y=1: closest beacon is at x=15, y=3 122 | ''' 123 | EXPECTED = 56000011 124 | 125 | 126 | @pytest.mark.parametrize( 127 | ('input_s', 'expected'), 128 | ( 129 | (INPUT_S, EXPECTED), 130 | ), 131 | ) 132 | def test(input_s: str, expected: int) -> None: 133 | assert compute(input_s, m=20) == expected 134 | 135 | 136 | def main() -> int: 137 | parser = argparse.ArgumentParser() 138 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 139 | args = parser.parse_args() 140 | 141 | with open(args.data_file) as f, support.timing('z3'): 142 | print(compute_z3(f.read()), file=sys.stderr) 143 | 144 | with open(args.data_file) as f, support.timing(): 145 | print(compute(f.read())) 146 | 147 | return 0 148 | 149 | 150 | if __name__ == '__main__': 151 | raise SystemExit(main()) 152 | -------------------------------------------------------------------------------- /day16/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day16/__init__.py -------------------------------------------------------------------------------- /day16/input.txt: -------------------------------------------------------------------------------- 1 | Valve TM has flow rate=3; tunnels lead to valves GU, KQ, BV, MK 2 | Valve BX has flow rate=0; tunnels lead to valves CD, HX 3 | Valve GV has flow rate=8; tunnels lead to valves MP, SE 4 | Valve OI has flow rate=0; tunnels lead to valves ZB, RG 5 | Valve OY has flow rate=0; tunnels lead to valves XG, ZB 6 | Valve EZ has flow rate=0; tunnels lead to valves OU, LI 7 | Valve TN has flow rate=0; tunnels lead to valves DT, GU 8 | Valve SE has flow rate=0; tunnels lead to valves GV, CD 9 | Valve SG has flow rate=0; tunnels lead to valves XR, NK 10 | Valve EB has flow rate=0; tunnels lead to valves SJ, CE 11 | Valve QB has flow rate=0; tunnels lead to valves AW, MI 12 | Valve GU has flow rate=0; tunnels lead to valves TN, TM 13 | Valve AW has flow rate=11; tunnels lead to valves QB, IG, IK, VK 14 | Valve IG has flow rate=0; tunnels lead to valves AW, SH 15 | Valve MJ has flow rate=0; tunnels lead to valves IK, XR 16 | Valve HX has flow rate=0; tunnels lead to valves BX, AA 17 | Valve IK has flow rate=0; tunnels lead to valves MJ, AW 18 | Valve QZ has flow rate=0; tunnels lead to valves AF, XG 19 | Valve CV has flow rate=0; tunnels lead to valves KT, AA 20 | Valve ES has flow rate=0; tunnels lead to valves BV, CD 21 | Valve NK has flow rate=0; tunnels lead to valves YQ, SG 22 | Valve SL has flow rate=0; tunnels lead to valves DT, XL 23 | Valve RG has flow rate=17; tunnels lead to valves SJ, OI, WC 24 | Valve ZB has flow rate=9; tunnels lead to valves OY, MP, DI, OX, OI 25 | Valve SJ has flow rate=0; tunnels lead to valves RG, EB 26 | Valve GF has flow rate=19; tunnels lead to valves DQ, SH, IH 27 | Valve OU has flow rate=10; tunnels lead to valves EZ, TL, WC 28 | Valve TL has flow rate=0; tunnels lead to valves OU, OX 29 | Valve XG has flow rate=18; tunnels lead to valves QZ, OY 30 | Valve EK has flow rate=20; tunnels lead to valves FD, MI 31 | Valve BV has flow rate=0; tunnels lead to valves TM, ES 32 | Valve AA has flow rate=0; tunnels lead to valves CV, HX, TR, MK, DQ 33 | Valve UO has flow rate=23; tunnel leads to valve AF 34 | Valve LI has flow rate=0; tunnels lead to valves EZ, CE 35 | Valve MI has flow rate=0; tunnels lead to valves EK, QB 36 | Valve MP has flow rate=0; tunnels lead to valves GV, ZB 37 | Valve YQ has flow rate=14; tunnels lead to valves VK, MG, NK 38 | Valve AF has flow rate=0; tunnels lead to valves UO, QZ 39 | Valve SH has flow rate=0; tunnels lead to valves IG, GF 40 | Valve FD has flow rate=0; tunnels lead to valves IH, EK 41 | Valve KQ has flow rate=0; tunnels lead to valves TM, FQ 42 | Valve DI has flow rate=0; tunnels lead to valves ZB, CD 43 | Valve KT has flow rate=0; tunnels lead to valves DT, CV 44 | Valve MG has flow rate=0; tunnels lead to valves NQ, YQ 45 | Valve DQ has flow rate=0; tunnels lead to valves GF, AA 46 | Valve CE has flow rate=21; tunnels lead to valves LI, EB 47 | Valve MK has flow rate=0; tunnels lead to valves AA, TM 48 | Valve XL has flow rate=0; tunnels lead to valves CD, SL 49 | Valve OX has flow rate=0; tunnels lead to valves TL, ZB 50 | Valve DT has flow rate=5; tunnels lead to valves NQ, TP, KT, SL, TN 51 | Valve IH has flow rate=0; tunnels lead to valves GF, FD 52 | Valve TP has flow rate=0; tunnels lead to valves XR, DT 53 | Valve FQ has flow rate=0; tunnels lead to valves XR, KQ 54 | Valve CD has flow rate=6; tunnels lead to valves DI, BX, XL, ES, SE 55 | Valve XR has flow rate=7; tunnels lead to valves TR, FQ, TP, MJ, SG 56 | Valve VK has flow rate=0; tunnels lead to valves YQ, AW 57 | Valve WC has flow rate=0; tunnels lead to valves RG, OU 58 | Valve TR has flow rate=0; tunnels lead to valves XR, AA 59 | Valve NQ has flow rate=0; tunnels lead to valves DT, MG 60 | -------------------------------------------------------------------------------- /day16/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import itertools 6 | import os.path 7 | import re 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | RATE = re.compile(r'rate=(\d+);') 14 | VALVES = re.compile(r'to valves? (.*)$') 15 | 16 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 17 | 18 | 19 | def compute(s: str) -> int: 20 | edges = {} 21 | rates = {} 22 | 23 | for line in s.splitlines(): 24 | _, name, *_ = line.split() 25 | match_valves = VALVES.search(line) 26 | assert match_valves is not None 27 | targets = match_valves[1].split(', ') 28 | match = RATE.search(line) 29 | assert match is not None 30 | edges[name] = targets 31 | rates[name] = int(match[1]) 32 | 33 | weights = {} 34 | positive_rates = frozenset(k for k, v in rates.items() if v) 35 | meaningful_edges = ['AA', *positive_rates] 36 | for a, b in itertools.combinations(meaningful_edges, r=2): 37 | todo_bfs: collections.deque[tuple[str, ...]] 38 | todo_bfs = collections.deque([(a,)]) 39 | while todo_bfs: 40 | path = todo_bfs.popleft() 41 | if path[-1] == b: 42 | break 43 | else: 44 | todo_bfs.extend( 45 | (*path, n) for n in edges[path[-1]] 46 | if n not in path 47 | ) 48 | weights[(a, b)] = len(path) 49 | weights[(b, a)] = len(path) 50 | 51 | # time to total 52 | best = -1 53 | todo: list[tuple[int, int, str, frozenset[str]]] 54 | todo = [(0, 0, 'AA', positive_rates)] 55 | while todo: 56 | score, time, current, possible = todo.pop() 57 | 58 | best = max(best, score) 59 | 60 | for p in possible: 61 | needed_time = time + weights[(current, p)] 62 | if needed_time < 30: 63 | todo.append(( 64 | score + (30 - needed_time) * rates[p], 65 | needed_time, 66 | p, 67 | possible - {p}, 68 | )) 69 | 70 | return best 71 | 72 | 73 | INPUT_S = '''\ 74 | Valve AA has flow rate=0; tunnels lead to valves DD, II, BB 75 | Valve BB has flow rate=13; tunnels lead to valves CC, AA 76 | Valve CC has flow rate=2; tunnels lead to valves DD, BB 77 | Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE 78 | Valve EE has flow rate=3; tunnels lead to valves FF, DD 79 | Valve FF has flow rate=0; tunnels lead to valves EE, GG 80 | Valve GG has flow rate=0; tunnels lead to valves FF, HH 81 | Valve HH has flow rate=22; tunnel leads to valve GG 82 | Valve II has flow rate=0; tunnels lead to valves AA, JJ 83 | Valve JJ has flow rate=21; tunnel leads to valve II 84 | ''' 85 | EXPECTED = 1651 86 | 87 | 88 | @pytest.mark.parametrize( 89 | ('input_s', 'expected'), 90 | ( 91 | (INPUT_S, EXPECTED), 92 | ), 93 | ) 94 | def test(input_s: str, expected: int) -> None: 95 | assert compute(input_s) == expected 96 | 97 | 98 | def main() -> int: 99 | parser = argparse.ArgumentParser() 100 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 101 | args = parser.parse_args() 102 | 103 | with open(args.data_file) as f, support.timing(): 104 | print(compute(f.read())) 105 | 106 | return 0 107 | 108 | 109 | if __name__ == '__main__': 110 | raise SystemExit(main()) 111 | -------------------------------------------------------------------------------- /day16/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import itertools 6 | import os.path 7 | import re 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | RATE = re.compile(r'rate=(\d+);') 14 | VALVES = re.compile(r'to valves? (.*)$') 15 | 16 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 17 | 18 | 19 | def compute(s: str) -> int: 20 | edges = {} 21 | rates = {} 22 | 23 | for line in s.splitlines(): 24 | _, name, *_ = line.split() 25 | match_valves = VALVES.search(line) 26 | assert match_valves is not None 27 | targets = match_valves[1].split(', ') 28 | match = RATE.search(line) 29 | assert match is not None 30 | edges[name] = targets 31 | rates[name] = int(match[1]) 32 | 33 | weights = {} 34 | positive_rates = frozenset(k for k, v in rates.items() if v) 35 | meaningful_edges = ['AA', *positive_rates] 36 | for a, b in itertools.combinations(meaningful_edges, r=2): 37 | todo_bfs: collections.deque[tuple[str, ...]] 38 | todo_bfs = collections.deque([(a,)]) 39 | while todo_bfs: 40 | path = todo_bfs.popleft() 41 | if path[-1] == b: 42 | break 43 | else: 44 | todo_bfs.extend( 45 | (*path, n) for n in edges[path[-1]] 46 | if n not in path 47 | ) 48 | weights[(a, b)] = len(path) 49 | weights[(b, a)] = len(path) 50 | 51 | # time to total 52 | best: dict[frozenset[str], int] = {} 53 | todo: list[tuple[int, int, str, frozenset[str]]] 54 | todo = [(0, 0, 'AA', frozenset())] 55 | while todo: 56 | score, time, current, seen = todo.pop() 57 | 58 | best[seen] = max(best.get(seen, score), score) 59 | 60 | for p in positive_rates - seen: 61 | needed_time = time + weights[(current, p)] 62 | if needed_time < 26: 63 | todo.append(( 64 | score + (26 - needed_time) * rates[p], 65 | needed_time, 66 | p, 67 | seen | {p}, 68 | )) 69 | 70 | return max( 71 | v1 + v2 72 | for (k1, v1), (k2, v2) in itertools.combinations(best.items(), r=2) 73 | if not k1 & k2 74 | ) 75 | 76 | 77 | INPUT_S = '''\ 78 | Valve AA has flow rate=0; tunnels lead to valves DD, II, BB 79 | Valve BB has flow rate=13; tunnels lead to valves CC, AA 80 | Valve CC has flow rate=2; tunnels lead to valves DD, BB 81 | Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE 82 | Valve EE has flow rate=3; tunnels lead to valves FF, DD 83 | Valve FF has flow rate=0; tunnels lead to valves EE, GG 84 | Valve GG has flow rate=0; tunnels lead to valves FF, HH 85 | Valve HH has flow rate=22; tunnel leads to valve GG 86 | Valve II has flow rate=0; tunnels lead to valves AA, JJ 87 | Valve JJ has flow rate=21; tunnel leads to valve II 88 | ''' 89 | EXPECTED = 1707 90 | 91 | 92 | @pytest.mark.parametrize( 93 | ('input_s', 'expected'), 94 | ( 95 | (INPUT_S, EXPECTED), 96 | ), 97 | ) 98 | def test(input_s: str, expected: int) -> None: 99 | assert compute(input_s) == expected 100 | 101 | 102 | def main() -> int: 103 | parser = argparse.ArgumentParser() 104 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 105 | args = parser.parse_args() 106 | 107 | with open(args.data_file) as f, support.timing(): 108 | print(compute(f.read())) 109 | 110 | return 0 111 | 112 | 113 | if __name__ == '__main__': 114 | raise SystemExit(main()) 115 | -------------------------------------------------------------------------------- /day17/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day17/__init__.py -------------------------------------------------------------------------------- /day17/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 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 | class Piece: 16 | def __init__(self, places: set[tuple[int, int]]) -> None: 17 | self.places = frozenset((x, -y) for x, y in places) 18 | 19 | @functools.cached_property 20 | def height(self) -> int: 21 | _, by = support.bounds(self.places) 22 | return by.max - by.min + 1 23 | 24 | @functools.cached_property 25 | def width(self) -> int: 26 | bx, _ = support.bounds(self.places) 27 | return bx.max - bx.min + 1 28 | 29 | def at(self, dx: int, dy: int) -> set[tuple[int, int]]: 30 | return {(x + dx, y + dy) for x, y in self.places} 31 | 32 | def __hash__(self) -> int: 33 | return hash(self.places) 34 | 35 | 36 | PIECES_S = '''\ 37 | #### 38 | 39 | .#. 40 | ### 41 | .#. 42 | 43 | ..# 44 | ..# 45 | ### 46 | 47 | # 48 | # 49 | # 50 | # 51 | 52 | ## 53 | ## 54 | ''' 55 | PIECES = tuple( 56 | Piece(support.parse_coords_hash(piece)) 57 | for piece in PIECES_S.split('\n\n') 58 | ) 59 | 60 | 61 | def format_coords_hash(coords: set[tuple[int, int]]) -> str: 62 | bx, by = support.bounds(coords) 63 | return '\n'.join( 64 | ''.join( 65 | '#' if (x, y) in coords else ' ' 66 | for x in bx.range 67 | ) 68 | for y in range(by.max, by.min - 1, -1) 69 | ) 70 | 71 | 72 | def print_coords_hash(coords: set[tuple[int, int]]) -> None: 73 | print(format_coords_hash(coords)) 74 | 75 | 76 | def move( 77 | coords: set[tuple[int, int]], 78 | piece: Piece, 79 | x: int, 80 | y: int, 81 | direction: str, 82 | ) -> int: 83 | if direction == '<': 84 | if x == 0 or piece.at(x - 1, y) & coords: 85 | return x 86 | else: 87 | return x - 1 88 | elif direction == '>': 89 | if x == 7 - piece.width or piece.at(x + 1, y) & coords: 90 | return x 91 | else: 92 | return x + 1 93 | else: 94 | raise NotImplementedError(f'??? {direction=}') 95 | 96 | 97 | def compute(s: str) -> int: 98 | s = s.strip() 99 | 100 | pieces = itertools.cycle(PIECES) 101 | 102 | coords = support.parse_coords_hash('#######') 103 | max_height = 0 104 | gas = itertools.cycle(s) 105 | 106 | for _ in range(2022): 107 | piece = next(pieces) 108 | x = 2 109 | y = max_height + piece.height + 3 110 | 111 | while True: 112 | x = move(coords, piece, x, y, next(gas)) 113 | 114 | if piece.at(x, y - 1) & coords: 115 | coords |= piece.at(x, y) 116 | max_height = max(y, max_height) 117 | break 118 | else: 119 | y -= 1 120 | 121 | return max_height 122 | 123 | 124 | INPUT_S = '''\ 125 | >>><<><>><<<>><>>><<<>>><<<><<<>><>><<>> 126 | ''' 127 | EXPECTED = 3068 128 | 129 | 130 | @pytest.mark.parametrize( 131 | ('input_s', 'expected'), 132 | ( 133 | (INPUT_S, EXPECTED), 134 | ), 135 | ) 136 | def test(input_s: str, expected: int) -> None: 137 | assert compute(input_s) == expected 138 | 139 | 140 | def main() -> int: 141 | parser = argparse.ArgumentParser() 142 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 143 | args = parser.parse_args() 144 | 145 | with open(args.data_file) as f, support.timing(): 146 | print(compute(f.read())) 147 | 148 | return 0 149 | 150 | 151 | if __name__ == '__main__': 152 | raise SystemExit(main()) 153 | -------------------------------------------------------------------------------- /day18/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day18/__init__.py -------------------------------------------------------------------------------- /day18/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import Generator 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 adjacent_faces( 15 | x: int, 16 | y: int, 17 | z: int, 18 | ) -> Generator[tuple[int, int, int], None, None]: 19 | yield x + 1, y, z 20 | yield x - 1, y, z 21 | yield x, y + 1, z 22 | yield x, y - 1, z 23 | yield x, y, z + 1 24 | yield x, y, z - 1 25 | 26 | 27 | def compute(s: str) -> int: 28 | count = 0 29 | coords = set() 30 | 31 | for line in s.splitlines(): 32 | x, y, z = map(int, line.split(',')) 33 | count += 6 34 | for cx, cy, cz in adjacent_faces(x, y, z): 35 | if (cx, cy, cz) in coords: 36 | count -= 2 37 | coords.add((x, y, z)) 38 | 39 | return count 40 | 41 | 42 | INPUT_S = '''\ 43 | 2,2,2 44 | 1,2,2 45 | 3,2,2 46 | 2,1,2 47 | 2,3,2 48 | 2,2,1 49 | 2,2,3 50 | 2,2,4 51 | 2,2,6 52 | 1,2,5 53 | 3,2,5 54 | 2,1,5 55 | 2,3,5 56 | ''' 57 | EXPECTED = 64 58 | 59 | 60 | @pytest.mark.parametrize( 61 | ('input_s', 'expected'), 62 | ( 63 | (INPUT_S, EXPECTED), 64 | ), 65 | ) 66 | def test(input_s: str, expected: int) -> None: 67 | assert compute(input_s) == expected 68 | 69 | 70 | def main() -> int: 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 73 | args = parser.parse_args() 74 | 75 | with open(args.data_file) as f, support.timing(): 76 | print(compute(f.read())) 77 | 78 | return 0 79 | 80 | 81 | if __name__ == '__main__': 82 | raise SystemExit(main()) 83 | -------------------------------------------------------------------------------- /day18/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import Generator 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 adjacent_faces( 15 | x: int, 16 | y: int, 17 | z: int, 18 | ) -> Generator[tuple[int, int, int], None, None]: 19 | yield x + 1, y, z 20 | yield x - 1, y, z 21 | yield x, y + 1, z 22 | yield x, y - 1, z 23 | yield x, y, z + 1 24 | yield x, y, z - 1 25 | 26 | 27 | def surface_area(pts: set[tuple[int, int, int]]) -> int: 28 | count = 0 29 | coords = set() 30 | 31 | for pt in pts: 32 | count += 6 33 | for cpt in adjacent_faces(*pt): 34 | if cpt in coords: 35 | count -= 2 36 | coords.add(pt) 37 | return count 38 | 39 | 40 | def compute(s: str) -> int: 41 | count = 0 42 | coords = set() 43 | 44 | for line in s.splitlines(): 45 | x, y, z = map(int, line.split(',')) 46 | count += 6 47 | for cx, cy, cz in adjacent_faces(x, y, z): 48 | if (cx, cy, cz) in coords: 49 | count -= 2 50 | coords.add((x, y, z)) 51 | 52 | bx, by, bz = support.bounds(coords) 53 | 54 | all_coords = { 55 | (x, y, z) 56 | for x in range(bx.min - 1, bx.max + 2) 57 | for y in range(by.min - 1, by.max + 2) 58 | for z in range(bz.min - 1, bz.max + 2) 59 | } 60 | 61 | remaining = all_coords - coords 62 | 63 | todo = [min(remaining)] 64 | while todo: 65 | pt = todo.pop() 66 | if pt in remaining: 67 | remaining.discard(pt) 68 | else: 69 | continue 70 | 71 | for cpt in adjacent_faces(*pt): 72 | todo.append(cpt) 73 | 74 | return count - surface_area(remaining) 75 | 76 | 77 | INPUT_S = '''\ 78 | 2,2,2 79 | 1,2,2 80 | 3,2,2 81 | 2,1,2 82 | 2,3,2 83 | 2,2,1 84 | 2,2,3 85 | 2,2,4 86 | 2,2,6 87 | 1,2,5 88 | 3,2,5 89 | 2,1,5 90 | 2,3,5 91 | ''' 92 | EXPECTED = 58 93 | 94 | 95 | @pytest.mark.parametrize( 96 | ('input_s', 'expected'), 97 | ( 98 | (INPUT_S, EXPECTED), 99 | ), 100 | ) 101 | def test(input_s: str, expected: int) -> None: 102 | assert compute(input_s) == expected 103 | 104 | 105 | def main() -> int: 106 | parser = argparse.ArgumentParser() 107 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 108 | args = parser.parse_args() 109 | 110 | with open(args.data_file) as f, support.timing(): 111 | print(compute(f.read())) 112 | 113 | return 0 114 | 115 | 116 | if __name__ == '__main__': 117 | raise SystemExit(main()) 118 | -------------------------------------------------------------------------------- /day19/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day19/__init__.py -------------------------------------------------------------------------------- /day19/input.txt: -------------------------------------------------------------------------------- 1 | Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 2 ore and 15 obsidian. 2 | Blueprint 2: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 15 obsidian. 3 | Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 8 clay. Each geode robot costs 3 ore and 9 obsidian. 4 | Blueprint 4: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 12 clay. Each geode robot costs 2 ore and 10 obsidian. 5 | Blueprint 5: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 6 clay. Each geode robot costs 3 ore and 16 obsidian. 6 | Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 8 obsidian. 7 | Blueprint 7: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 3 ore and 9 obsidian. 8 | Blueprint 8: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 16 obsidian. 9 | Blueprint 9: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian. 10 | Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 4 ore and 7 obsidian. 11 | Blueprint 11: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 10 clay. Each geode robot costs 2 ore and 13 obsidian. 12 | Blueprint 12: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 7 obsidian. 13 | Blueprint 13: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 2 ore and 11 obsidian. 14 | Blueprint 14: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 9 clay. Each geode robot costs 3 ore and 7 obsidian. 15 | Blueprint 15: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 2 ore and 10 obsidian. 16 | Blueprint 16: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 3 ore and 19 obsidian. 17 | Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 8 obsidian. 18 | Blueprint 18: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 4 ore and 10 obsidian. 19 | Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 11 obsidian. 20 | Blueprint 20: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 10 obsidian. 21 | Blueprint 21: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 10 clay. Each geode robot costs 2 ore and 14 obsidian. 22 | Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian. 23 | Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian. 24 | Blueprint 24: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 20 obsidian. 25 | Blueprint 25: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 2 ore and 13 obsidian. 26 | Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 2 ore and 10 obsidian. 27 | Blueprint 27: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 9 clay. Each geode robot costs 4 ore and 16 obsidian. 28 | Blueprint 28: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 9 clay. Each geode robot costs 3 ore and 15 obsidian. 29 | Blueprint 29: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 9 obsidian. 30 | Blueprint 30: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian. 31 | -------------------------------------------------------------------------------- /day20/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day20/__init__.py -------------------------------------------------------------------------------- /day20/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from unittest import mock 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 | orig_numbers = support.parse_numbers_split(s) 17 | numbers = collections.deque(list(enumerate(orig_numbers))) 18 | 19 | for i, num in enumerate(orig_numbers): 20 | idx = numbers.index((i, mock.ANY)) 21 | numbers.rotate(-idx) 22 | assert numbers.popleft() == (i, num) 23 | numbers.rotate(-num) 24 | numbers.appendleft((i, num)) 25 | 26 | idx_0 = numbers.index((mock.ANY, 0)) 27 | return sum( 28 | numbers[(idx_0 + i) % len(numbers)][1] 29 | for i in (1000, 2000, 3000) 30 | ) 31 | 32 | 33 | INPUT_S = '''\ 34 | 1 35 | 2 36 | -3 37 | 3 38 | -2 39 | 0 40 | 4 41 | ''' 42 | EXPECTED = 3 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 | -------------------------------------------------------------------------------- /day20/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from unittest import mock 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 | orig_numbers = support.parse_numbers_split(s) 17 | orig_numbers = [n * 811589153 for n in orig_numbers] 18 | numbers = collections.deque(list(enumerate(orig_numbers))) 19 | 20 | for _ in range(10): 21 | for i, num in enumerate(orig_numbers): 22 | idx = numbers.index((i, mock.ANY)) 23 | numbers.rotate(-idx) 24 | assert numbers.popleft() == (i, num) 25 | numbers.rotate(-num) 26 | numbers.appendleft((i, num)) 27 | 28 | idx_0 = numbers.index((mock.ANY, 0)) 29 | return sum( 30 | numbers[(idx_0 + i) % len(numbers)][1] 31 | for i in (1000, 2000, 3000) 32 | ) 33 | 34 | 35 | INPUT_S = '''\ 36 | 1 37 | 2 38 | -3 39 | 3 40 | -2 41 | 0 42 | 4 43 | ''' 44 | EXPECTED = 1623178306 45 | 46 | 47 | @pytest.mark.parametrize( 48 | ('input_s', 'expected'), 49 | ( 50 | (INPUT_S, EXPECTED), 51 | ), 52 | ) 53 | def test(input_s: str, expected: int) -> None: 54 | assert compute(input_s) == expected 55 | 56 | 57 | def main() -> int: 58 | parser = argparse.ArgumentParser() 59 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 60 | args = parser.parse_args() 61 | 62 | with open(args.data_file) as f, support.timing(): 63 | print(compute(f.read())) 64 | 65 | return 0 66 | 67 | 68 | if __name__ == '__main__': 69 | raise SystemExit(main()) 70 | -------------------------------------------------------------------------------- /day21/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day21/__init__.py -------------------------------------------------------------------------------- /day21/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import functools 5 | import operator 6 | import os.path 7 | from typing import Callable 8 | 9 | import pytest 10 | 11 | import support 12 | 13 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 14 | 15 | OPS = { 16 | '+': operator.add, 17 | '-': operator.sub, 18 | '/': lambda a, b: a // b, 19 | '*': operator.mul, 20 | } 21 | 22 | 23 | def compute(s: str) -> int: 24 | ops: dict[str, int | tuple[Callable[[int, int], int], str, str]] = {} 25 | 26 | for line in s.splitlines(): 27 | if len(line.split()) == 4: 28 | name, rest = line.split(': ') 29 | op1, op, op2 = rest.split() 30 | ops[name] = (OPS[op], op1, op2) 31 | else: 32 | name, rest = line.split(': ') 33 | ops[name] = int(rest) 34 | 35 | @functools.lru_cache 36 | def _value(name: str) -> int: 37 | val = ops[name] 38 | if isinstance(val, int): 39 | return val 40 | else: 41 | fn, left, right = val 42 | return fn(_value(left), _value(right)) 43 | 44 | return _value('root') 45 | 46 | 47 | INPUT_S = '''\ 48 | root: pppw + sjmn 49 | dbpl: 5 50 | cczh: sllz + lgvd 51 | zczc: 2 52 | ptdq: humn - dvpt 53 | dvpt: 3 54 | lfqf: 4 55 | humn: 5 56 | ljgn: 2 57 | sjmn: drzm * dbpl 58 | sllz: 4 59 | pppw: cczh / lfqf 60 | lgvd: ljgn * ptdq 61 | drzm: hmdt - zczc 62 | hmdt: 32 63 | ''' 64 | EXPECTED = 152 65 | 66 | 67 | @pytest.mark.parametrize( 68 | ('input_s', 'expected'), 69 | ( 70 | (INPUT_S, EXPECTED), 71 | ), 72 | ) 73 | def test(input_s: str, expected: int) -> None: 74 | assert compute(input_s) == expected 75 | 76 | 77 | def main() -> int: 78 | parser = argparse.ArgumentParser() 79 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 80 | args = parser.parse_args() 81 | 82 | with open(args.data_file) as f, support.timing(): 83 | print(compute(f.read())) 84 | 85 | return 0 86 | 87 | 88 | if __name__ == '__main__': 89 | raise SystemExit(main()) 90 | -------------------------------------------------------------------------------- /day21/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import contextlib 5 | import functools 6 | import os.path 7 | import sys 8 | 9 | import pytest 10 | from z3 import Int 11 | from z3 import Optimize 12 | from z3 import sat 13 | 14 | import support 15 | 16 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 17 | 18 | OPS = { 19 | '+': lambda a, b: a + b, 20 | '-': lambda a, b: a - b, 21 | '/': lambda a, b: a / b, 22 | '*': lambda a, b: a * b, 23 | } 24 | OPS2 = { 25 | **OPS, 26 | '/': lambda a, b: a // b, 27 | } 28 | 29 | 30 | def compute_z3(s: str) -> int: 31 | o = Optimize() 32 | 33 | for line in s.splitlines(): 34 | if line.startswith('humn:'): 35 | continue 36 | elif line.startswith('root:'): 37 | _, a, _, b = line.split() 38 | o.add(Int(a) == Int(b)) 39 | elif len(line.split()) == 4: 40 | name, rest = line.split(': ') 41 | op1, op, op2 = rest.split() 42 | o.add(Int(name) == OPS[op](Int(op1), Int(op2))) 43 | else: 44 | name, rest = line.split(': ') 45 | o.add(Int(name) == int(rest)) 46 | 47 | assert o.check() == sat 48 | 49 | return o.model()[Int('humn')].as_long() 50 | 51 | 52 | def compute(s: str) -> int: 53 | ops: dict[str, int | tuple[str, str, str]] = {} 54 | 55 | root_left = root_right = None 56 | for line in s.splitlines(): 57 | if line.startswith('humn:'): 58 | continue 59 | elif line.startswith('root:'): 60 | _, root_left, _, root_right = line.split() 61 | elif len(line.split()) == 4: 62 | name, rest = line.split(': ') 63 | op1, op, op2 = rest.split() 64 | ops[name] = (op1, op, op2) 65 | else: 66 | name, rest = line.split(': ') 67 | ops[name] = int(rest) 68 | 69 | assert root_left is not None and root_right is not None 70 | 71 | @functools.lru_cache 72 | def _value(s: str) -> int: 73 | val = ops[s] 74 | if isinstance(val, int): 75 | return val 76 | else: 77 | lhs_s, op, rhs_s = val 78 | return OPS2[op](_value(lhs_s), _value(rhs_s)) 79 | 80 | right_val = _value(root_right) 81 | assert isinstance(right_val, int) 82 | 83 | expr = ops[root_left] 84 | with contextlib.suppress(KeyError): # our loop ends when we lookup humn 85 | while True: 86 | assert not isinstance(expr, int), expr 87 | lhs_s, op, rhs_s = expr 88 | try: 89 | lhs = _value(lhs_s) 90 | except KeyError: # this side contains the variable! 91 | rhs = _value(rhs_s) 92 | 93 | if op == '*': 94 | right_val //= rhs 95 | elif op == '/': 96 | right_val *= rhs 97 | elif op == '+': 98 | right_val -= rhs 99 | elif op == '-': 100 | right_val += rhs 101 | else: 102 | raise AssertionError('unreachable') 103 | 104 | expr = ops[lhs_s] 105 | else: 106 | if op == '-': 107 | right_val -= lhs 108 | right_val *= -1 109 | elif op == '+': 110 | right_val -= lhs 111 | elif op == '*': 112 | right_val //= lhs 113 | elif op == '/': 114 | right_val = lhs // right_val 115 | else: 116 | raise AssertionError('unreachable') 117 | expr = ops[rhs_s] 118 | 119 | return right_val 120 | 121 | 122 | INPUT_S = '''\ 123 | root: pppw + sjmn 124 | dbpl: 5 125 | cczh: sllz + lgvd 126 | zczc: 2 127 | ptdq: humn - dvpt 128 | dvpt: 3 129 | lfqf: 4 130 | humn: 5 131 | ljgn: 2 132 | sjmn: drzm * dbpl 133 | sllz: 4 134 | pppw: cczh / lfqf 135 | lgvd: ljgn * ptdq 136 | drzm: hmdt - zczc 137 | hmdt: 32 138 | ''' 139 | EXPECTED = 301 140 | 141 | 142 | @pytest.mark.parametrize( 143 | ('input_s', 'expected'), 144 | ( 145 | (INPUT_S, EXPECTED), 146 | ), 147 | ) 148 | def test(input_s: str, expected: int) -> None: 149 | assert compute(input_s) == expected 150 | 151 | 152 | def main() -> int: 153 | parser = argparse.ArgumentParser() 154 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 155 | args = parser.parse_args() 156 | 157 | with open(args.data_file) as f, support.timing('z3'): 158 | print(compute_z3(f.read()), file=sys.stderr) 159 | 160 | print('Alexandra, please stop cheating', file=sys.stderr) 161 | 162 | with open(args.data_file) as f, support.timing(): 163 | print(compute(f.read())) 164 | 165 | return 0 166 | 167 | 168 | if __name__ == '__main__': 169 | raise SystemExit(main()) 170 | -------------------------------------------------------------------------------- /day22/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day22/__init__.py -------------------------------------------------------------------------------- /day22/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 | 14 | def compute(s: str) -> int: 15 | map_s, dirs = s.split('\n\n') 16 | 17 | coords = {} 18 | for y, line in enumerate(map_s.splitlines()): 19 | for x, c in enumerate(line): 20 | if c in '.#': 21 | coords[(x, y)] = c 22 | 23 | bx, by = support.bounds(coords) 24 | y = by.min 25 | x = min(x for (x, y) in coords if y == 0) 26 | direction = support.Direction4.RIGHT 27 | 28 | for part in re.split('([RL])', dirs): 29 | if part == 'R': 30 | direction = direction.cw 31 | elif part == 'L': 32 | direction = direction.ccw 33 | else: 34 | n = int(part) 35 | for _ in range(n): 36 | cand = direction.apply(x, y) 37 | if cand not in coords: 38 | if direction is support.Direction4.RIGHT: 39 | cand_x = min(cx for (cx, cy) in coords if cy == y) 40 | cand = (cand_x, y) 41 | elif direction is support.Direction4.LEFT: 42 | cand_x = max(cx for (cx, cy) in coords if cy == y) 43 | cand = (cand_x, y) 44 | elif direction is support.Direction4.UP: 45 | cand_y = max(cy for (cx, cy) in coords if cx == x) 46 | cand = (x, cand_y) 47 | elif direction is support.Direction4.DOWN: 48 | cand_y = min(cy for (cx, cy) in coords if cx == x) 49 | cand = (x, cand_y) 50 | else: 51 | raise NotImplementedError(direction) 52 | 53 | if coords[cand] == '#': 54 | break 55 | else: 56 | x, y = cand 57 | 58 | facing = { 59 | support.Direction4.RIGHT: 0, 60 | support.Direction4.LEFT: 2, 61 | support.Direction4.UP: 3, 62 | support.Direction4.DOWN: 1, 63 | } 64 | 65 | return 1000 * (y + 1) + 4 * (x + 1) + facing[direction] 66 | 67 | 68 | INPUT_S = '''\ 69 | ...# 70 | .#.. 71 | #... 72 | .... 73 | ...#.......# 74 | ........#... 75 | ..#....#.... 76 | ..........#. 77 | ...#.... 78 | .....#.. 79 | .#...... 80 | ......#. 81 | 82 | 10R5L5R10L4R5L5 83 | ''' 84 | EXPECTED = 6032 85 | 86 | 87 | @pytest.mark.parametrize( 88 | ('input_s', 'expected'), 89 | ( 90 | (INPUT_S, EXPECTED), 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 | -------------------------------------------------------------------------------- /day23/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day23/__init__.py -------------------------------------------------------------------------------- /day23/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 | -------------------------------------------------------------------------------- /day23/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) -> int: 16 | coords = support.parse_coords_hash(s) 17 | 18 | choices = collections.deque([ 19 | (support.Direction4.UP, ((-1, -1), (0, -1), (1, -1))), 20 | (support.Direction4.DOWN, ((-1, 1), (0, 1), (1, 1))), 21 | (support.Direction4.LEFT, ((-1, 1), (-1, 0), (-1, -1))), 22 | (support.Direction4.RIGHT, ((1, 1), (1, 0), (1, -1))), 23 | ]) 24 | 25 | for _ in range(10): 26 | moves: dict[tuple[int, int], list[tuple[int, int]]] 27 | moves = collections.defaultdict(list) 28 | 29 | for x, y in coords: 30 | if all( 31 | (cx, cy) not in coords 32 | for cx, cy in support.adjacent_8(x, y) 33 | ): 34 | continue 35 | 36 | for cand_dir, cand_points in choices: 37 | if all( 38 | (x + dx, y + dy) not in coords 39 | for dx, dy in cand_points 40 | ): 41 | moves[cand_dir.apply(x, y)].append((x, y)) 42 | break 43 | 44 | moved = {k: v[0] for k, v in moves.items() if len(v) == 1} 45 | coords = (coords - set(moved.values())) | moved.keys() 46 | 47 | choices.rotate(-1) 48 | 49 | bx, by = support.bounds(coords) 50 | return (bx.max - bx.min + 1) * (by.max - by.min + 1) - len(coords) 51 | 52 | 53 | INPUT_S = '''\ 54 | ....#.. 55 | ..###.# 56 | #...#.# 57 | .#...## 58 | #.###.. 59 | ##.#.## 60 | .#..#.. 61 | ''' 62 | EXPECTED = 110 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 | print('Alexandra, call me', file=sys.stderr) 81 | 82 | with open(args.data_file) as f, support.timing(): 83 | print(compute(f.read())) 84 | 85 | return 0 86 | 87 | 88 | if __name__ == '__main__': 89 | raise SystemExit(main()) 90 | -------------------------------------------------------------------------------- /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) -> int: 15 | coords = support.parse_coords_hash(s) 16 | 17 | choices = collections.deque([ 18 | (support.Direction4.UP, ((-1, -1), (0, -1), (1, -1))), 19 | (support.Direction4.DOWN, ((-1, 1), (0, 1), (1, 1))), 20 | (support.Direction4.LEFT, ((-1, 1), (-1, 0), (-1, -1))), 21 | (support.Direction4.RIGHT, ((1, 1), (1, 0), (1, -1))), 22 | ]) 23 | 24 | i = 0 25 | while True: 26 | i += 1 27 | moves: dict[tuple[int, int], list[tuple[int, int]]] 28 | moves = collections.defaultdict(list) 29 | 30 | for x, y in coords: 31 | if all( 32 | (cx, cy) not in coords 33 | for cx, cy in support.adjacent_8(x, y) 34 | ): 35 | continue 36 | 37 | for cand_dir, cand_points in choices: 38 | if all( 39 | (x + dx, y + dy) not in coords 40 | for dx, dy in cand_points 41 | ): 42 | moves[cand_dir.apply(x, y)].append((x, y)) 43 | break 44 | 45 | moved = {k: v[0] for k, v in moves.items() if len(v) == 1} 46 | coords = (coords - set(moved.values())) | moved.keys() 47 | 48 | choices.rotate(-1) 49 | 50 | if not moved: 51 | break 52 | 53 | return i 54 | 55 | 56 | INPUT_S = '''\ 57 | ....#.. 58 | ..###.# 59 | #...#.# 60 | .#...## 61 | #.###.. 62 | ##.#.## 63 | .#..#.. 64 | ''' 65 | EXPECTED = 20 66 | 67 | 68 | @ pytest.mark.parametrize( 69 | ('input_s', 'expected'), 70 | ( 71 | (INPUT_S, EXPECTED), 72 | ), 73 | ) 74 | def test(input_s: str, expected: int) -> None: 75 | assert compute(input_s) == expected 76 | 77 | 78 | def main() -> int: 79 | parser = argparse.ArgumentParser() 80 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 81 | args = parser.parse_args() 82 | 83 | with open(args.data_file) as f, support.timing(): 84 | print(compute(f.read())) 85 | 86 | return 0 87 | 88 | 89 | if __name__ == '__main__': 90 | raise SystemExit(main()) 91 | -------------------------------------------------------------------------------- /day24/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day24/__init__.py -------------------------------------------------------------------------------- /day24/input.txt: -------------------------------------------------------------------------------- 1 | #.#################################################################################################### 2 | #v>v><>^<>^.^vv<>><>>v.>v<.v>>v<>>v.^<^>v>^<^v.^>v^>>>v^.^>v>v<.^v<>># 3 | #>>v<^<>vvv.>^.v^^>^<>v.<^<>v^v^<>>.v.^<<<.^vv<^<^v>.vvv.<>>^>.<<<<<<.<^^.v<# 4 | #<<<>^^>>>>.v^>v<>^>^^v>v^^v..v^.<<^>^>.>v^^vv.<>>^><<>v.^>v<>^^<<>v<<^^>vv<<^^...^># 5 | #>>v><><<<^<.>v^<v^^<>>v<<>>.<>v><^<^<.^<><^^^vv>>vv>^>v^>>vv>><<>^v.vv>^<^<>v<# 6 | #<^<^>v^^<>>^<.^^>^vv>>v^.>>v^<..>>.v>vv.^>><>v<<>.^<<<<<>>>^<>><^>>v># 7 | #>v>^^^v^>^>v>>>v>v>><<>v..>>vvv<^><<>>^^.>v^<^>>.^v>>vvv<^<.v># 8 | #>v^>>v^<^<<^..<.v^<.<><>v>v^<.><<<<^><<.<><><><.<.^.>^v^><<vvv>>^>v<^v^<><>.^><# 9 | #<><>>vv..<^v^.^^vv.^>^>>v<>vv>^><<<><^vvv>>><>><>>^^<^^v^^>.^>^<.<<^..vv^>^v^v<<.v^# 10 | #>><^<>.v.><^.^^..^^<><^>^..><^^>v^>.^>v^v>>>>vv<<>>vv^>...v># 11 | #>vvv>v>><^>>.<^<<^<^>^.^v>>vv^vv<^v><>>v^<^v.v^<.^^>><.v^^><<^<><^>>vv^.>>v.><<# 12 | #<^<^<>v^v^^^<vv<<<.v<.>^.v.>vvv>^<<^^>.^<<.v^v..v<<<<<^^v^v^<>^>>^.>^<^v<.v..^v^>^># 13 | #<<^^<>^^>v.<<><<^<<^v<<>^^<.^^^^<>^>><^>.v^v>v.<.^<>>^v<<.<>>><.v<^^>>^v>^^<# 14 | #>v><>..v^>v>^<<^>vv>.^v^v>>v^^^>><><<>v^.vv^.<>^^v^>.<^><<^^>>v^.^^^>v.<>^^<^>>^<^<<<^>^<>>^<# 15 | #>vv<.vv><>^>>>>.<>.>v^>>v><<v^>vv><.<>^>^<^^v^>v^<><>^^^>^<<^^.>..>.>^>^>>^>v>v<# 16 | #<>>v^>^<.v^^.^^>^^<^.^.>^<<^>><^vv><.v><^vv^^^^^v><>.^^v^v.>^v.<>>v>vv^.vv^^<>^>.><<# 17 | #>>>>>>>.v<>^<^.>>vv.^v>^>.>^>>^>>v^>v>.v.><<^^<^<^^^>>><^>>v>>v<^^.^># 18 | #>^^^^<>^^v^^><<.<><>^v^v^>>>^^>.vv>vv>^v^<>^vv^vv.^vv>v^v>v>>.<>>><^^<^^>^<# 19 | #<^^^^.<>^>^<^vvv>^.><^^v<>><>vvvv^>^^<<^v><<^<<>^.v<><>>^.>v>vv>><^<# 20 | #>v^^^^><^v.<^.><.^>>^<>v.<<^<>.>vv<<.vv.v>v^.<^<^^>>^.><>vv>vv>^<><^>>><><>><^^<^^>v<^.^>><v>v^<<<<^^.v>v^.>v^<^><^v>^^>^^^^.^>.v^<^^<<><>.^^>v<^.<# 22 | #>v^^<>.vvv>>.<..^^.<<^>>v^<>.>>v>.<^>.vvv^>v>v<>v<^vv<<<<<>.^v^^.>><.# 23 | #<>>.<>^^>>><>>^<><<^^v^><<<>^<^vv<<.v^<<<^>v>v^<^.<<<<^<^v^>vv.<.^>^.>^v^v>^>^<^.v><.# 24 | #<^<.<^^<>^v<^<^>^>^.>.<<^.v<>v^^>>..v^v<.>^>^.^^v>^v^>vv>>^<^>>>^>><<><>v.vv><>.><^.v<^# 25 | #^^>v>.vv><>^v<^<^.<^>>v^^.><^v<.<>v.>^^.v.^v><^^.^v>v.>^^.>^^><>>>>.^^.<# 26 | #<>^<>^><.^^^v<^v^>v.>^<>^^v^.><>^vv<>.^><^^>^v<<.>>^^<.v<>vv>>>v^v>v<^>>^<.<# 27 | #>v^^>>^<^<^>v<^vvvv<<<^<.^^>.>^.v<<.v^vv<>^>.^<>^v<<.<^><^<><<>^># 28 | #<<><..v<^v<>^.v>v^.<>>v>>vv>v^^^>^^><>>^v>v><<>^^v..>^<^vv^<^<# 29 | #><>vv<<<.vv>^^^<^>^><^v>vv>>>^^^<>><<<<<<<^>^v>>.>>v.>v^<<>^v^.# 30 | #.^>>>vv>vv>^^v>>^.^v.>^>..<^<^>.^^v^v^>vv<>v^^><<.^>>^..<^^>><.vv>>v^<<^v>>>>v><>.^v># 31 | #>^^^>v<><>>>^^vv^^^v.v>>^v^v^^>^v.>vv..^>>^v^<.^<^v<^<..^v>^<^^^v.v^>^^<>v><^<.^># 32 | #>v>v>>>vvvv<>>v^><>v>v>.v^vv.^^v.^>>>^^^>v.v<^>>v^^v^^>^^v^vv<v<^.># 33 | #><^<>v^>>^><>vv>v^><^.v^><<<^v>^>.v<<>^vvvv>.vv^v.><<^v^>v>>v^v><<^v<>^^<# 34 | #v>v<^>.<>..>>vv^^v^^.>^<^><^>>>><.^>v^v^<>.<.>.<<^>>vvv<>.v^<>vvvv>v<.>^vv^v># 35 | #>.<^<<^<>^v.^v^>v^<<>^^>^<.vvvv<^<.v^<>v>>><.^.>v^>^><.>^>>.v^.^..^<<>># 36 | #<>>>vv^.v<vv^^v^><v<.>^.^v^<.>><><>^<>>vv>^>.>v><><^vv>>v>^^<<..><.>.>^.># 37 | ####################################################################################################.# 38 | -------------------------------------------------------------------------------- /day24/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | import re 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 | lines = s.splitlines() 17 | 18 | start_x, = (x for x, c in enumerate(lines[0]) if c == '.') 19 | start_x -= 1 20 | start_y = -1 21 | target_x, = (x for x, c in enumerate(lines[-1]) if c == '.') 22 | target_x -= 1 23 | target_y = len(lines) - 3 24 | 25 | lines = [line[1:-1] for line in lines[1:-1]] 26 | new_s = ''.join(lines) 27 | 28 | width = len(lines[0]) 29 | height = len(lines) 30 | 31 | up = tuple( 32 | int(re.sub('[^^]', '0', line).replace('^', '1')[::-1], 2) 33 | for line in lines 34 | ) 35 | down = tuple( 36 | int(re.sub('[^v]', '0', line).replace('v', '1')[::-1], 2) 37 | for line in lines 38 | ) 39 | left = tuple( 40 | int(re.sub('[^<]', '0', new_s[i::width]).replace('<', '1')[::-1], 2) 41 | for i in range(width) 42 | ) 43 | right = tuple( 44 | int(re.sub('[^>]', '0', new_s[i::width]).replace('>', '1')[::-1], 2) 45 | for i in range(width) 46 | ) 47 | 48 | seen = set() 49 | todo = collections.deque([(0, start_x, start_y, up, down, left, right)]) 50 | while todo: 51 | depth, x, y, up, down, left, right = todo.popleft() 52 | 53 | if y != -1 and (up[y] | down[y]) & (1 << x): 54 | continue 55 | elif y != -1 and (left[x] | right[x]) & (1 << y): 56 | continue 57 | elif x == target_x and y == target_y: 58 | return depth + 1 59 | 60 | if (depth, x, y, up, down, left, right) in seen: 61 | continue 62 | else: 63 | seen.add((depth, x, y, up, down, left, right)) 64 | 65 | next_masks = ( 66 | up[1:] + (up[0],), 67 | (down[-1],) + down[:-1], 68 | left[1:] + (left[0],), 69 | (right[-1],) + right[:-1], 70 | ) 71 | 72 | # wait 73 | todo.append((depth + 1, x, y, *next_masks)) 74 | 75 | # move 76 | for (cx, cy) in support.adjacent_4(x, y): 77 | if 0 <= cx < width and 0 <= cy < height: 78 | todo.append((depth + 1, cx, cy, *next_masks)) 79 | 80 | raise NotImplementedError('???') 81 | 82 | 83 | INPUT_S = '''\ 84 | #.###### 85 | #>>.<^<# 86 | #.<..<<# 87 | #>v.><># 88 | #<^v^^># 89 | ######.# 90 | ''' 91 | EXPECTED = 18 92 | 93 | 94 | @ pytest.mark.parametrize( 95 | ('input_s', 'expected'), 96 | ( 97 | (INPUT_S, EXPECTED), 98 | ), 99 | 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 | -------------------------------------------------------------------------------- /day24/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | import re 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 | lines = s.splitlines() 17 | 18 | start_x, = (x for x, c in enumerate(lines[0]) if c == '.') 19 | start_x -= 1 20 | start_y = -1 21 | target_x, = (x for x, c in enumerate(lines[-1]) if c == '.') 22 | target_x -= 1 23 | target_y = len(lines) - 2 24 | 25 | lines = [line[1:-1] for line in lines[1:-1]] 26 | new_s = ''.join(lines) 27 | 28 | width = len(lines[0]) 29 | height = len(lines) 30 | 31 | up = tuple( 32 | int(re.sub('[^^]', '0', line).replace('^', '1')[::-1], 2) 33 | for line in lines 34 | ) 35 | down = tuple( 36 | int(re.sub('[^v]', '0', line).replace('v', '1')[::-1], 2) 37 | for line in lines 38 | ) 39 | left = tuple( 40 | int(re.sub('[^<]', '0', new_s[i::width]).replace('<', '1')[::-1], 2) 41 | for i in range(width) 42 | ) 43 | right = tuple( 44 | int(re.sub('[^>]', '0', new_s[i::width]).replace('>', '1')[::-1], 2) 45 | for i in range(width) 46 | ) 47 | 48 | seen = set() 49 | todo = collections.deque([(0, 0, start_x, start_y, up, down, left, right)]) 50 | while todo: 51 | tup = todo.popleft() 52 | 53 | if tup in seen: 54 | continue 55 | else: 56 | seen.add(tup) 57 | 58 | depth, phase, x, y, up, down, left, right = tup 59 | 60 | if (x, y) != (start_x, start_y) and (x, y) != (target_x, target_y): 61 | if y != -1 and (up[y] | down[y]) & (1 << x): 62 | continue 63 | elif y != -1 and (left[x] | right[x]) & (1 << y): 64 | continue 65 | 66 | if phase == 3 and (x, y) == (target_x, target_y): 67 | return depth 68 | 69 | next_masks = ( 70 | up[1:] + (up[0],), 71 | (down[-1],) + down[:-1], 72 | left[1:] + (left[0],), 73 | (right[-1],) + right[:-1], 74 | ) 75 | 76 | # wait 77 | todo.append((depth + 1, phase, x, y, *next_masks)) 78 | 79 | # move 80 | for (cx, cy) in support.adjacent_4(x, y): 81 | if 0 <= cx < width and 0 <= cy < height: 82 | todo.append((depth + 1, phase, cx, cy, *next_masks)) 83 | elif phase == 0 and (cx, cy) == (target_x, target_y): 84 | todo.append((depth + 1, 1, cx, cy, *next_masks)) 85 | elif phase == 1 and (cx, cy) == (start_x, start_y): 86 | todo.append((depth + 1, 2, cx, cy, *next_masks)) 87 | elif phase == 2 and (cx, cy) == (target_x, target_y): 88 | todo.append((depth + 1, 3, cx, cy, *next_masks)) 89 | 90 | raise NotImplementedError('???') 91 | 92 | 93 | INPUT_S = '''\ 94 | #.###### 95 | #>>.<^<# 96 | #.<..<<# 97 | #>v.><># 98 | #<^v^^># 99 | ######.# 100 | ''' 101 | EXPECTED = 54 102 | 103 | 104 | @ pytest.mark.parametrize( 105 | ('input_s', 'expected'), 106 | ( 107 | (INPUT_S, EXPECTED), 108 | ), 109 | 110 | 111 | ) 112 | def test(input_s: str, expected: int) -> None: 113 | assert compute(input_s) == expected 114 | 115 | 116 | def main() -> int: 117 | parser = argparse.ArgumentParser() 118 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 119 | args = parser.parse_args() 120 | 121 | with open(args.data_file) as f, support.timing(): 122 | print(compute(f.read())) 123 | 124 | return 0 125 | 126 | 127 | if __name__ == '__main__': 128 | raise SystemExit(main()) 129 | -------------------------------------------------------------------------------- /day25/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2022/8efb34cb1b71b90b9510fd40e34dcc3138cf2b07/day25/__init__.py -------------------------------------------------------------------------------- /day25/input.txt: -------------------------------------------------------------------------------- 1 | 1---210=1=02-202 2 | 2--200=0112- 3 | 112=2=200=02= 4 | 11-1 5 | 1=1=112011=2=== 6 | 2-=11==1 7 | 1==210-00=0= 8 | 1-2===-11--0-020 9 | 111==121==-- 10 | 1-=2-11-112 11 | 1=-10-10-11 12 | 1=21101==100=0-01 13 | 1222=0=22=1==1= 14 | 122-==-22 15 | 2=-=02 16 | 101-2-01 17 | 122- 18 | 2=111-22-2=202-001 19 | 1=1= 20 | 10=2 21 | 2-22-0=20-11 22 | 2=-22-10 23 | 1=1 24 | 10-==-=01202=0101 25 | 121212101012=1 26 | 1=2212=2-022021= 27 | 2=00=10--2-020-=20 28 | 1-0222-22- 29 | 1=011 30 | 1120--2-=1=22=1 31 | 102==2----1 32 | 11=1020=0--102==0- 33 | 12--0-11-21--1=2 34 | 220-01010-111 35 | 2=2=1=0=1-001-2 36 | 1--0-= 37 | 1111-01 38 | 1-2=-1=1221-00==1=1 39 | 1-=0-00----022121=0= 40 | 2- 41 | 2-111200=2-12-0 42 | 1002-002 43 | 2020 44 | 110=12==--1=12=2== 45 | 102-11122--0 46 | 2=-=2-=1===11 47 | 110=20 48 | 2--12 49 | 1=-2-1=01= 50 | 11122=211 51 | 1-0002 52 | 1==1=21011= 53 | 1=0-00122 54 | 1---0112-=222 55 | 2-222120=- 56 | 1000 57 | 2-010=-0- 58 | 20=12000202221 59 | 1120=2=21-2 60 | 12==-1 61 | 2=2==11=10=0 62 | 1-0-001=-2 63 | 20==2-02==--=101 64 | 111001--20102 65 | 121=21 66 | 1=1-==2-=121 67 | 10100==0= 68 | 2= 69 | 1=-0=20---=-02=1== 70 | 1-0=12=0=2=12 71 | 21---2---2 72 | 12=-=--=0 73 | 2=002--=10 74 | 1=1=-21==010=1-=2- 75 | 1=-10--= 76 | 20022-1---1-211 77 | 1= 78 | 1-102==2=2000 79 | 22-0222- 80 | 1120=1=202=0110-000 81 | 220102==0- 82 | 10001=====1--1-=02 83 | 2012-0=1=1=21210-2 84 | 1-=1-0-0=- 85 | 21212-==01-1= 86 | 22222= 87 | 1--21==1-1-101-022 88 | 111 89 | 1-000212 90 | 1-1=20= 91 | 1-21 92 | 212====-1 93 | 1== 94 | 2-===2-10=110= 95 | 1--22 96 | 1=0-10010=12==100= 97 | 201- 98 | 1=1000220=12 99 | 21--=-2 100 | 10-0-200=1-0122 101 | 2100222==20-=1-10 102 | 100=11111121=1 103 | 2110 104 | 21201-1 105 | 120=2-1--120001 106 | 1==1=01100112 107 | 11101 108 | 1-=1=0 109 | 2-=2 110 | 10 111 | 122=020221220-0 112 | 2==21 113 | 10=1011=1011 114 | 11=02 115 | 2210=---=0- 116 | 212 117 | 2010-0202=22 118 | 112-121111=2 119 | 101112-0-1=00 120 | 10==-=-20=-112===0 121 | 1=0-10-0 122 | 1=11-0=--110020= 123 | 20212--=-=01-11= 124 | 1==0-=0 125 | 10-21010022110- 126 | 10--12=000 127 | 10=0 128 | -------------------------------------------------------------------------------- /day25/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import math 5 | import os.path 6 | import sys 7 | 8 | import pytest 9 | from z3 import Int 10 | from z3 import Optimize 11 | from z3 import sat 12 | 13 | import support 14 | 15 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 16 | 17 | REV = { 18 | 2: '2', 19 | 1: '1', 20 | 0: '0', 21 | -1: '-', 22 | -2: '=', 23 | } 24 | 25 | 26 | def encode_z3(n: int) -> str: 27 | near = int(math.log(n, 5)) 28 | for nterms in (near - 1, near, near + 1): 29 | o = Optimize() 30 | ints = [] 31 | for i in range(nterms): 32 | this_int = Int(f'i_{i}') 33 | o.add(this_int <= 2) 34 | o.add(this_int >= -2) 35 | ints.append(this_int * (5 ** (nterms - i - 1))) 36 | o.add(sum(ints) == n) 37 | 38 | if o.check() == sat: 39 | m = o.model() 40 | return ''.join( 41 | REV[m[Int(f'i_{i}')].as_long()] 42 | for i in range(nterms) 43 | ) 44 | 45 | raise AssertionError('unreachable') 46 | 47 | 48 | def encode(n: int) -> str: 49 | ret = '' 50 | while n: 51 | rem = n % 5 52 | if rem <= 2: 53 | ret += str(rem) 54 | else: 55 | ret += {3: '=', 4: '-'}[rem] 56 | 57 | n //= 5 58 | n += rem // 3 59 | 60 | return ret[::-1] 61 | 62 | 63 | def compute_value(s: str) -> int: 64 | ret = 0 65 | for line in s.splitlines(): 66 | n = 0 67 | for i, c in enumerate(reversed(line)): 68 | if c.isdigit(): 69 | n += int(c) * (5 ** i) 70 | elif c == '-': 71 | n -= 1 * (5 ** i) 72 | elif c == '=': 73 | n -= 2 * (5 ** i) 74 | ret += n 75 | return ret 76 | 77 | 78 | def compute_z3(s: str) -> str: 79 | return encode_z3(compute_value(s)) 80 | 81 | 82 | def compute(s: str) -> str: 83 | return encode(compute_value(s)) 84 | 85 | 86 | INPUT_S = '''\ 87 | 1=-0-2 88 | 12111 89 | 2=0= 90 | 21 91 | 2=01 92 | 111 93 | 20012 94 | 112 95 | 1=-1= 96 | 1-12 97 | 12 98 | 1= 99 | 122 100 | ''' 101 | EXPECTED = '2=-1=0' 102 | 103 | 104 | @pytest.mark.parametrize( 105 | ('input_s', 'expected'), 106 | ( 107 | (INPUT_S, EXPECTED), 108 | ), 109 | ) 110 | def test(input_s: str, expected: int) -> None: 111 | assert compute(input_s) == expected 112 | 113 | 114 | def main() -> int: 115 | parser = argparse.ArgumentParser() 116 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 117 | args = parser.parse_args() 118 | 119 | with open(args.data_file) as f, support.timing('z3'): 120 | print(compute_z3(f.read()), file=sys.stderr) 121 | 122 | with open(args.data_file) as f, support.timing(): 123 | print(compute(f.read())) 124 | 125 | return 0 126 | 127 | 128 | if __name__ == '__main__': 129 | raise SystemExit(main()) 130 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e support 2 | pytest 3 | z3-solver 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | mypy_path = support 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/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/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from setuptools import setup 4 | setup() 5 | -------------------------------------------------------------------------------- /support/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 | --------------------------------------------------------------------------------