├── .activate.sh ├── .deactivate.sh ├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── day00 ├── __init__.py ├── input.txt └── part1.py ├── day01 ├── __init__.py ├── input.txt ├── part1.py ├── 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 └── part2.py ├── day12 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day13 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day14 ├── __init__.py ├── input.txt ├── part1.py └── part2.py ├── day15 ├── __init__.py ├── 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 ├── 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 | /.mypy_cache 5 | /venv 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.0.1 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - repo: https://github.com/PyCQA/flake8 9 | rev: 4.0.1 10 | hooks: 11 | - id: flake8 12 | - repo: https://github.com/pre-commit/mirrors-autopep8 13 | rev: v1.5.7 14 | hooks: 15 | - id: autopep8 16 | - repo: https://github.com/asottile/reorder_python_imports 17 | rev: v2.6.0 18 | hooks: 19 | - id: reorder-python-imports 20 | args: [ 21 | --py38-plus, 22 | --add-import, 'from __future__ import annotations', 23 | '--application-directories=.:support', 24 | ] 25 | - repo: https://github.com/asottile/pyupgrade 26 | rev: v2.29.1 27 | hooks: 28 | - id: pyupgrade 29 | args: [--py38-plus] 30 | - repo: https://github.com/pre-commit/mirrors-mypy 31 | rev: v0.920 32 | hooks: 33 | - id: mypy 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/anthonywritescode/aoc2021/main.svg)](https://results.pre-commit.ci/latest/github/anthonywritescode/aoc2021/main) 2 | 3 | advent of code 2021 4 | =================== 5 | 6 | https://adventofcode.com/2021 7 | 8 | ### stream / youtube 9 | 10 | - [Streamed daily on twitch](https://twitch.tv/anthonywritescode) 11 | - [Streams uploaded to youtube afterwards](https://www.youtube.com/channel/UChPxcypesw8L-iqltstSI4Q) 12 | - [Uploaded to youtube afterwards](https://www.youtube.com/anthonywritescode) 13 | 14 | ### about 15 | 16 | for 2021, I'm planning to implement in python and then some meme language... 17 | maybe. 18 | 19 | ### timing 20 | 21 | - comparing to these numbers isn't necessarily useful 22 | - normalize your timing to day 1 part 1 and compare 23 | - alternate implementations are listed in parens 24 | - these timings are very non-scientific (sample size 1) 25 | 26 | ```console 27 | $ find -maxdepth 1 -type d -name 'day*' -not -name day00 | sort | xargs --replace bash -xc 'python {}/part1.py {}/input.txt; python {}/part2.py {}/input.txt' 28 | + python day01/part1.py day01/input.txt 29 | 1195 30 | > 1208 μs 31 | + python day01/part2.py day01/input.txt 32 | 1235 33 | > 1196 μs 34 | + python day02/part1.py day02/input.txt 35 | 1947824 36 | > 675 μs 37 | + python day02/part2.py day02/input.txt 38 | 1813062561 39 | > 736 μs 40 | + python day03/part1.py day03/input.txt 41 | 3277364 42 | > 2797 μs 43 | + python day03/part2.py day03/input.txt 44 | 5736383 45 | > 7091 μs (haxy dicts) 46 | 5736383 47 | > 2184 μs 48 | + python day04/part1.py day04/input.txt 49 | 60368 50 | > 17870 μs 51 | + python day04/part2.py day04/input.txt 52 | 17435 53 | > 44811 μs 54 | + python day05/part1.py day05/input.txt 55 | 5197 56 | > 142 ms 57 | + python day05/part2.py day05/input.txt 58 | 18605 59 | > 261 ms 60 | + python day06/part1.py day06/input.txt 61 | 383160 62 | > 922 μs 63 | + python day06/part2.py day06/input.txt 64 | 1721148811504 65 | > 2567 μs 66 | + python day07/part1.py day07/input.txt 67 | 335330 68 | > 711 μs 69 | + python day07/part2.py day07/input.txt 70 | 92439766 71 | > 3680 μs 72 | + python day08/part1.py day08/input.txt 73 | 519 74 | > 472 μs 75 | + python day08/part2.py day08/input.txt 76 | 1027483 77 | > 10599 μs 78 | + python day09/part1.py day09/input.txt 79 | 506 80 | > 12520 μs 81 | + python day09/part2.py day09/input.txt 82 | 931200 83 | > 46183 μs 84 | + python day10/part1.py day10/input.txt 85 | 316851 86 | > 2531 μs 87 | + python day10/part2.py day10/input.txt 88 | 2182912364 89 | > 2763 μs 90 | + python day11/part1.py day11/input.txt 91 | 1637 92 | > 19016 μs 93 | + python day11/part2.py day11/input.txt 94 | 242 95 | > 43382 μs 96 | + python day12/part1.py day12/input.txt 97 | 4573 98 | > 27702 μs 99 | + python day12/part2.py day12/input.txt 100 | 117509 101 | > 810 ms 102 | + python day13/part1.py day13/input.txt 103 | 807 104 | > 1397 μs 105 | + python day13/part2.py day13/input.txt 106 | # ## # # #### ## # # #### ## 107 | # # # # # # # # # # # # 108 | # # #### ### # # # ### # 109 | # # ## # # # # ## # # # # 110 | # # # # # # # # # # # # # 111 | #### ### # # #### ### ## #### ## 112 | > 2839 μs 113 | + python day14/part1.py day14/input.txt 114 | 3095 115 | > 12231 μs 116 | + python day14/part2.py day14/input.txt 117 | 3152788426516 118 | > 6875 μs 119 | + python day15/part1.py day15/input.txt 120 | 621 121 | > 97837 μs 122 | + python day15/part2.py day15/input.txt 123 | 2904 124 | > 3439 ms 125 | + python day16/part1.py day16/input.txt 126 | 906 127 | > 2082 μs 128 | + python day16/part2.py day16/input.txt 129 | 819324480368 130 | > 2146 μs 131 | + python day17/part1.py day17/input.txt 132 | 8646 133 | > 31 μs 134 | + python day17/part2.py day17/input.txt 135 | 5945 136 | > 432 ms 137 | + python day18/part1.py day18/input.txt 138 | 4017 139 | > 253 ms 140 | + python day18/part2.py day18/input.txt 141 | 4583 142 | > 4660 ms 143 | + python day19/part1.py day19/input.txt 144 | 381 145 | > 1419 ms 146 | + python day19/part2.py day19/input.txt 147 | 12201 148 | > 1424 ms 149 | + python day20/part1.py day20/input.txt 150 | 5437 151 | > 155 ms 152 | + python day20/part2.py day20/input.txt 153 | 19340 154 | > 8706 ms 155 | + python day21/part1.py day21/input.txt 156 | 989352 157 | > 912 μs 158 | + python day21/part2.py day21/input.txt 159 | 430229563871565 160 | > 104 ms 161 | + python day22/part1.py day22/input.txt 162 | 583641 163 | > 1380 ms 164 | + python day22/part2.py day22/input.txt 165 | 1182153534186233 166 | > 1679 ms 167 | + python day23/part1.py day23/input.txt 168 | 14348 169 | > 192 ms 170 | + python day23/part2.py day23/input.txt 171 | 40954 172 | > 6154 ms 173 | + python day24/part1.py day24/input.txt 174 | 91897399498995 175 | > 1784 ms 176 | + python day24/part2.py day24/input.txt 177 | 51121176121391 178 | > 1376 ms 179 | + python day25/part1.py day25/input.txt 180 | 337 181 | > 10665 ms 182 | ``` 183 | -------------------------------------------------------------------------------- /day00/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day00/__init__.py -------------------------------------------------------------------------------- /day00/input.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/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/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/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 | numbers = support.parse_numbers_split(s) 15 | return sum(numbers[i] > numbers[i - 1] for i in range(1, len(numbers))) 16 | 17 | 18 | INPUT_S = '''\ 19 | 199 20 | 200 21 | 208 22 | 210 23 | 200 24 | 207 25 | 240 26 | 269 27 | 260 28 | 263 29 | ''' 30 | 31 | 32 | @pytest.mark.parametrize( 33 | ('input_s', 'expected'), 34 | ( 35 | (INPUT_S, 7), 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 | -------------------------------------------------------------------------------- /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 numbers(n INT); 6 | INSERT INTO numbers 7 | SELECT t.value 8 | FROM json_each('[' || REPLACE((SELECT s FROM input), char(10), ',') || ']') t; 9 | 10 | SELECT SUM(numbers1.n > numbers2.n) 11 | FROM numbers AS numbers1 12 | INNER JOIN numbers as numbers2 13 | WHERE numbers1.ROWID = numbers2.ROWID + 1; 14 | -------------------------------------------------------------------------------- /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 | numbers = support.parse_numbers_split(s) 15 | return sum(numbers[i] > numbers[i - 3] for i in range(3, len(numbers))) 16 | 17 | 18 | INPUT_S = '''\ 19 | 199 20 | 200 21 | 208 22 | 210 23 | 200 24 | 207 25 | 240 26 | 269 27 | 260 28 | 263 29 | ''' 30 | 31 | 32 | @pytest.mark.parametrize( 33 | ('input_s', 'expected'), 34 | ( 35 | (INPUT_S, 5), 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 | -------------------------------------------------------------------------------- /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 numbers(n INT); 6 | INSERT INTO numbers 7 | SELECT t.value 8 | FROM json_each('[' || REPLACE((SELECT s FROM input), char(10), ',') || ']') t; 9 | 10 | SELECT SUM(numbers1.n > numbers2.n) 11 | FROM numbers AS numbers1 12 | INNER JOIN numbers as numbers2 13 | WHERE numbers1.ROWID = numbers2.ROWID + 3; 14 | -------------------------------------------------------------------------------- /day02/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day02/__init__.py -------------------------------------------------------------------------------- /day02/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | lines = s.splitlines() 15 | 16 | position = 0 17 | depth = 0 18 | for line in lines: 19 | direction, n_s = line.split() 20 | n = int(n_s) 21 | 22 | if direction == 'up': 23 | position -= n 24 | elif direction == 'down': 25 | position += n 26 | elif direction == 'forward': 27 | depth += n 28 | 29 | return position * depth 30 | 31 | 32 | INPUT_S = '''\ 33 | forward 5 34 | down 5 35 | forward 8 36 | up 3 37 | down 8 38 | forward 2 39 | ''' 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ('input_s', 'expected'), 44 | ( 45 | (INPUT_S, 150), 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/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 answer (n INT); 6 | WITH RECURSIVE 7 | nn (n, position, depth, rest) 8 | AS ( 9 | SELECT 0, 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | nn.n + 1, 13 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, ' ')) 14 | WHEN 'forward' THEN 15 | nn.position + 16 | SUBSTR( 17 | nn.rest, 18 | INSTR(nn.rest, ' '), 19 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 20 | ) 21 | ELSE nn.position 22 | END, 23 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, ' ')) 24 | WHEN 'up' THEN 25 | nn.depth - 26 | SUBSTR( 27 | nn.rest, 28 | INSTR(nn.rest, ' '), 29 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 30 | ) 31 | WHEN 'down' THEN 32 | nn.depth + 33 | SUBSTR( 34 | nn.rest, 35 | INSTR(nn.rest, ' '), 36 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 37 | ) 38 | ELSE nn.depth 39 | END, 40 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 41 | FROM nn 42 | WHERE nn.rest != '' 43 | ) 44 | INSERT INTO answer 45 | SELECT nn.position * nn.depth FROM nn ORDER BY nn.n DESC LIMIT 1; 46 | 47 | SELECT * FROM answer; 48 | -------------------------------------------------------------------------------- /day02/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | lines = s.splitlines() 15 | 16 | aim = 0 17 | position = 0 18 | depth = 0 19 | for line in lines: 20 | direction, n_s = line.split() 21 | n = int(n_s) 22 | 23 | if direction == 'up': 24 | aim -= n 25 | elif direction == 'down': 26 | aim += n 27 | elif direction == 'forward': 28 | position += n 29 | depth += aim * n 30 | 31 | return position * depth 32 | 33 | 34 | INPUT_S = '''\ 35 | forward 5 36 | down 5 37 | forward 8 38 | up 3 39 | down 8 40 | forward 2 41 | ''' 42 | 43 | 44 | @pytest.mark.parametrize( 45 | ('input_s', 'expected'), 46 | ( 47 | (INPUT_S, 900), 48 | ), 49 | ) 50 | def test(input_s: str, expected: int) -> None: 51 | assert compute(input_s) == expected 52 | 53 | 54 | def main() -> int: 55 | parser = argparse.ArgumentParser() 56 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 57 | args = parser.parse_args() 58 | 59 | with open(args.data_file) as f, support.timing(): 60 | print(compute(f.read())) 61 | 62 | return 0 63 | 64 | 65 | if __name__ == '__main__': 66 | raise SystemExit(main()) 67 | -------------------------------------------------------------------------------- /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 answer (n INT); 6 | WITH RECURSIVE 7 | nn (n, aim, position, depth, rest) 8 | AS ( 9 | SELECT 0, 0, 0, 0, (SELECT s || char(10) FROM input) 10 | UNION ALL 11 | SELECT 12 | nn.n + 1, 13 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, ' ')) 14 | WHEN 'up' THEN 15 | nn.aim - 16 | SUBSTR( 17 | nn.rest, 18 | INSTR(nn.rest, ' '), 19 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 20 | ) 21 | WHEN 'down' THEN 22 | nn.aim + 23 | SUBSTR( 24 | nn.rest, 25 | INSTR(nn.rest, ' '), 26 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 27 | ) 28 | ELSE nn.aim 29 | END, 30 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, ' ')) 31 | WHEN 'forward' THEN 32 | nn.position + 33 | SUBSTR( 34 | nn.rest, 35 | INSTR(nn.rest, ' '), 36 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 37 | ) 38 | ELSE nn.position 39 | END, 40 | CASE SUBSTR(nn.rest, 0, INSTR(nn.rest, ' ')) 41 | WHEN 'forward' THEN 42 | nn.depth + 43 | nn.aim * 44 | SUBSTR( 45 | nn.rest, 46 | INSTR(nn.rest, ' '), 47 | INSTR(nn.rest, char(10)) - INSTR(nn.rest, ' ') 48 | ) 49 | ELSE nn.depth 50 | END, 51 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 52 | FROM nn 53 | WHERE nn.rest != '' 54 | ) 55 | INSERT INTO answer 56 | SELECT nn.position * nn.depth FROM nn ORDER BY nn.n DESC LIMIT 1; 57 | 58 | SELECT * FROM answer; 59 | -------------------------------------------------------------------------------- /day03/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/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 | lines = s.splitlines() 15 | 16 | counts = [0] * len(lines[0]) 17 | 18 | for line in lines: 19 | for i, c in enumerate(line): 20 | if c == '1': 21 | counts[i] += 1 22 | 23 | gamma = 0 24 | eps = 0 25 | for i in range(len(lines[0])): 26 | gamma <<= 1 27 | eps <<= 1 28 | if counts[i] > len(lines) // 2: 29 | gamma += 1 30 | else: 31 | eps += 1 32 | 33 | return gamma * eps 34 | 35 | 36 | INPUT_S = '''\ 37 | 00100 38 | 11110 39 | 10110 40 | 10111 41 | 10101 42 | 01111 43 | 00111 44 | 11100 45 | 10000 46 | 11001 47 | 00010 48 | 01010 49 | ''' 50 | 51 | 52 | @pytest.mark.parametrize( 53 | ('input_s', 'expected'), 54 | ( 55 | (INPUT_S, 198), 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 | -------------------------------------------------------------------------------- /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 vars(line_len); 6 | INSERT INTO vars SELECT INSTR(s, char(10)) - 1 FROM input; 7 | 8 | -- no power function in this version sadface 9 | CREATE TABLE vars2 (maxmult); 10 | WITH RECURSIVE 11 | nn (mult, n) 12 | AS ( 13 | SELECT 1, 0 14 | UNION ALL 15 | SELECT nn.mult * 2, nn.n + 1 16 | FROM nn 17 | WHERE nn.n < (SELECT line_len FROM vars) 18 | ) 19 | INSERT INTO vars2 SELECT MAX(nn.mult) FROM nn; 20 | 21 | CREATE TABLE positions (mult, c); 22 | WITH RECURSIVE 23 | nn (mult, c, rest) 24 | AS ( 25 | SELECT 26 | (SELECT maxmult FROM vars2), 27 | char(10), 28 | (SELECT s FROM input) 29 | UNION ALL 30 | SELECT 31 | CASE SUBSTR(nn.rest, 1, 1) 32 | WHEN char(10) THEN (SELECT maxmult FROM vars2) 33 | ELSE nn.mult / 2 34 | END, 35 | SUBSTR(nn.rest, 1, 1), 36 | SUBSTR(nn.rest, 2) 37 | FROM nn 38 | WHERE nn.rest != '' 39 | ) 40 | INSERT INTO positions 41 | SELECT nn.mult, nn.c FROM nn WHERE nn.c != char(10); 42 | 43 | SELECT ( 44 | SELECT SUM(c * mult) 45 | FROM ( 46 | SELECT c, mult, count(1) 47 | FROM positions 48 | GROUP BY mult, c 49 | ORDER BY COUNT(1) DESC LIMIT (SELECT line_len FROM vars) 50 | ) 51 | ) * ( 52 | SELECT SUM(c * mult) 53 | FROM ( 54 | SELECT c, mult, count(1) 55 | FROM positions 56 | GROUP BY mult, c 57 | ORDER BY COUNT(1) ASC LIMIT (SELECT line_len FROM vars) 58 | ) 59 | ); 60 | -------------------------------------------------------------------------------- /day03/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import Any 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 | lines = s.splitlines() 16 | 17 | columns = list(zip(*lines)) 18 | 19 | path: list[str] = [] 20 | included = set(range(len(lines))) 21 | while len(included) != 1: 22 | column = columns[len(path)] 23 | ones = sum(column[pos] == '1' for pos in included) 24 | if ones >= len(included) / 2: 25 | path.append('1') 26 | else: 27 | path.append('0') 28 | 29 | included = {pos for pos in included if column[pos] == path[-1]} 30 | 31 | best = lines[next(iter(included))] 32 | 33 | path = [] 34 | included = set(range(len(lines))) 35 | while len(included) != 1: 36 | column = columns[len(path)] 37 | ones = sum(column[pos] == '1' for pos in included) 38 | if ones < len(included) / 2: 39 | path.append('1') 40 | else: 41 | path.append('0') 42 | 43 | included = {pos for pos in included if column[pos] == path[-1]} 44 | 45 | worst = lines[next(iter(included))] 46 | 47 | return int(best, 2) * int(worst, 2) 48 | 49 | 50 | def compute_haxy_dicts(s: str) -> int: 51 | lines = s.splitlines() 52 | 53 | root: dict[str, Any] = {'count': 0} 54 | for line in lines: 55 | current = root 56 | for c in line: 57 | current['count'] += 1 58 | current.setdefault(c, {'count': 0}) 59 | current = current[c] 60 | current['count'] += 1 61 | 62 | path = [] 63 | current = root 64 | while True: 65 | if ( 66 | current.get('1', {}).get('count', 0) >= 67 | current.get('0', {}).get('count', 0) 68 | ): 69 | current = current['1'] 70 | path.append('1') 71 | else: 72 | current = current['0'] 73 | path.append('0') 74 | 75 | if current['count'] == 1: 76 | prefix = ''.join(path) 77 | break 78 | 79 | path = [] 80 | current = root 81 | while True: 82 | if ( 83 | current.get('1', {}).get('count', 0) < 84 | current.get('0', {}).get('count', 0) 85 | ): 86 | current = current['1'] 87 | path.append('1') 88 | else: 89 | current = current['0'] 90 | path.append('0') 91 | 92 | if current['count'] == 1: 93 | prefix2 = ''.join(path) 94 | break 95 | 96 | best, = (line for line in lines if line.startswith(prefix)) 97 | worst, = (line for line in lines if line.startswith(prefix2)) 98 | 99 | return int(best, 2) * int(worst, 2) 100 | 101 | 102 | INPUT_S = '''\ 103 | 00100 104 | 11110 105 | 10110 106 | 10111 107 | 10101 108 | 01111 109 | 00111 110 | 11100 111 | 10000 112 | 11001 113 | 00010 114 | 01010 115 | ''' 116 | 117 | 118 | @pytest.mark.parametrize( 119 | ('input_s', 'expected'), 120 | ( 121 | (INPUT_S, 230), 122 | ), 123 | ) 124 | def test(input_s: str, expected: int) -> None: 125 | assert compute(input_s) == expected 126 | 127 | 128 | def main() -> int: 129 | parser = argparse.ArgumentParser() 130 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 131 | args = parser.parse_args() 132 | 133 | with open(args.data_file) as f, support.timing('haxy dicts'): 134 | print(compute_haxy_dicts(f.read())) 135 | 136 | with open(args.data_file) as f, support.timing(): 137 | print(compute(f.read())) 138 | 139 | return 0 140 | 141 | 142 | if __name__ == '__main__': 143 | raise SystemExit(main()) 144 | -------------------------------------------------------------------------------- /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 vars (line_len); 6 | INSERT INTO vars SELECT INSTR(s, char(10)) - 1 FROM input; 7 | 8 | CREATE TABLE lines (s); 9 | WITH RECURSIVE 10 | nn (s, rest) 11 | AS ( 12 | SELECT '', (SELECT s || char(10) FROM input) 13 | UNION ALL 14 | SELECT 15 | SUBSTR(nn.rest, 1, INSTR(nn.rest, char(10)) - 1), 16 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 17 | FROM nn 18 | WHERE nn.rest != '' 19 | ) 20 | INSERT INTO lines 21 | SELECT nn.s FROM nn WHERE nn.s != ''; 22 | 23 | WITH RECURSIVE 24 | nn (i, o2_s, o2_num, co2_s, co2_num) 25 | AS ( 26 | SELECT -1, '', 0, '', 0 27 | UNION ALL 28 | SELECT 29 | nn.i + 1, 30 | CASE ( 31 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.o2_s || '1%' 32 | ) >= ( 33 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.o2_s || '0%' 34 | ) 35 | WHEN 1 THEN nn.o2_s || '1' 36 | ELSE nn.o2_s || '0' 37 | END, 38 | CASE ( 39 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.o2_s || '1%' 40 | ) >= ( 41 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.o2_s || '0%' 42 | ) 43 | WHEN 1 THEN nn.o2_num * 2 + 1 44 | ELSE nn.o2_num * 2 45 | END, 46 | CASE ( 47 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '1%') >= 1 AND 48 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '0%') = 0 49 | ) OR ( 50 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '1%') >= 1 AND 51 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '0%') >= 1 AND 52 | ( 53 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.co2_s || '0%' 54 | ) > ( 55 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.co2_s || '1%' 56 | ) 57 | ) 58 | WHEN 1 THEN nn.co2_s || '1' 59 | ELSE nn.co2_s || '0' 60 | END, 61 | CASE ( 62 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '1%') >= 1 AND 63 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '0%') = 0 64 | ) OR ( 65 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '1%') >= 1 AND 66 | (SELECT COUNT(1) FROM lines where s LIKE nn.co2_s || '0%') >= 1 AND 67 | ( 68 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.co2_s || '0%' 69 | ) > ( 70 | SELECT COUNT(1) FROM lines WHERE s LIKE nn.co2_s || '1%' 71 | ) 72 | ) 73 | WHEN 1 THEN nn.co2_num * 2 + 1 74 | ELSE nn.co2_num * 2 75 | END 76 | FROM nn 77 | WHERE nn.i < (SELECT line_len -1 FROM vars) 78 | ) 79 | SELECT nn.o2_num * nn.co2_num FROM nn ORDER BY nn.i DESC LIMIT 1; 80 | -------------------------------------------------------------------------------- /day04/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day04/__init__.py -------------------------------------------------------------------------------- /day04/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | class Board(NamedTuple): 15 | left: set[int] 16 | ints: list[int] 17 | 18 | @property 19 | def summed(self) -> int: 20 | return sum(self.left) 21 | 22 | @property 23 | def solved(self) -> bool: 24 | for i in range(5): 25 | for j in range(5): 26 | if self.ints[i * 5 + j] in self.left: 27 | break 28 | else: 29 | return True 30 | 31 | for j in range(5): 32 | if self.ints[i + j * 5] in self.left: 33 | break 34 | else: 35 | return True 36 | else: 37 | return False 38 | 39 | @classmethod 40 | def parse(cls, board: str) -> Board: 41 | ints = support.parse_numbers_split(board) 42 | return cls(set(ints), ints) 43 | 44 | 45 | def compute(s: str) -> int: 46 | first, *board_strings = s.split('\n\n') 47 | 48 | boards = [Board.parse(s) for s in board_strings] 49 | 50 | for number in support.parse_numbers_comma(first): 51 | for board in boards: 52 | board.left.discard(number) 53 | 54 | for board in boards: 55 | if board.solved: 56 | return board.summed * number 57 | 58 | raise AssertionError('unreachable') 59 | 60 | 61 | INPUT_S = '''\ 62 | 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 63 | 64 | 22 13 17 11 0 65 | 8 2 23 4 24 66 | 21 9 14 16 7 67 | 6 10 3 18 5 68 | 1 12 20 15 19 69 | 70 | 3 15 0 2 22 71 | 9 18 13 17 5 72 | 19 8 7 25 23 73 | 20 11 10 24 4 74 | 14 21 16 12 6 75 | 76 | 14 21 17 24 4 77 | 10 16 15 9 19 78 | 18 8 23 26 20 79 | 22 11 13 6 5 80 | 2 0 12 3 7 81 | ''' 82 | 83 | 84 | @pytest.mark.parametrize( 85 | ('input_s', 'expected'), 86 | ( 87 | (INPUT_S, 4512), 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 | -------------------------------------------------------------------------------- /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 bingo_rows (id, c, c1, c2, c3, c4, c5, c1d, c2d, c3d, c4d, c5d); 6 | WITH RECURSIVE 7 | nn (id, r, c1, c2, c3, c4, c5, rest) 8 | AS ( 9 | SELECT 10 | -1, 4, 0, 0, 0, 0, 0, 11 | ( 12 | SELECT 13 | SUBSTR(s, INSTR(s, char(10)) + 2) || char(10) || char(10) 14 | FROM input 15 | ) 16 | UNION ALL 17 | SELECT 18 | CASE nn.r = 4 WHEN 1 THEN nn.id + 1 ELSE nn.id END, 19 | CASE nn.r = 4 WHEN 1 THEN 0 ELSE nn.r + 1 END, 20 | SUBSTR(nn.rest, 1, 2) + 0, 21 | SUBSTR(nn.rest, 4, 2) + 0, 22 | SUBSTR(nn.rest, 7, 2) + 0, 23 | SUBSTR(nn.rest, 10, 2) + 0, 24 | SUBSTR(nn.rest, 13, 2) + 0, 25 | CASE nn.r = 3 26 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 2) 27 | ELSE SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 28 | END 29 | FROM nn WHERE nn.rest != '' 30 | ) 31 | INSERT INTO bingo_rows 32 | SELECT nn.id, 1, nn.c1, nn.c2, nn.c3, nn.c4, nn.c5, 0, 0, 0, 0, 0 33 | FROM nn WHERE nn.id >= 0; 34 | 35 | WITH RECURSIVE 36 | nn (id, r, c1, c2, c3, c4, c5, rest) 37 | AS ( 38 | SELECT 39 | -1, 4, 0, 0, 0, 0, 0, 40 | ( 41 | SELECT 42 | SUBSTR(s, INSTR(s, char(10)) + 2) || char(10) || char(10) 43 | FROM input 44 | ) 45 | UNION ALL 46 | SELECT 47 | CASE nn.r = 4 WHEN 1 THEN nn.id + 1 ELSE nn.id END, 48 | CASE nn.r = 4 WHEN 1 THEN 0 ELSE nn.r + 1 END, 49 | SUBSTR(nn.rest, 1 + nn.r * 3, 2) + 0, 50 | SUBSTR(nn.rest, 16 + nn.r * 3, 2) + 0, 51 | SUBSTR(nn.rest, 31 + nn.r * 3, 2) + 0, 52 | SUBSTR(nn.rest, 46 + nn.r * 3, 2) + 0, 53 | SUBSTR(nn.rest, 61 + nn.r * 3, 2) + 0, 54 | CASE nn.r = 3 55 | WHEN 1 THEN 56 | SUBSTR(nn.rest, INSTR(nn.rest, char(10) || char(10)) + 2) 57 | ELSE nn.rest 58 | END 59 | FROM nn WHERE nn.rest != '' 60 | ) 61 | INSERT INTO bingo_rows 62 | SELECT nn.id, 0, nn.c1, nn.c2, nn.c3, nn.c4, nn.c5, 0, 0, 0, 0, 0 63 | FROM nn WHERE nn.id >= 0; 64 | 65 | CREATE TABLE answers (n INT); 66 | CREATE TABLE nums (n INT); 67 | CREATE TRIGGER ttrig AFTER INSERT ON nums FOR EACH ROW BEGIN 68 | UPDATE bingo_rows SET c1d = 1 WHERE c1 = NEW.n; 69 | UPDATE bingo_rows SET c2d = 1 WHERE c2 = NEW.n; 70 | UPDATE bingo_rows SET c3d = 1 WHERE c3 = NEW.n; 71 | UPDATE bingo_rows SET c4d = 1 WHERE c4 = NEW.n; 72 | UPDATE bingo_rows SET c5d = 1 WHERE c5 = NEW.n; 73 | 74 | INSERT INTO answers 75 | SELECT NEW.n * ( 76 | SELECT 77 | SUM( 78 | c1 * (1 - c1d) + 79 | c2 * (1 - c2d) + 80 | c3 * (1 - c3d) + 81 | c4 * (1 - c4d) + 82 | c5 * (1 - c5d) 83 | ) 84 | FROM bingo_rows AS b2 85 | WHERE b2.id = bingo_rows.id AND b2.c = 1 86 | ) 87 | FROM bingo_rows 88 | WHERE c1d and c2d and c3d and c4d and c5d; 89 | 90 | DELETE FROM bingo_rows WHERE id IN ( 91 | SELECT id FROM bingo_rows 92 | WHERE c1d and c2d and c3d and c4d and c5d 93 | ); 94 | END; 95 | 96 | WITH RECURSIVE 97 | nn (n, rest) 98 | AS ( 99 | SELECT -1, (SELECT SUBSTR(s, 1, INSTR(s, char(10)) - 1) || ',' FROM input) 100 | UNION ALL 101 | SELECT 102 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1), 103 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 104 | FROM nn 105 | WHERE nn.rest != '' 106 | ) 107 | INSERT INTO nums 108 | SELECT nn.n FROM nn WHERE nn.n != -1; 109 | 110 | SELECT n FROM answers ORDER BY ROWID ASC LIMIT 1; 111 | -------------------------------------------------------------------------------- /day04/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | class Board(NamedTuple): 15 | left: set[int] 16 | ints: list[int] 17 | 18 | @property 19 | def summed(self) -> int: 20 | return sum(self.left) 21 | 22 | @property 23 | def solved(self) -> bool: 24 | for i in range(5): 25 | for j in range(5): 26 | if self.ints[i * 5 + j] in self.left: 27 | break 28 | else: 29 | return True 30 | 31 | for j in range(5): 32 | if self.ints[i + j * 5] in self.left: 33 | break 34 | else: 35 | return True 36 | else: 37 | return False 38 | 39 | @classmethod 40 | def parse(cls, board: str) -> Board: 41 | ints = support.parse_numbers_split(board) 42 | return cls(set(ints), ints) 43 | 44 | 45 | def compute(s: str) -> int: 46 | first, *board_strings = s.split('\n\n') 47 | 48 | boards = [Board.parse(s) for s in board_strings] 49 | 50 | last_won = -1 51 | seen = set() 52 | for number in support.parse_numbers_comma(first): 53 | for board in boards: 54 | board.left.discard(number) 55 | 56 | for i, board in enumerate(boards): 57 | if i not in seen and board.solved: 58 | last_won = board.summed * number 59 | seen.add(i) 60 | 61 | return last_won 62 | 63 | 64 | INPUT_S = '''\ 65 | 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 66 | 67 | 22 13 17 11 0 68 | 8 2 23 4 24 69 | 21 9 14 16 7 70 | 6 10 3 18 5 71 | 1 12 20 15 19 72 | 73 | 3 15 0 2 22 74 | 9 18 13 17 5 75 | 19 8 7 25 23 76 | 20 11 10 24 4 77 | 14 21 16 12 6 78 | 79 | 14 21 17 24 4 80 | 10 16 15 9 19 81 | 18 8 23 26 20 82 | 22 11 13 6 5 83 | 2 0 12 3 7 84 | ''' 85 | 86 | 87 | @pytest.mark.parametrize( 88 | ('input_s', 'expected'), 89 | ( 90 | (INPUT_S, 1924), 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 | -------------------------------------------------------------------------------- /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 bingo_rows (id, c, c1, c2, c3, c4, c5, c1d, c2d, c3d, c4d, c5d); 6 | WITH RECURSIVE 7 | nn (id, r, c1, c2, c3, c4, c5, rest) 8 | AS ( 9 | SELECT 10 | -1, 4, 0, 0, 0, 0, 0, 11 | ( 12 | SELECT 13 | SUBSTR(s, INSTR(s, char(10)) + 2) || char(10) || char(10) 14 | FROM input 15 | ) 16 | UNION ALL 17 | SELECT 18 | CASE nn.r = 4 WHEN 1 THEN nn.id + 1 ELSE nn.id END, 19 | CASE nn.r = 4 WHEN 1 THEN 0 ELSE nn.r + 1 END, 20 | SUBSTR(nn.rest, 1, 2) + 0, 21 | SUBSTR(nn.rest, 4, 2) + 0, 22 | SUBSTR(nn.rest, 7, 2) + 0, 23 | SUBSTR(nn.rest, 10, 2) + 0, 24 | SUBSTR(nn.rest, 13, 2) + 0, 25 | CASE nn.r = 3 26 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 2) 27 | ELSE SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 28 | END 29 | FROM nn WHERE nn.rest != '' 30 | ) 31 | INSERT INTO bingo_rows 32 | SELECT nn.id, 1, nn.c1, nn.c2, nn.c3, nn.c4, nn.c5, 0, 0, 0, 0, 0 33 | FROM nn WHERE nn.id >= 0; 34 | 35 | WITH RECURSIVE 36 | nn (id, r, c1, c2, c3, c4, c5, rest) 37 | AS ( 38 | SELECT 39 | -1, 4, 0, 0, 0, 0, 0, 40 | ( 41 | SELECT 42 | SUBSTR(s, INSTR(s, char(10)) + 2) || char(10) || char(10) 43 | FROM input 44 | ) 45 | UNION ALL 46 | SELECT 47 | CASE nn.r = 4 WHEN 1 THEN nn.id + 1 ELSE nn.id END, 48 | CASE nn.r = 4 WHEN 1 THEN 0 ELSE nn.r + 1 END, 49 | SUBSTR(nn.rest, 1 + nn.r * 3, 2) + 0, 50 | SUBSTR(nn.rest, 16 + nn.r * 3, 2) + 0, 51 | SUBSTR(nn.rest, 31 + nn.r * 3, 2) + 0, 52 | SUBSTR(nn.rest, 46 + nn.r * 3, 2) + 0, 53 | SUBSTR(nn.rest, 61 + nn.r * 3, 2) + 0, 54 | CASE nn.r = 3 55 | WHEN 1 THEN 56 | SUBSTR(nn.rest, INSTR(nn.rest, char(10) || char(10)) + 2) 57 | ELSE nn.rest 58 | END 59 | FROM nn WHERE nn.rest != '' 60 | ) 61 | INSERT INTO bingo_rows 62 | SELECT nn.id, 0, nn.c1, nn.c2, nn.c3, nn.c4, nn.c5, 0, 0, 0, 0, 0 63 | FROM nn WHERE nn.id >= 0; 64 | 65 | CREATE TABLE answers (n INT); 66 | CREATE TABLE nums (n INT); 67 | CREATE TRIGGER ttrig AFTER INSERT ON nums FOR EACH ROW BEGIN 68 | UPDATE bingo_rows SET c1d = 1 WHERE c1 = NEW.n; 69 | UPDATE bingo_rows SET c2d = 1 WHERE c2 = NEW.n; 70 | UPDATE bingo_rows SET c3d = 1 WHERE c3 = NEW.n; 71 | UPDATE bingo_rows SET c4d = 1 WHERE c4 = NEW.n; 72 | UPDATE bingo_rows SET c5d = 1 WHERE c5 = NEW.n; 73 | 74 | INSERT INTO answers 75 | SELECT NEW.n * ( 76 | SELECT 77 | SUM( 78 | c1 * (1 - c1d) + 79 | c2 * (1 - c2d) + 80 | c3 * (1 - c3d) + 81 | c4 * (1 - c4d) + 82 | c5 * (1 - c5d) 83 | ) 84 | FROM bingo_rows AS b2 85 | WHERE b2.id = bingo_rows.id AND b2.c = 1 86 | ) 87 | FROM bingo_rows 88 | WHERE c1d and c2d and c3d and c4d and c5d; 89 | 90 | DELETE FROM bingo_rows WHERE id IN ( 91 | SELECT id FROM bingo_rows 92 | WHERE c1d and c2d and c3d and c4d and c5d 93 | ); 94 | END; 95 | 96 | WITH RECURSIVE 97 | nn (n, rest) 98 | AS ( 99 | SELECT -1, (SELECT SUBSTR(s, 1, INSTR(s, char(10)) - 1) || ',' FROM input) 100 | UNION ALL 101 | SELECT 102 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1), 103 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 104 | FROM nn 105 | WHERE nn.rest != '' 106 | ) 107 | INSERT INTO nums 108 | SELECT nn.n FROM nn WHERE nn.n != -1; 109 | 110 | SELECT n FROM answers ORDER BY ROWID DESC LIMIT 1; 111 | -------------------------------------------------------------------------------- /day05/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day05/__init__.py -------------------------------------------------------------------------------- /day05/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 | positions: collections.Counter[tuple[int, int]] = collections.Counter() 16 | lines = s.splitlines() 17 | for line in lines: 18 | p1, p2 = line.split(' -> ') 19 | x1, y1 = support.parse_numbers_comma(p1) 20 | x2, y2 = support.parse_numbers_comma(p2) 21 | 22 | if x1 == x2: 23 | for y in range(min(y1, y2), max(y1, y2) + 1): 24 | positions[(x1, y)] += 1 25 | elif y1 == y2: 26 | for x in range(min(x1, x2), max(x1, x2) + 1): 27 | positions[x, y1] += 1 28 | 29 | n = 0 30 | for k, v in positions.most_common(): 31 | if v > 1: 32 | n += 1 33 | else: 34 | break 35 | return n 36 | 37 | 38 | INPUT_S = '''\ 39 | 0,9 -> 5,9 40 | 8,0 -> 0,8 41 | 9,4 -> 3,4 42 | 2,2 -> 2,1 43 | 7,0 -> 7,4 44 | 6,4 -> 2,0 45 | 0,9 -> 2,9 46 | 3,4 -> 1,4 47 | 0,0 -> 8,8 48 | 5,5 -> 8,2 49 | ''' 50 | EXPECTED = 5 51 | 52 | 53 | @pytest.mark.parametrize( 54 | ('input_s', 'expected'), 55 | ( 56 | (INPUT_S, EXPECTED), 57 | ), 58 | ) 59 | def test(input_s: str, expected: int) -> None: 60 | assert compute(input_s) == expected 61 | 62 | 63 | def main() -> int: 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 66 | args = parser.parse_args() 67 | 68 | with open(args.data_file) as f, support.timing(): 69 | print(compute(f.read())) 70 | 71 | return 0 72 | 73 | 74 | if __name__ == '__main__': 75 | raise SystemExit(main()) 76 | -------------------------------------------------------------------------------- /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 lines (x1 INT, y1 INT, x2 INT, y2 INT); 6 | WITH RECURSIVE 7 | nn (x1, y1, x2, y2, 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 | INSTR(nn.rest, ',') + 1, 16 | INSTR(nn.rest, ' ') - INSTR(nn.rest, ',') - 1 17 | ), 18 | SUBSTR( 19 | nn.rest, 20 | INSTR(nn.rest, '>') + 2, 21 | INSTR( 22 | SUBSTR(nn.rest, INSTR(nn.rest, '>') + 2), 23 | ',' 24 | ) - 1 25 | ), 26 | SUBSTR( 27 | nn.rest, 28 | INSTR(nn.rest, '>') + 29 | INSTR(SUBSTR(nn.rest, INSTR(nn.rest, '>')), ','), 30 | INSTR(nn.rest, char(10)) - 31 | INSTR(nn.rest, '>') - 32 | INSTR(SUBSTR(nn.rest, INSTR(nn.rest, '>')), ',') 33 | ), 34 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 35 | FROM nn 36 | WHERE nn.rest != '' 37 | ) 38 | INSERT INTO lines 39 | SELECT x1, y1, x2, y2 40 | FROM nn 41 | WHERE x1 > 0 AND (x1 = x2 OR y1 = y2); 42 | 43 | CREATE TABLE points (x, y, val); 44 | WITH RECURSIVE 45 | nn (rid, x, y, x2, y2) 46 | AS ( 47 | SELECT ROWID, x1, y1, x2, y2 FROM lines WHERE ROWID = 1 48 | UNION ALL 49 | SELECT 50 | CASE nn.x = nn.x2 AND nn.y = nn.y2 51 | WHEN 1 THEN nn.rid + 1 52 | ELSE nn.rid 53 | END, 54 | CASE nn.x = nn.x2 AND nn.y = nn.y2 55 | WHEN 1 THEN (SELECT lines.x1 FROM lines WHERE ROWID = nn.rid + 1) 56 | ELSE 57 | CASE nn.x = nn.x2 58 | WHEN 1 THEN nn.x 59 | ELSE 60 | CASE nn.x < nn.x2 61 | WHEN 1 THEN nn.x + 1 62 | ELSE nn.x - 1 63 | END 64 | END 65 | END, 66 | CASE nn.x = nn.x2 AND nn.y = nn.y2 67 | WHEN 1 THEN (SELECT lines.y1 FROM lines WHERE ROWID = nn.rid + 1) 68 | ELSE 69 | CASE nn.y = nn.y2 70 | WHEN 1 THEN nn.y 71 | ELSE 72 | CASE nn.y < nn.y2 73 | WHEN 1 THEN nn.y + 1 74 | ELSE nn.y - 1 75 | END 76 | END 77 | END, 78 | CASE nn.x = nn.x2 AND nn.y = nn.y2 79 | WHEN 1 THEN (SELECT lines.x2 FROM lines WHERE ROWID = nn.rid + 1) 80 | ELSE nn.x2 81 | END, 82 | CASE nn.x = nn.x2 AND nn.y = nn.y2 83 | WHEN 1 THEN (SELECT lines.y2 FROM lines WHERE ROWID = nn.rid + 1) 84 | ELSE nn.y2 85 | END 86 | FROM nn 87 | WHERE nn.x IS NOT NULL 88 | ) 89 | INSERT INTO points 90 | SELECT nn.x, nn.y, COUNT(1) FROM nn WHERE nn.x IS NOT NULL GROUP BY nn.x, nn.y; 91 | 92 | SELECT COUNT(1) FROM points WHERE val > 1; 93 | -------------------------------------------------------------------------------- /day05/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 | positions: collections.Counter[tuple[int, int]] = collections.Counter() 16 | lines = s.splitlines() 17 | for line in lines: 18 | p1, p2 = line.split(' -> ') 19 | x1, y1 = support.parse_numbers_comma(p1) 20 | x2, y2 = support.parse_numbers_comma(p2) 21 | 22 | if x2 > x1: 23 | x_d = 1 24 | elif x2 < x1: 25 | x_d = -1 26 | else: 27 | x_d = 0 28 | 29 | if y2 > y1: 30 | y_d = 1 31 | elif y2 < y1: 32 | y_d = -1 33 | else: 34 | y_d = 0 35 | 36 | x, y = x1, y1 37 | while (x, y) != (x2, y2): 38 | positions[(x, y)] += 1 39 | x, y = x + x_d, y + y_d 40 | positions[(x2, y2)] += 1 41 | 42 | n = 0 43 | for k, v in positions.most_common(): 44 | if v > 1: 45 | n += 1 46 | else: 47 | break 48 | return n 49 | 50 | 51 | INPUT_S = '''\ 52 | 0,9 -> 5,9 53 | 8,0 -> 0,8 54 | 9,4 -> 3,4 55 | 2,2 -> 2,1 56 | 7,0 -> 7,4 57 | 6,4 -> 2,0 58 | 0,9 -> 2,9 59 | 3,4 -> 1,4 60 | 0,0 -> 8,8 61 | 5,5 -> 8,2 62 | ''' 63 | EXPECTED = 12 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 | -------------------------------------------------------------------------------- /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 lines (x1 INT, y1 INT, x2 INT, y2 INT); 6 | WITH RECURSIVE 7 | nn (x1, y1, x2, y2, 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 | INSTR(nn.rest, ',') + 1, 16 | INSTR(nn.rest, ' ') - INSTR(nn.rest, ',') - 1 17 | ), 18 | SUBSTR( 19 | nn.rest, 20 | INSTR(nn.rest, '>') + 2, 21 | INSTR( 22 | SUBSTR(nn.rest, INSTR(nn.rest, '>') + 2), 23 | ',' 24 | ) - 1 25 | ), 26 | SUBSTR( 27 | nn.rest, 28 | INSTR(nn.rest, '>') + 29 | INSTR(SUBSTR(nn.rest, INSTR(nn.rest, '>')), ','), 30 | INSTR(nn.rest, char(10)) - 31 | INSTR(nn.rest, '>') - 32 | INSTR(SUBSTR(nn.rest, INSTR(nn.rest, '>')), ',') 33 | ), 34 | SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 35 | FROM nn 36 | WHERE nn.rest != '' 37 | ) 38 | INSERT INTO lines 39 | SELECT x1, y1, x2, y2 FROM nn WHERE x1 > 0; 40 | 41 | CREATE TABLE points (x, y, val); 42 | WITH RECURSIVE 43 | nn (rid, x, y, x2, y2) 44 | AS ( 45 | SELECT ROWID, x1, y1, x2, y2 FROM lines WHERE ROWID = 1 46 | UNION ALL 47 | SELECT 48 | CASE nn.x = nn.x2 AND nn.y = nn.y2 49 | WHEN 1 THEN nn.rid + 1 50 | ELSE nn.rid 51 | END, 52 | CASE nn.x = nn.x2 AND nn.y = nn.y2 53 | WHEN 1 THEN (SELECT lines.x1 FROM lines WHERE ROWID = nn.rid + 1) 54 | ELSE 55 | CASE nn.x = nn.x2 56 | WHEN 1 THEN nn.x 57 | ELSE 58 | CASE nn.x < nn.x2 59 | WHEN 1 THEN nn.x + 1 60 | ELSE nn.x - 1 61 | END 62 | END 63 | END, 64 | CASE nn.x = nn.x2 AND nn.y = nn.y2 65 | WHEN 1 THEN (SELECT lines.y1 FROM lines WHERE ROWID = nn.rid + 1) 66 | ELSE 67 | CASE nn.y = nn.y2 68 | WHEN 1 THEN nn.y 69 | ELSE 70 | CASE nn.y < nn.y2 71 | WHEN 1 THEN nn.y + 1 72 | ELSE nn.y - 1 73 | END 74 | END 75 | END, 76 | CASE nn.x = nn.x2 AND nn.y = nn.y2 77 | WHEN 1 THEN (SELECT lines.x2 FROM lines WHERE ROWID = nn.rid + 1) 78 | ELSE nn.x2 79 | END, 80 | CASE nn.x = nn.x2 AND nn.y = nn.y2 81 | WHEN 1 THEN (SELECT lines.y2 FROM lines WHERE ROWID = nn.rid + 1) 82 | ELSE nn.y2 83 | END 84 | FROM nn 85 | WHERE nn.x IS NOT NULL 86 | ) 87 | INSERT INTO points 88 | SELECT nn.x, nn.y, COUNT(1) FROM nn WHERE nn.x IS NOT NULL GROUP BY nn.x, nn.y; 89 | 90 | SELECT COUNT(1) FROM points WHERE val > 1; 91 | -------------------------------------------------------------------------------- /day06/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day06/__init__.py -------------------------------------------------------------------------------- /day06/input.txt: -------------------------------------------------------------------------------- 1 | 5,1,2,1,5,3,1,1,1,1,1,2,5,4,1,1,1,1,2,1,2,1,1,1,1,1,2,1,5,1,1,1,3,1,1,1,3,1,1,3,1,1,4,3,1,1,4,1,1,1,1,2,1,1,1,5,1,1,5,1,1,1,4,4,2,5,1,1,5,1,1,2,2,1,2,1,1,5,3,1,2,1,1,3,1,4,3,3,1,1,3,1,5,1,1,3,1,1,4,4,1,1,1,5,1,1,1,4,4,1,3,1,4,1,1,4,5,1,1,1,4,3,1,4,1,1,4,4,3,5,1,2,2,1,2,2,1,1,1,2,1,1,1,4,1,1,3,1,1,2,1,4,1,1,1,1,1,1,1,1,2,2,1,1,5,5,1,1,1,5,1,1,1,1,5,1,3,2,1,1,5,2,3,1,2,2,2,5,1,1,3,1,1,1,5,1,4,1,1,1,3,2,1,3,3,1,3,1,1,1,1,1,1,1,2,3,1,5,1,4,1,3,5,1,1,1,2,2,1,1,1,1,5,4,1,1,3,1,2,4,2,1,1,3,5,1,1,1,3,1,1,1,5,1,1,1,1,1,3,1,1,1,4,1,1,1,1,2,2,1,1,1,1,5,3,1,2,3,4,1,1,5,1,2,4,2,1,1,1,2,1,1,1,1,1,1,1,4,1,5 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 | numbers = collections.Counter(support.parse_numbers_comma(s)) 16 | 17 | for d in range(80): 18 | numbers2 = collections.Counter({8: numbers[0], 6: numbers[0]}) 19 | numbers2.update({k - 1: v for k, v in numbers.items() if k > 0}) 20 | numbers = numbers2 21 | 22 | return sum(numbers.values()) 23 | 24 | 25 | INPUT_S = '''\ 26 | 3,4,3,1,2 27 | ''' 28 | EXPECTED = 5934 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 | -------------------------------------------------------------------------------- /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 counts (n0, n1, n2, n3, n4, n5, n6, n7, n8); 6 | WITH RECURSIVE 7 | nn (n, rest) 8 | AS ( 9 | SELECT -1, (SELECT s || ',' FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1) + 0, 13 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 14 | FROM nn WHERE nn.rest != '' 15 | ) 16 | INSERT INTO counts 17 | SELECT 18 | (SELECT COUNT(1) FROM nn WHERE nn.n = 0), 19 | (SELECT COUNT(1) FROM nn WHERE nn.n = 1), 20 | (SELECT COUNT(1) FROM nn WHERE nn.n = 2), 21 | (SELECT COUNT(1) FROM nn WHERE nn.n = 3), 22 | (SELECT COUNT(1) FROM nn WHERE nn.n = 4), 23 | (SELECT COUNT(1) FROM nn WHERE nn.n = 5), 24 | (SELECT COUNT(1) FROM nn WHERE nn.n = 6), 25 | (SELECT COUNT(1) FROM nn WHERE nn.n = 7), 26 | (SELECT COUNT(1) FROM nn WHERE nn.n = 8); 27 | 28 | WITH RECURSIVE 29 | nn (i, n0, n1, n2, n3, n4, n5, n6, n7, n8) 30 | AS ( 31 | SELECT 0, * FROM counts 32 | UNION ALL 33 | SELECT 34 | nn.i + 1, 35 | nn.n1, 36 | nn.n2, 37 | nn.n3, 38 | nn.n4, 39 | nn.n5, 40 | nn.n6, 41 | nn.n7 + nn.n0, 42 | nn.n8, 43 | nn.n0 44 | FROM nn WHERE nn.i < 80 45 | ) 46 | SELECT n0 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 47 | FROM nn ORDER BY nn.i DESC LIMIT 1; 48 | -------------------------------------------------------------------------------- /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 | numbers = collections.Counter(support.parse_numbers_comma(s)) 16 | 17 | for d in range(256): 18 | numbers2 = collections.Counter({8: numbers[0], 6: numbers[0]}) 19 | numbers2.update({k - 1: v for k, v in numbers.items() if k > 0}) 20 | numbers = numbers2 21 | 22 | return sum(numbers.values()) 23 | 24 | 25 | INPUT_S = '''\ 26 | 3,4,3,1,2 27 | ''' 28 | EXPECTED = 26984457539 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 | -------------------------------------------------------------------------------- /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 counts (n0, n1, n2, n3, n4, n5, n6, n7, n8); 6 | WITH RECURSIVE 7 | nn (n, rest) 8 | AS ( 9 | SELECT -1, (SELECT s || ',' FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1) + 0, 13 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 14 | FROM nn WHERE nn.rest != '' 15 | ) 16 | INSERT INTO counts 17 | SELECT 18 | (SELECT COUNT(1) FROM nn WHERE nn.n = 0), 19 | (SELECT COUNT(1) FROM nn WHERE nn.n = 1), 20 | (SELECT COUNT(1) FROM nn WHERE nn.n = 2), 21 | (SELECT COUNT(1) FROM nn WHERE nn.n = 3), 22 | (SELECT COUNT(1) FROM nn WHERE nn.n = 4), 23 | (SELECT COUNT(1) FROM nn WHERE nn.n = 5), 24 | (SELECT COUNT(1) FROM nn WHERE nn.n = 6), 25 | (SELECT COUNT(1) FROM nn WHERE nn.n = 7), 26 | (SELECT COUNT(1) FROM nn WHERE nn.n = 8); 27 | 28 | WITH RECURSIVE 29 | nn (i, n0, n1, n2, n3, n4, n5, n6, n7, n8) 30 | AS ( 31 | SELECT 0, * FROM counts 32 | UNION ALL 33 | SELECT 34 | nn.i + 1, 35 | nn.n1, 36 | nn.n2, 37 | nn.n3, 38 | nn.n4, 39 | nn.n5, 40 | nn.n6, 41 | nn.n7 + nn.n0, 42 | nn.n8, 43 | nn.n0 44 | FROM nn WHERE nn.i < 256 45 | ) 46 | SELECT n0 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 47 | FROM nn ORDER BY nn.i DESC LIMIT 1; 48 | -------------------------------------------------------------------------------- /day07/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day07/__init__.py -------------------------------------------------------------------------------- /day07/input.txt: -------------------------------------------------------------------------------- 1 | 1101,1,29,67,1102,0,1,65,1008,65,35,66,1005,66,28,1,67,65,20,4,0,1001,65,1,65,1106,0,8,99,35,67,101,99,105,32,110,39,101,115,116,32,112,97,115,32,117,110,101,32,105,110,116,99,111,100,101,32,112,114,111,103,114,97,109,10,20,1091,861,228,628,186,980,996,710,541,354,1611,69,1331,91,1220,447,523,38,1286,244,643,1069,566,70,155,1710,1266,120,302,72,232,387,1086,278,1122,605,1559,98,111,1816,795,543,1217,304,356,129,839,704,49,523,370,74,13,232,179,101,664,892,266,622,197,404,147,882,435,504,48,766,684,1362,136,830,1393,1259,925,68,879,251,43,1339,61,98,403,51,1008,197,659,195,1823,233,121,731,82,141,580,18,427,774,13,685,496,752,63,132,39,237,18,167,51,299,22,19,1442,305,1283,253,159,731,302,76,115,185,136,447,821,307,207,30,1427,251,589,0,1096,1240,261,442,757,5,172,847,858,382,425,79,402,166,1058,186,35,21,324,183,1293,95,410,321,12,155,88,409,40,428,1180,199,1444,487,148,57,1187,15,100,983,77,0,1667,359,513,659,339,142,968,994,787,0,443,183,133,538,4,332,1459,204,1156,710,1654,20,36,407,890,1265,1090,743,36,78,1033,781,608,476,103,1401,24,4,875,414,799,305,1047,842,72,497,362,270,73,12,9,0,21,11,51,1357,455,505,483,552,199,1108,214,238,686,1496,116,154,1403,35,272,738,1024,50,50,934,564,19,395,324,447,794,1326,14,407,1694,452,439,455,442,86,1515,588,809,224,112,156,21,1405,610,187,23,370,112,397,995,777,75,1281,32,60,284,388,916,555,200,675,20,320,32,398,104,113,447,113,351,396,322,36,1674,1708,232,1004,514,95,859,382,116,277,239,343,3,433,11,55,699,513,1465,319,44,306,224,615,482,695,421,300,321,283,579,323,102,275,17,723,632,713,277,801,222,130,189,1549,49,784,690,136,444,1315,259,1334,472,711,1109,276,70,315,838,35,328,766,1100,460,4,178,630,571,5,106,429,368,547,1210,840,162,166,10,403,880,287,44,1001,316,402,1054,174,7,1194,105,58,268,667,86,588,166,547,238,1586,77,112,244,107,63,873,1152,16,407,198,26,587,20,1449,775,653,1369,732,262,566,222,10,102,22,573,233,297,1238,789,291,93,206,1274,177,58,841,672,37,16,262,201,241,938,133,774,978,631,511,0,263,498,799,51,330,11,206,325,173,676,15,457,364,46,373,34,1475,530,672,295,55,4,297,274,1519,15,688,555,96,160,185,583,646,41,378,1572,67,219,572,143,16,286,65,788,886,243,883,1012,109,90,742,464,1099,388,1855,731,62,6,415,66,232,542,98,1123,11,414,1262,4,440,8,691,130,164,773,992,423,115,1807,1618,153,168,1213,719,291,316,311,110,24,608,0,127,131,142,196,232,75,248,412,275,1295,239,86,967,133,422,415,894,280,807,345,446,250,979,231,713,201,1009,208,444,43,1400,434,8,221,141,235,909,1018,340,0,178,1144,353,662,491,294,6,440,446,824,1392,379,269,1427,911,671,231,424,102,718,86,54,130,206,514,137,1075,1573,248,472,602,249,974,8,372,59,2,940,165,1132,327,1424,63,6,97,140,1302,439,1237,59,324,733,397,477,426,278,274,1636,745,41,269,257,51,173,503,88,1223,754,228,584,72,632,645,323,156,7,337,192,375,583,613,370,172,528,1282,360,208,17,208,802,22,67,290,242,458,809,6,1388,49,14,327,911,121,475,101,617,284,91,1,35,421,293,1419,602,143,142,168,657,472,219,345,411,115,387,5,494,383,348,85,1070,154,42,63,586,953,563,12,1263,788,762,222,351,730,42,643,877,522,775,698,564,604,155,191,430,5,386,787,120,470,433,564,955,411,1090,210,1096,933,109,70,279,287,736,390,1075,30,194,30,1318,473,727,100,584,1227,89,432,120,393,1080,185,500,847,117,319,54,160,32,114,700,12,36,681,53,1063,113,82,1625,1426,53,658,80,253,36,16,810,560,602,177,275,147,335,1237,1447,176,55,366,721,471,501,217,67,899,914,168,981,1177,898,1,9,704,602,787,1280,305,57,786,1696,211,63,55,351,14,151,932,757,810,1748,169,1018,125,849,234,102,823,300,127,6,849,1163,229,726,397,656,135,466,137,1247,811,807,366,209,1703,24,1219,45,161,353,274,572,397,899,646,32,137,439,1048,2,391,8,214,736,518,409,414,567,1262,155,102,178,1247,526,10,94,759,781,18,14,1518,68,295,905,478,1581,89,429,937,438,915,1110,659,615,1128,8,125,89,289,1084,905,254,1227,184,883,983,110,1,24,748,1408,828,1187,63,264,481,214 2 | -------------------------------------------------------------------------------- /day07/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import statistics 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 | numbers = support.parse_numbers_comma(s) 16 | 17 | median = int(statistics.median(numbers)) 18 | return sum(abs(num - median) for num in numbers) 19 | 20 | 21 | INPUT_S = '''\ 22 | 16,1,2,0,4,2,7,1,2,14 23 | ''' 24 | EXPECTED = 37 25 | 26 | 27 | @pytest.mark.parametrize( 28 | ('input_s', 'expected'), 29 | ( 30 | (INPUT_S, EXPECTED), 31 | ), 32 | ) 33 | def test(input_s: str, expected: int) -> None: 34 | assert compute(input_s) == expected 35 | 36 | 37 | def main() -> int: 38 | parser = argparse.ArgumentParser() 39 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 40 | args = parser.parse_args() 41 | 42 | with open(args.data_file) as f, support.timing(): 43 | print(compute(f.read())) 44 | 45 | return 0 46 | 47 | 48 | if __name__ == '__main__': 49 | raise SystemExit(main()) 50 | -------------------------------------------------------------------------------- /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 numbers (n INT); 6 | WITH RECURSIVE 7 | nn (n, rest) 8 | AS ( 9 | SELECT -1, (SELECT s || ',' FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1) + 0, 13 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 14 | FROM nn WHERE nn.rest != '' 15 | ) 16 | INSERT INTO numbers SELECT nn.n FROM nn WHERE nn.n >= 0 ORDER BY nn.n ASC; 17 | 18 | SELECT SUM( 19 | ABS( 20 | n - ( 21 | SELECT n FROM numbers 22 | WHERE ROWID = (SELECT MAX(ROWID) FROM numbers) / 2 23 | ) 24 | ) 25 | ) FROM numbers; 26 | -------------------------------------------------------------------------------- /day07/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import statistics 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 | numbers = support.parse_numbers_comma(s) 16 | 17 | def get_val(n: int) -> int: 18 | return sum(abs(num - n) * (abs(num - n) + 1) // 2 for num in numbers) 19 | 20 | mean = round(statistics.mean(numbers)) 21 | val = get_val(mean) 22 | 23 | if get_val(mean - 1) < val: 24 | direction = -1 25 | else: 26 | direction = 1 27 | 28 | while get_val(mean + direction) < val: 29 | mean += direction 30 | val = get_val(mean) 31 | 32 | return val 33 | 34 | 35 | INPUT_S = '''\ 36 | 16,1,2,0,4,2,7,1,2,14 37 | ''' 38 | EXPECTED = 168 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 | -------------------------------------------------------------------------------- /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 numbers (n INT); 6 | WITH RECURSIVE 7 | nn (n, rest) 8 | AS ( 9 | SELECT -1, (SELECT s || ',' FROM input) 10 | UNION ALL 11 | SELECT 12 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ',') - 1) + 0, 13 | SUBSTR(nn.rest, INSTR(nn.rest, ',') + 1) 14 | FROM nn WHERE nn.rest != '' 15 | ) 16 | INSERT INTO numbers SELECT nn.n FROM nn WHERE nn.n >= 0 ORDER BY nn.n ASC; 17 | 18 | WITH RECURSIVE nn(n) AS ( 19 | SELECT MIN(n) FROM numbers 20 | UNION ALL 21 | SELECT nn.n + 1 FROM nn 22 | WHERE nn.n < (SELECT MAX(n) FROM numbers) 23 | ) 24 | SELECT MIN(summed) FROM ( 25 | SELECT 26 | ( 27 | SELECT SUM(ABS(nn.n - n) * (ABS(nn.n - n) + 1) / 2) 28 | FROM numbers 29 | ) AS summed 30 | FROM nn 31 | ); 32 | -------------------------------------------------------------------------------- /day08/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/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 | lines = s.splitlines() 15 | items = [] 16 | for line in lines: 17 | _, end_part = line.split(' | ') 18 | items.extend(end_part.split()) 19 | return sum(len(part) in (2, 3, 4, 7) for part in items) 20 | 21 | 22 | INPUT_S = '''\ 23 | be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe 24 | edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc 25 | fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg 26 | fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb 27 | aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea 28 | fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb 29 | dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe 30 | bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef 31 | egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb 32 | gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce 33 | ''' # noqa: E501 34 | EXPECTED = 26 35 | 36 | 37 | @pytest.mark.parametrize( 38 | ('input_s', 'expected'), 39 | ( 40 | (INPUT_S, EXPECTED), 41 | ), 42 | ) 43 | def test(input_s: str, expected: int) -> None: 44 | assert compute(input_s) == expected 45 | 46 | 47 | def main() -> int: 48 | parser = argparse.ArgumentParser() 49 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 50 | args = parser.parse_args() 51 | 52 | with open(args.data_file) as f, support.timing(): 53 | print(compute(f.read())) 54 | 55 | return 0 56 | 57 | 58 | if __name__ == '__main__': 59 | raise SystemExit(main()) 60 | -------------------------------------------------------------------------------- /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 | WITH RECURSIVE nn (s, rest) AS ( 6 | SELECT '', (SELECT SUBSTR(s, INSTR(s, '|') + 2) || char(10) FROM input) 7 | UNION ALL 8 | SELECT 9 | SUBSTR( 10 | nn.rest, 11 | 1, 12 | CASE INSTR(nn.rest, ' ') > 0 13 | WHEN 1 14 | THEN MIN(INSTR(nn.rest, ' '), INSTR(nn.rest, char(10))) - 1 15 | ELSE INSTR(nn.rest, char(10)) - 1 16 | END 17 | ), 18 | CASE ( 19 | INSTR(nn.rest, ' ') > 0 AND 20 | INSTR(nn.rest, ' ') < INSTR(nn.rest, char(10)) 21 | ) 22 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, ' ') + 1) 23 | ELSE 24 | CASE INSTR(nn.rest, '|') > 0 25 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, '|') + 2) 26 | ELSE SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 27 | END 28 | END 29 | FROM nn WHERE nn.rest != '' 30 | ) 31 | SELECT COUNT(1) 32 | FROM nn 33 | WHERE LENGTH(nn.s) IN (2, 3, 4, 7); 34 | -------------------------------------------------------------------------------- /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 | lines = s.splitlines() 15 | 16 | total = 0 17 | for line in lines: 18 | start, end = line.split(' | ') 19 | end_parts = [''.join(sorted(s)) for s in end.split()] 20 | digits = {*start.split(), *end_parts} 21 | digits = {''.join(sorted(part)) for part in digits} 22 | 23 | num_to_s = {} 24 | num_to_s[1], = (s for s in digits if len(s) == 2) 25 | num_to_s[7], = (s for s in digits if len(s) == 3) 26 | num_to_s[4], = (s for s in digits if len(s) == 4) 27 | num_to_s[8], = (s for s in digits if len(s) == 7) 28 | len6 = {s for s in digits if len(s) == 6} 29 | 30 | num_to_s[6], = (s for s in len6 if len(set(s) & set(num_to_s[1])) == 1) 31 | num_to_s[9], = (s for s in len6 if len(set(s) & set(num_to_s[4])) == 4) 32 | num_to_s[0], = len6 - {num_to_s[6], num_to_s[9]} 33 | 34 | len5 = {s for s in digits if len(s) == 5} 35 | 36 | num_to_s[5], = (s for s in len5 if len(set(s) & set(num_to_s[6])) == 5) 37 | num_to_s[3], = (s for s in len5 if len(set(s) & set(num_to_s[1])) == 2) 38 | num_to_s[2], = len5 - {num_to_s[5], num_to_s[3]} 39 | 40 | s_to_num = {v: k for k, v in num_to_s.items()} 41 | 42 | total += sum(10 ** (3 - i) * s_to_num[end_parts[i]] for i in range(4)) 43 | 44 | return total 45 | 46 | 47 | INPUT_S = '''\ 48 | be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe 49 | edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc 50 | fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg 51 | fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb 52 | aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea 53 | fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb 54 | dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe 55 | bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef 56 | egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb 57 | gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce 58 | ''' # noqa: E501 59 | EXPECTED = 61229 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 | -------------------------------------------------------------------------------- /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 nums (id INT, s STRING, val INT, PRIMARY KEY (id, s)); 6 | WITH RECURSIVE nn (pos, s, rest) AS ( 7 | SELECT -1, '', (SELECT s || char(10) FROM input) 8 | UNION ALL 9 | SELECT 10 | nn.pos + 1, 11 | SUBSTR(nn.rest, 1, INSTR(nn.rest, ' ') - 1), 12 | CASE SUBSTR(nn.rest, INSTR(nn.rest, ' ') + 1, 1) = '|' 13 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 14 | ELSE SUBSTR(nn.rest, INSTR(nn.rest, ' ') + 1) 15 | END 16 | FROM nn WHERE nn.rest != '' 17 | ) 18 | INSERT INTO nums 19 | SELECT 20 | CAST(nn.pos / 10 AS INT), 21 | (CASE nn.s LIKE '%a%' WHEN 1 THEN 'a' ELSE '' END) || 22 | (CASE nn.s LIKE '%b%' WHEN 1 THEN 'b' ELSE '' END) || 23 | (CASE nn.s LIKE '%c%' WHEN 1 THEN 'c' ELSE '' END) || 24 | (CASE nn.s LIKE '%d%' WHEN 1 THEN 'd' ELSE '' END) || 25 | (CASE nn.s LIKE '%e%' WHEN 1 THEN 'e' ELSE '' END) || 26 | (CASE nn.s LIKE '%f%' WHEN 1 THEN 'f' ELSE '' END) || 27 | (CASE nn.s LIKE '%g%' WHEN 1 THEN 'g' ELSE '' END), 28 | NULL 29 | FROM nn WHERE nn.s != ''; 30 | 31 | UPDATE nums SET val = 1 WHERE LENGTH(nums.s) = 2; 32 | UPDATE nums SET val = 7 WHERE LENGTH(nums.s) = 3; 33 | UPDATE nums SET val = 4 WHERE LENGTH(nums.s) = 4; 34 | UPDATE nums SET val = 8 WHERE LENGTH(nums.s) = 7; 35 | 36 | -- old sqlite does not have update with join 37 | INSERT OR REPLACE INTO nums 38 | SELECT nums.id, nums.s, 6 39 | FROM nums INNER JOIN nums AS n2 ON nums.id = n2.id AND n2.val = 1 40 | WHERE ( 41 | LENGTH(nums.s) = 6 AND 42 | 1 = ( 43 | (nums.s LIKE '%' || SUBSTR(n2.s, 1, 1) || '%') + 44 | (nums.s LIKE '%' || SUBSTR(n2.s, 2, 1) || '%') 45 | ) 46 | ); 47 | 48 | INSERT OR REPLACE INTO nums 49 | SELECT nums.id, nums.s, 9 50 | FROM nums INNER JOIN nums AS n2 ON nums.id = n2.id AND n2.val = 4 51 | WHERE ( 52 | LENGTH(nums.s) = 6 AND 53 | nums.s LIKE '%' || SUBSTR(n2.s, 1, 1) || '%' AND 54 | nums.s LIKE '%' || SUBSTR(n2.s, 2, 1) || '%' AND 55 | nums.s LIKE '%' || SUBSTR(n2.s, 3, 1) || '%' AND 56 | nums.s LIKE '%' || SUBSTR(n2.s, 4, 1) || '%' 57 | ); 58 | 59 | UPDATE nums SET val = 0 WHERE nums.val IS NULL AND LENGTH(nums.s) = 6; 60 | 61 | INSERT OR REPLACE INTO nums 62 | SELECT nums.id, nums.s, 3 63 | FROM nums INNER JOIN nums AS n2 ON nums.id = n2.id AND n2.val = 1 64 | WHERE ( 65 | LENGTH(nums.s) = 5 AND 66 | nums.s LIKE '%' || SUBSTR(n2.s, 1, 1) || '%' AND 67 | nums.s LIKE '%' || SUBSTR(n2.s, 2, 1) || '%' 68 | ); 69 | 70 | INSERT OR REPLACE INTO nums 71 | SELECT nums.id, nums.s, 5 72 | FROM nums INNER JOIN nums AS n2 ON nums.id = n2.id AND n2.val = 6 73 | WHERE ( 74 | LENGTH(nums.s) = 5 AND 75 | n2.s LIKE '%' || SUBSTR(nums.s, 1, 1) || '%' AND 76 | n2.s LIKE '%' || SUBSTR(nums.s, 2, 1) || '%' AND 77 | n2.s LIKE '%' || SUBSTR(nums.s, 3, 1) || '%' AND 78 | n2.s LIKE '%' || SUBSTR(nums.s, 4, 1) || '%' AND 79 | n2.s LIKE '%' || SUBSTR(nums.s, 5, 1) || '%' 80 | ); 81 | 82 | UPDATE nums SET val = 2 WHERE nums.val IS NULL; 83 | 84 | CREATE TABLE outputs (id, pos, s); 85 | WITH RECURSIVE nn (pos, s, rest) AS ( 86 | SELECT -1, '', (SELECT SUBSTR(s, INSTR(s, '|') + 2) || char(10) FROM input) 87 | UNION ALL 88 | SELECT 89 | nn.pos + 1, 90 | SUBSTR( 91 | nn.rest, 92 | 1, 93 | CASE INSTR(nn.rest, ' ') > 0 94 | WHEN 1 95 | THEN MIN(INSTR(nn.rest, ' '), INSTR(nn.rest, char(10))) - 1 96 | ELSE INSTR(nn.rest, char(10)) - 1 97 | END 98 | ), 99 | CASE ( 100 | INSTR(nn.rest, ' ') > 0 AND 101 | INSTR(nn.rest, ' ') < INSTR(nn.rest, char(10)) 102 | ) 103 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, ' ') + 1) 104 | ELSE 105 | CASE INSTR(nn.rest, '|') > 0 106 | WHEN 1 THEN SUBSTR(nn.rest, INSTR(nn.rest, '|') + 2) 107 | ELSE SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 108 | END 109 | END 110 | FROM nn WHERE nn.rest != '' 111 | ) 112 | INSERT INTO outputs 113 | SELECT 114 | CAST(nn.pos / 4 AS INT), 115 | nn.pos % 4, 116 | (CASE nn.s LIKE '%a%' WHEN 1 THEN 'a' ELSE '' END) || 117 | (CASE nn.s LIKE '%b%' WHEN 1 THEN 'b' ELSE '' END) || 118 | (CASE nn.s LIKE '%c%' WHEN 1 THEN 'c' ELSE '' END) || 119 | (CASE nn.s LIKE '%d%' WHEN 1 THEN 'd' ELSE '' END) || 120 | (CASE nn.s LIKE '%e%' WHEN 1 THEN 'e' ELSE '' END) || 121 | (CASE nn.s LIKE '%f%' WHEN 1 THEN 'f' ELSE '' END) || 122 | (CASE nn.s LIKE '%g%' WHEN 1 THEN 'g' ELSE '' END) 123 | FROM nn WHERE nn.pos >= 0; 124 | 125 | SELECT SUM( 126 | ( 127 | CASE outputs.pos 128 | WHEN 0 THEN 1000 129 | WHEN 1 THEN 100 130 | WHEN 2 THEN 10 131 | WHEN 3 THEN 1 132 | END 133 | ) * nums.val 134 | ) 135 | FROM outputs 136 | INNER JOIN nums ON nums.id = outputs.id AND nums.s = outputs.s; 137 | -------------------------------------------------------------------------------- /day09/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day09/__init__.py -------------------------------------------------------------------------------- /day09/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_int(s) 17 | coords = collections.defaultdict(lambda: sys.maxsize, coords) 18 | 19 | total = 0 20 | for (x, y), n in tuple(coords.items()): 21 | if all(coords[pt] > n for pt in support.adjacent_4(x, y)): 22 | total += n + 1 23 | 24 | return total 25 | 26 | 27 | INPUT_S = '''\ 28 | 2199943210 29 | 3987894921 30 | 9856789892 31 | 8767896789 32 | 9899965678 33 | ''' 34 | EXPECTED = 15 35 | 36 | 37 | @pytest.mark.parametrize( 38 | ('input_s', 'expected'), 39 | ( 40 | (INPUT_S, EXPECTED), 41 | ), 42 | ) 43 | def test(input_s: str, expected: int) -> None: 44 | assert compute(input_s) == expected 45 | 46 | 47 | def main() -> int: 48 | parser = argparse.ArgumentParser() 49 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 50 | args = parser.parse_args() 51 | 52 | with open(args.data_file) as f, support.timing(): 53 | print(compute(f.read())) 54 | 55 | return 0 56 | 57 | 58 | if __name__ == '__main__': 59 | raise SystemExit(main()) 60 | -------------------------------------------------------------------------------- /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 | CREATE TABLE vars(width); 6 | INSERT INTO vars SELECT INSTR(s, char(10)) - 1 FROM input; 7 | 8 | CREATE TABLE coords(y, x, n INT); 9 | WITH RECURSIVE nn (x, c, rest) AS ( 10 | SELECT -1, '', (SELECT s || char(10) FROM input) 11 | UNION ALL 12 | SELECT 13 | nn.x + 1, 14 | SUBSTR(nn.rest, 1, 1), 15 | CASE INSTR(nn.rest, char(10)) = 2 16 | WHEN 1 THEN SUBSTR(nn.rest, 3) 17 | ELSE SUBSTR(nn.rest, 2) 18 | END 19 | FROM nn WHERE nn.rest != '' 20 | ) 21 | INSERT INTO coords 22 | SELECT 23 | nn.x / (SELECT width FROM vars), 24 | nn.x % (SELECT width FROM vars), 25 | nn.c 26 | FROM nn WHERE nn.c != ''; 27 | 28 | SELECT SUM(c.n + 1) 29 | FROM coords AS c 30 | LEFT OUTER JOIN coords AS c1 ON c1.y = c.y AND c1.x = c.x + 1 31 | LEFT OUTER JOIN coords AS c2 ON c2.y = c.y AND c2.x = c.x - 1 32 | LEFT OUTER JOIN coords AS c3 ON c3.y = c.y + 1 AND c3.x = c.x 33 | LEFT OUTER JOIN coords AS c4 ON c4.y = c.y - 1 AND c4.x = c.x 34 | WHERE ( 35 | COALESCE(c1.n, 9) > c.n AND 36 | COALESCE(c2.n, 9) > c.n AND 37 | COALESCE(c3.n, 9) > c.n AND 38 | COALESCE(c4.n, 9) > c.n 39 | ); 40 | -------------------------------------------------------------------------------- /day09/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_int(s) 16 | coords = collections.defaultdict(lambda: 9, coords) 17 | 18 | sizes = [] 19 | for (x, y), n in tuple(coords.items()): 20 | if all(coords[pt] > n for pt in support.adjacent_4(x, y)): 21 | seen = set() 22 | todo = [(x, y)] 23 | while todo: 24 | x, y = todo.pop() 25 | seen.add((x, y)) 26 | 27 | for other in support.adjacent_4(x, y): 28 | if other not in seen and coords[other] != 9: 29 | todo.append(other) 30 | 31 | sizes.append(len(seen)) 32 | 33 | sizes.sort() 34 | return sizes[-1] * sizes[-2] * sizes[-3] 35 | 36 | 37 | INPUT_S = '''\ 38 | 2199943210 39 | 3987894921 40 | 9856789892 41 | 8767896789 42 | 9899965678 43 | ''' 44 | EXPECTED = 1134 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 | -------------------------------------------------------------------------------- /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 | CREATE TABLE vars(width); 6 | INSERT INTO vars SELECT INSTR(s, char(10)) - 1 FROM input; 7 | 8 | CREATE TABLE coords(y, x, n INT); 9 | WITH RECURSIVE nn (x, c, rest) AS ( 10 | SELECT -1, '', (SELECT s || char(10) FROM input) 11 | UNION ALL 12 | SELECT 13 | nn.x + 1, 14 | SUBSTR(nn.rest, 1, 1), 15 | CASE INSTR(nn.rest, char(10)) = 2 16 | WHEN 1 THEN SUBSTR(nn.rest, 3) 17 | ELSE SUBSTR(nn.rest, 2) 18 | END 19 | FROM nn WHERE nn.rest != '' 20 | ) 21 | INSERT INTO coords 22 | SELECT 23 | nn.x / (SELECT width FROM vars), 24 | nn.x % (SELECT width FROM vars), 25 | nn.c 26 | FROM nn WHERE nn.c != ''; 27 | 28 | CREATE TABLE minimums (y, x); 29 | INSERT INTO minimums 30 | SELECT c.y, c.x 31 | FROM coords AS c 32 | LEFT OUTER JOIN coords AS c1 ON c1.y = c.y AND c1.x = c.x + 1 33 | LEFT OUTER JOIN coords AS c2 ON c2.y = c.y AND c2.x = c.x - 1 34 | LEFT OUTER JOIN coords AS c3 ON c3.y = c.y + 1 AND c3.x = c.x 35 | LEFT OUTER JOIN coords AS c4 ON c4.y = c.y - 1 AND c4.x = c.x 36 | WHERE ( 37 | COALESCE(c1.n, 9) > c.n AND 38 | COALESCE(c2.n, 9) > c.n AND 39 | COALESCE(c3.n, 9) > c.n AND 40 | COALESCE(c4.n, 9) > c.n 41 | ); 42 | 43 | PRAGMA recursive_triggers = on; 44 | 45 | CREATE TABLE basins (y, x, orig_y, orig_x, PRIMARY KEY (y, x)); 46 | CREATE TRIGGER ttrig AFTER INSERT ON basins 47 | BEGIN 48 | INSERT OR REPLACE INTO basins 49 | SELECT y, x, NEW.orig_y, NEW.orig_x 50 | FROM coords 51 | WHERE ( 52 | ( 53 | (coords.y = NEW.y AND coords.x = NEW.x + 1) OR 54 | (coords.y = NEW.y AND coords.x = NEW.x - 1) OR 55 | (coords.y = NEW.y + 1 AND coords.x = NEW.x) OR 56 | (coords.y = NEW.y - 1 AND coords.x = NEW.x) 57 | ) AND 58 | coords.n < 9 AND 59 | ( 60 | SELECT 1 61 | FROM basins 62 | WHERE basins.y = coords.y AND basins.x = coords.x 63 | ) IS NULL 64 | ); 65 | END; 66 | 67 | INSERT INTO basins 68 | SELECT y, x, y, x FROM minimums; 69 | 70 | CREATE TABLE sizes (n); 71 | 72 | INSERT INTO sizes 73 | SELECT COUNT(1) 74 | FROM basins 75 | GROUP BY orig_y, orig_x 76 | ORDER BY COUNT(1) DESC LIMIT 3; 77 | 78 | SELECT 79 | (SELECT n FROM sizes WHERE ROWID = 1) * 80 | (SELECT n FROM sizes WHERE ROWID = 2) * 81 | (SELECT n FROM sizes WHERE ROWID = 3); 82 | -------------------------------------------------------------------------------- /day10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day10/__init__.py -------------------------------------------------------------------------------- /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 | POINTS = { 13 | ')': 3, 14 | ']': 57, 15 | '}': 1197, 16 | '>': 25137, 17 | } 18 | FORWARD = {'{': '}', '(': ')', '[': ']', '<': '>'} 19 | REVERSE = {v: k for k, v in FORWARD.items()} 20 | 21 | 22 | def compute(s: str) -> int: 23 | total = 0 24 | lines = s.splitlines() 25 | for line in lines: 26 | stack = [] 27 | for c in line: 28 | if c in FORWARD: 29 | stack.append(c) 30 | elif c in REVERSE: 31 | if stack[-1] != REVERSE[c]: 32 | total += POINTS[c] 33 | break 34 | else: 35 | stack.pop() 36 | return total 37 | 38 | 39 | INPUT_S = '''\ 40 | [({(<(())[]>[[{[]{<()<>> 41 | [(()[<>])]({[<{<<[]>>( 42 | {([(<{}[<>[]}>{[]{[(<()> 43 | (((({<>}<{<{<>}{[]{[]{} 44 | [[<[([]))<([[{}[[()]]] 45 | [{[{({}]{}}([{[{{{}}([] 46 | {<[[]]>}<{[{[{[]{()[[[] 47 | [<(<(<(<{}))><([]([]() 48 | <{([([[(<>()){}]>(<<{{ 49 | <{([{{}}[<[[[<>{}]]]>[]] 50 | ''' 51 | EXPECTED = 26397 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 | -------------------------------------------------------------------------------- /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 braces (k, v, n); 6 | INSERT INTO braces VALUES 7 | ('(', ')', 3), 8 | ('[', ']', 57), 9 | ('{', '}', 1197), 10 | ('<', '>', 25137); 11 | 12 | WITH RECURSIVE nn (total, stack, rest) AS ( 13 | SELECT 0, '', (SELECT s || char(10) FROM input) 14 | UNION ALL 15 | SELECT 16 | CASE 17 | WHEN ( 18 | SUBSTR(nn.rest, 1, 1) IN (')', ']', '}', '>') AND 19 | SUBSTR(nn.rest, 1, 1) != SUBSTR(nn.stack, -1, 1) 20 | ) 21 | THEN ( 22 | nn.total + 23 | (SELECT n FROM braces WHERE v = SUBSTR(nn.rest, 1, 1)) 24 | ) 25 | ELSE nn.total 26 | END, 27 | CASE 28 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN '' 29 | WHEN SUBSTR(nn.rest, 1, 1) IN ('(', '[', '{', '<') 30 | THEN ( 31 | nn.stack || 32 | (SELECT v FROM braces WHERE k = SUBSTR(nn.rest, 1, 1)) 33 | ) 34 | WHEN SUBSTR(nn.rest, 1, 1) = SUBSTR(nn.stack, -1, 1) 35 | THEN SUBSTR(nn.stack, 1, LENGTH(nn.stack) - 1) 36 | WHEN SUBSTR(nn.rest, 1, 1) != SUBSTR(nn.stack, -1, 1) 37 | THEN '' 38 | ELSE 'WAT?' 39 | END, 40 | CASE 41 | WHEN ( 42 | SUBSTR(nn.rest, 1, 1) IN (')', ']', '}', '>') AND 43 | SUBSTR(nn.rest, 1, 1) != SUBSTR(nn.stack, -1, 1) 44 | ) 45 | THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 46 | ELSE SUBSTR(nn.rest, 2) 47 | END 48 | FROM nn WHERE nn.rest != '' 49 | ) 50 | SELECT MAX(total) FROM nn; 51 | -------------------------------------------------------------------------------- /day10/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | import statistics 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | POINTS = { 14 | ')': 1, 15 | ']': 2, 16 | '}': 3, 17 | '>': 4, 18 | } 19 | FORWARD = {'{': '}', '(': ')', '[': ']', '<': '>'} 20 | REVERSE = {v: k for k, v in FORWARD.items()} 21 | 22 | 23 | def compute(s: str) -> int: 24 | scores = [] 25 | lines = s.splitlines() 26 | for line in lines: 27 | stack = [] 28 | for c in line: 29 | if c in FORWARD: 30 | stack.append(c) 31 | elif c in REVERSE: 32 | if stack[-1] != REVERSE[c]: 33 | break 34 | else: 35 | stack.pop() 36 | else: 37 | score = 0 38 | for c in reversed(stack): 39 | score *= 5 40 | score += POINTS[FORWARD[c]] 41 | scores.append(score) 42 | 43 | return int(statistics.median(scores)) 44 | 45 | 46 | INPUT_S = '''\ 47 | [({(<(())[]>[[{[]{<()<>> 48 | [(()[<>])]({[<{<<[]>>( 49 | {([(<{}[<>[]}>{[]{[(<()> 50 | (((({<>}<{<{<>}{[]{[]{} 51 | [[<[([]))<([[{}[[()]]] 52 | [{[{({}]{}}([{[{{{}}([] 53 | {<[[]]>}<{[{[{[]{()[[[] 54 | [<(<(<(<{}))><([]([]() 55 | <{([([[(<>()){}]>(<<{{ 56 | <{([{{}}[<[[[<>{}]]]>[]] 57 | ''' 58 | EXPECTED = 288957 59 | 60 | 61 | @pytest.mark.parametrize( 62 | ('input_s', 'expected'), 63 | ( 64 | (INPUT_S, EXPECTED), 65 | ), 66 | ) 67 | def test(input_s: str, expected: int) -> None: 68 | assert compute(input_s) == expected 69 | 70 | 71 | def main() -> int: 72 | parser = argparse.ArgumentParser() 73 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 74 | args = parser.parse_args() 75 | 76 | with open(args.data_file) as f, support.timing(): 77 | print(compute(f.read())) 78 | 79 | return 0 80 | 81 | 82 | if __name__ == '__main__': 83 | raise SystemExit(main()) 84 | -------------------------------------------------------------------------------- /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 braces (k, v, n); 6 | INSERT INTO braces VALUES 7 | ('(', ')', 1), 8 | ('[', ']', 2), 9 | ('{', '}', 3), 10 | ('<', '>', 4), 11 | ('x', 'x', 0); 12 | 13 | CREATE TABLE leftovers (n INT); 14 | WITH RECURSIVE nn (s, stack, rest) AS ( 15 | SELECT '', '', (SELECT s || char(10) FROM input) 16 | UNION ALL 17 | SELECT 18 | CASE 19 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) 20 | THEN ( 21 | nn.stack || 22 | SUBSTR('xxxxxxxxxxxxxxx', 1, 15 - LENGTH(nn.stack)) 23 | ) 24 | ELSE '' 25 | END, 26 | CASE 27 | WHEN SUBSTR(nn.rest, 1, 1) = char(10) THEN '' 28 | WHEN SUBSTR(nn.rest, 1, 1) IN ('(', '[', '{', '<') 29 | THEN ( 30 | nn.stack || 31 | (SELECT v FROM braces WHERE k = SUBSTR(nn.rest, 1, 1)) 32 | ) 33 | WHEN SUBSTR(nn.rest, 1, 1) = SUBSTR(nn.stack, -1, 1) 34 | THEN SUBSTR(nn.stack, 1, LENGTH(nn.stack) - 1) 35 | WHEN SUBSTR(nn.rest, 1, 1) != SUBSTR(nn.stack, -1, 1) 36 | THEN '' 37 | ELSE 'WAT?' 38 | END, 39 | CASE 40 | WHEN ( 41 | SUBSTR(nn.rest, 1, 1) IN (')', ']', '}', '>') AND 42 | SUBSTR(nn.rest, 1, 1) != SUBSTR(nn.stack, -1, 1) 43 | ) 44 | THEN SUBSTR(nn.rest, INSTR(nn.rest, char(10)) + 1) 45 | ELSE SUBSTR(nn.rest, 2) 46 | END 47 | FROM nn WHERE nn.rest != '' 48 | ) 49 | INSERT INTO leftovers 50 | SELECT t.n FROM ( 51 | SELECT ( 52 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -15, 1)) * 1 + 53 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -14, 1)) * 5 + 54 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -13, 1)) * 25 + 55 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -12, 1)) * 125 + 56 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -11, 1)) * 625 + 57 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -10, 1)) * 3125 + 58 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -9, 1)) * 15625 + 59 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -8, 1)) * 78125 + 60 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -7, 1)) * 390625 + 61 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -6, 1)) * 1953125 + 62 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -5, 1)) * 9765625 + 63 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -4, 1)) * 48828125 + 64 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -3, 1)) * 244140625 + 65 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -2, 1)) * 1220703125 + 66 | (SELECT n FROM braces WHERE v = SUBSTR(nn.s, -1, 1)) * 6103515625 67 | ) AS n 68 | FROM nn WHERE nn.s != '' 69 | ) AS t 70 | ORDER BY t.n ASC; 71 | 72 | SELECT leftovers.n FROM leftovers 73 | WHERE ROWID = (SELECT COUNT(1) / 2 + 1 FROM leftovers); 74 | -------------------------------------------------------------------------------- /day11/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day11/__init__.py -------------------------------------------------------------------------------- /day11/input.txt: -------------------------------------------------------------------------------- 1 | 5251578181 2 | 6158452313 3 | 1818578571 4 | 3844615143 5 | 6857251244 6 | 2375817613 7 | 8883514435 8 | 2321265735 9 | 2857275182 10 | 4821156644 11 | -------------------------------------------------------------------------------- /day11/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | coords = support.parse_coords_int(s) 15 | 16 | flashes = 0 17 | for _ in range(100): 18 | todo = [] 19 | for pt in coords: 20 | coords[pt] += 1 21 | if coords[pt] > 9: 22 | todo.append(pt) 23 | 24 | while todo: 25 | flashing = todo.pop() 26 | if coords[flashing] == 0: 27 | continue 28 | coords[flashing] = 0 29 | flashes += 1 30 | for pt in support.adjacent_8(*flashing): 31 | if pt in coords and coords[pt] != 0: 32 | coords[pt] += 1 33 | if coords[pt] > 9: 34 | todo.append(pt) 35 | 36 | return flashes 37 | 38 | 39 | INPUT_S = '''\ 40 | 5483143223 41 | 2745854711 42 | 5264556173 43 | 6141336146 44 | 6357385478 45 | 4167524645 46 | 2176841721 47 | 6882881134 48 | 4846848554 49 | 5283751526 50 | ''' 51 | EXPECTED = 1656 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 | -------------------------------------------------------------------------------- /day11/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 | 16 | step = 0 17 | while True: 18 | step += 1 19 | todo = [] 20 | for pt in coords: 21 | coords[pt] += 1 22 | if coords[pt] > 9: 23 | todo.append(pt) 24 | 25 | while todo: 26 | flashing = todo.pop() 27 | if coords[flashing] == 0: 28 | continue 29 | coords[flashing] = 0 30 | for pt in support.adjacent_8(*flashing): 31 | if pt in coords and coords[pt] != 0: 32 | coords[pt] += 1 33 | if coords[pt] > 9: 34 | todo.append(pt) 35 | 36 | if all(val == 0 for val in coords.values()): 37 | return step 38 | 39 | raise AssertionError('unreachable') 40 | 41 | 42 | INPUT_S = '''\ 43 | 5483143223 44 | 2745854711 45 | 5264556173 46 | 6141336146 47 | 6357385478 48 | 4167524645 49 | 2176841721 50 | 6882881134 51 | 4846848554 52 | 5283751526 53 | ''' 54 | EXPECTED = 195 55 | 56 | 57 | @pytest.mark.parametrize( 58 | ('input_s', 'expected'), 59 | ( 60 | (INPUT_S, EXPECTED), 61 | ), 62 | ) 63 | def test(input_s: str, expected: int) -> None: 64 | assert compute(input_s) == expected 65 | 66 | 67 | def main() -> int: 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 70 | args = parser.parse_args() 71 | 72 | with open(args.data_file) as f, support.timing(): 73 | print(compute(f.read())) 74 | 75 | return 0 76 | 77 | 78 | if __name__ == '__main__': 79 | raise SystemExit(main()) 80 | -------------------------------------------------------------------------------- /day12/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day12/__init__.py -------------------------------------------------------------------------------- /day12/input.txt: -------------------------------------------------------------------------------- 1 | fw-ll 2 | end-dy 3 | tx-fw 4 | tx-tr 5 | dy-jb 6 | ZD-dy 7 | dy-BL 8 | dy-tr 9 | dy-KX 10 | KX-start 11 | KX-tx 12 | fw-ZD 13 | tr-end 14 | fw-jb 15 | fw-yi 16 | ZD-nr 17 | start-fw 18 | tx-ll 19 | ll-jb 20 | yi-jb 21 | yi-ll 22 | yi-start 23 | ZD-end 24 | ZD-jb 25 | tx-ZD 26 | -------------------------------------------------------------------------------- /day12/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 | edges = collections.defaultdict(set) 16 | for line in s.splitlines(): 17 | src, dst = line.split('-') 18 | edges[src].add(dst) 19 | edges[dst].add(src) 20 | 21 | done = set() 22 | 23 | todo: list[tuple[str, ...]] = [('start',)] 24 | while todo: 25 | path = todo.pop() 26 | if path[-1] == 'end': 27 | done.add(path) 28 | continue 29 | 30 | for choice in edges[path[-1]]: 31 | if choice.isupper() or choice not in path: 32 | todo.append((*path, choice)) 33 | 34 | return len(done) 35 | 36 | 37 | INPUT_S = '''\ 38 | start-A 39 | start-b 40 | A-c 41 | A-b 42 | b-d 43 | A-end 44 | b-end 45 | ''' 46 | EXPECTED = 10 47 | INPUT_2 = '''\ 48 | dc-end 49 | HN-start 50 | start-kj 51 | dc-start 52 | dc-HN 53 | LN-dc 54 | HN-end 55 | kj-sa 56 | kj-HN 57 | kj-dc 58 | ''' 59 | 60 | 61 | @pytest.mark.parametrize( 62 | ('input_s', 'expected'), 63 | ( 64 | (INPUT_S, EXPECTED), 65 | (INPUT_2, 19), 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.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 | edges = collections.defaultdict(set) 16 | for line in s.splitlines(): 17 | src, dst = line.split('-') 18 | edges[src].add(dst) 19 | edges[dst].add(src) 20 | 21 | done = set() 22 | 23 | todo: list[tuple[tuple[str, ...], bool]] = [(('start',), False)] 24 | while todo: 25 | path, double_small = todo.pop() 26 | if path[-1] == 'end': 27 | done.add(path) 28 | continue 29 | 30 | for choice in edges[path[-1]] - {'start'}: 31 | if choice.isupper(): 32 | todo.append(((*path, choice), double_small)) 33 | elif double_small is False and path.count(choice) == 1: 34 | todo.append(((*path, choice), True)) 35 | elif choice not in path: 36 | todo.append(((*path, choice), double_small)) 37 | 38 | return len(done) 39 | 40 | 41 | INPUT_S = '''\ 42 | start-A 43 | start-b 44 | A-c 45 | A-b 46 | b-d 47 | A-end 48 | b-end 49 | ''' 50 | EXPECTED = 36 51 | INPUT_2 = '''\ 52 | dc-end 53 | HN-start 54 | start-kj 55 | dc-start 56 | dc-HN 57 | LN-dc 58 | HN-end 59 | kj-sa 60 | kj-HN 61 | kj-dc 62 | ''' 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ('input_s', 'expected'), 67 | ( 68 | (INPUT_S, EXPECTED), 69 | (INPUT_2, 103), 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 | -------------------------------------------------------------------------------- /day13/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day13/__init__.py -------------------------------------------------------------------------------- /day13/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 | 16 | points, instructions = s.split('\n\n') 17 | 18 | for line in points.splitlines(): 19 | x, y = support.parse_numbers_comma(line) 20 | coords.add((x, y)) 21 | 22 | for line in instructions.splitlines(): 23 | start, end = line.split('=') 24 | direction = start[-1] 25 | value = int(end) 26 | 27 | if direction == 'x': 28 | coords = { 29 | ( 30 | x if x < value else value - (x - value), 31 | y 32 | ) 33 | for x, y in coords 34 | } 35 | else: 36 | coords = { 37 | ( 38 | x, 39 | y if y < value else value - (y - value), 40 | ) 41 | for x, y in coords 42 | } 43 | 44 | break # remove for part 2 45 | 46 | return len(coords) 47 | 48 | 49 | INPUT_S = '''\ 50 | 6,10 51 | 0,14 52 | 9,10 53 | 0,3 54 | 10,4 55 | 4,11 56 | 6,0 57 | 6,12 58 | 4,1 59 | 0,13 60 | 10,12 61 | 3,4 62 | 3,0 63 | 8,4 64 | 1,10 65 | 2,14 66 | 8,10 67 | 9,0 68 | 69 | fold along y=7 70 | fold along x=5 71 | ''' 72 | EXPECTED = 17 73 | 74 | 75 | @pytest.mark.parametrize( 76 | ('input_s', 'expected'), 77 | ( 78 | (INPUT_S, EXPECTED), 79 | ), 80 | ) 81 | def test(input_s: str, expected: int) -> None: 82 | assert compute(input_s) == expected 83 | 84 | 85 | def main() -> int: 86 | parser = argparse.ArgumentParser() 87 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 88 | args = parser.parse_args() 89 | 90 | with open(args.data_file) as f, support.timing(): 91 | print(compute(f.read())) 92 | 93 | return 0 94 | 95 | 96 | if __name__ == '__main__': 97 | raise SystemExit(main()) 98 | -------------------------------------------------------------------------------- /day13/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 | coords = set() 15 | 16 | points, instructions = s.split('\n\n') 17 | 18 | for line in points.splitlines(): 19 | x, y = support.parse_numbers_comma(line) 20 | coords.add((x, y)) 21 | 22 | for line in instructions.splitlines(): 23 | start, end = line.split('=') 24 | direction = start[-1] 25 | value = int(end) 26 | 27 | if direction == 'x': 28 | coords = { 29 | ( 30 | x if x < value else value - (x - value), 31 | y 32 | ) 33 | for x, y in coords 34 | } 35 | else: 36 | coords = { 37 | ( 38 | x, 39 | y if y < value else value - (y - value), 40 | ) 41 | for x, y in coords 42 | } 43 | 44 | return support.format_coords_hash(coords) 45 | 46 | 47 | INPUT_S = '''\ 48 | 6,10 49 | 0,14 50 | 9,10 51 | 0,3 52 | 10,4 53 | 4,11 54 | 6,0 55 | 6,12 56 | 4,1 57 | 0,13 58 | 10,12 59 | 3,4 60 | 3,0 61 | 8,4 62 | 1,10 63 | 2,14 64 | 8,10 65 | 9,0 66 | 67 | fold along y=7 68 | fold along x=5 69 | ''' 70 | EXPECTED_S = '''\ 71 | ##### 72 | # # 73 | # # 74 | # # 75 | ##### 76 | ''' 77 | 78 | 79 | @pytest.mark.parametrize( 80 | ('input_s', 'expected'), 81 | ( 82 | (INPUT_S, EXPECTED_S.rstrip()), 83 | ), 84 | ) 85 | def test(input_s: str, expected: str) -> None: 86 | assert compute(input_s) == expected 87 | 88 | 89 | def main() -> int: 90 | parser = argparse.ArgumentParser() 91 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 92 | args = parser.parse_args() 93 | 94 | with open(args.data_file) as f, support.timing(): 95 | print(compute(f.read())) 96 | 97 | return 0 98 | 99 | 100 | if __name__ == '__main__': 101 | raise SystemExit(main()) 102 | -------------------------------------------------------------------------------- /day14/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day14/__init__.py -------------------------------------------------------------------------------- /day14/input.txt: -------------------------------------------------------------------------------- 1 | VOKKVSKKPSBVOOKVCFOV 2 | 3 | PK -> P 4 | BB -> V 5 | SO -> O 6 | OO -> V 7 | PV -> O 8 | CB -> H 9 | FH -> F 10 | SC -> F 11 | KF -> C 12 | VS -> O 13 | VP -> V 14 | FS -> K 15 | SP -> C 16 | FC -> N 17 | CF -> C 18 | BF -> V 19 | FN -> K 20 | NH -> F 21 | OB -> F 22 | SV -> H 23 | BN -> N 24 | OK -> K 25 | NF -> S 26 | OH -> S 27 | FV -> B 28 | OC -> F 29 | VF -> V 30 | HO -> H 31 | PS -> N 32 | NB -> N 33 | NS -> B 34 | OS -> P 35 | CS -> S 36 | CH -> N 37 | PC -> N 38 | BH -> F 39 | HP -> P 40 | HH -> V 41 | BK -> H 42 | HC -> B 43 | NK -> S 44 | SB -> C 45 | NO -> K 46 | SN -> H 47 | VV -> N 48 | ON -> P 49 | VN -> H 50 | VB -> P 51 | BV -> O 52 | CV -> N 53 | HV -> C 54 | SH -> C 55 | KV -> F 56 | BC -> O 57 | OF -> P 58 | NN -> C 59 | KN -> F 60 | CO -> C 61 | HN -> P 62 | PP -> V 63 | FP -> O 64 | CP -> S 65 | FB -> F 66 | CN -> S 67 | VC -> C 68 | PF -> F 69 | PO -> B 70 | KB -> H 71 | HF -> P 72 | SK -> P 73 | SF -> H 74 | VO -> N 75 | HK -> C 76 | HB -> C 77 | OP -> B 78 | SS -> V 79 | NV -> O 80 | KS -> N 81 | PH -> H 82 | KK -> B 83 | HS -> S 84 | PN -> F 85 | OV -> S 86 | PB -> S 87 | NC -> B 88 | BS -> N 89 | KP -> C 90 | FO -> B 91 | FK -> N 92 | BP -> C 93 | NP -> C 94 | KO -> C 95 | VK -> K 96 | FF -> C 97 | VH -> H 98 | CC -> F 99 | BO -> S 100 | KH -> B 101 | CK -> K 102 | KC -> C 103 | -------------------------------------------------------------------------------- /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 | s, repls = s.split('\n\n') 15 | 16 | patterns = {} 17 | for line in repls.splitlines(): 18 | in_p, ins = line.split(' -> ') 19 | patterns[in_p] = ins 20 | 21 | for i in range(10): 22 | next_s: list[str] = [] 23 | for i, c in enumerate(s): 24 | cand = s[i:i + 2] 25 | if cand in patterns: 26 | next_s.extend((c, patterns[cand])) 27 | else: 28 | next_s.append(c) 29 | s = ''.join(next_s) 30 | 31 | counts = { 32 | k: s.count(k) 33 | for k in set(patterns.values()) 34 | } 35 | s_counts = sorted(v for k, v in counts.items()) 36 | return s_counts[-1] - s_counts[0] 37 | 38 | 39 | INPUT_S = '''\ 40 | NNCB 41 | 42 | CH -> B 43 | HH -> N 44 | CB -> H 45 | NH -> C 46 | HB -> C 47 | HC -> B 48 | HN -> C 49 | NN -> C 50 | BH -> H 51 | NC -> B 52 | NB -> B 53 | BN -> B 54 | BB -> N 55 | BC -> B 56 | CC -> N 57 | CN -> C 58 | ''' 59 | EXPECTED = 1588 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 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 | s, repls = s.split('\n\n') 16 | 17 | counts: collections.Counter[str] = collections.Counter() 18 | for i in range(0, len(s) - 1): 19 | counts[s[i:i + 2]] += 1 20 | 21 | patterns = {} 22 | for line in repls.splitlines(): 23 | in_p, ins = line.split(' -> ') 24 | patterns[in_p] = ins 25 | 26 | for _ in range(40): 27 | counts2: collections.Counter[str] = collections.Counter() 28 | new_counts: collections.Counter[str] = collections.Counter() 29 | for k, v in counts.items(): 30 | new_counts[f'{k[0]}{patterns[k]}'] += v 31 | new_counts[f'{patterns[k]}{k[1]}'] += v 32 | counts2[k[0]] += v 33 | counts2[patterns[k]] += v 34 | counts = new_counts 35 | 36 | counts2[s[-1]] += 1 37 | 38 | s_counts = sorted(v for v in counts2.values()) 39 | return (s_counts[-1] - s_counts[0]) 40 | 41 | 42 | INPUT_S = '''\ 43 | NNCB 44 | 45 | CH -> B 46 | HH -> N 47 | CB -> H 48 | NH -> C 49 | HB -> C 50 | HC -> B 51 | HN -> C 52 | NN -> C 53 | BH -> H 54 | NC -> B 55 | NB -> B 56 | BN -> B 57 | BB -> N 58 | BC -> B 59 | CC -> N 60 | CN -> C 61 | ''' 62 | EXPECTED = 2188189693529 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ('input_s', 'expected'), 67 | ( 68 | (INPUT_S, EXPECTED), 69 | ), 70 | ) 71 | def test(input_s: str, expected: int) -> None: 72 | assert compute(input_s) == expected 73 | 74 | 75 | def main() -> int: 76 | parser = argparse.ArgumentParser() 77 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 78 | args = parser.parse_args() 79 | 80 | with open(args.data_file) as f, support.timing(): 81 | print(compute(f.read())) 82 | 83 | return 0 84 | 85 | 86 | if __name__ == '__main__': 87 | raise SystemExit(main()) 88 | -------------------------------------------------------------------------------- /day15/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day15/__init__.py -------------------------------------------------------------------------------- /day15/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import heapq 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def compute(s: str) -> int: 15 | coords = support.parse_coords_int(s) 16 | 17 | end = max(coords) 18 | 19 | seen = set() 20 | todo = [(0, (0, 0))] 21 | while todo: 22 | cost, last_coord = heapq.heappop(todo) 23 | 24 | if last_coord == end: 25 | return cost 26 | elif last_coord in seen: 27 | continue 28 | else: 29 | seen.add(last_coord) 30 | 31 | for cand in support.adjacent_4(*last_coord): 32 | if cand in coords: 33 | heapq.heappush(todo, (cost + coords[cand], cand)) 34 | 35 | raise AssertionError('unreachable') 36 | 37 | 38 | INPUT_S = '''\ 39 | 1163751742 40 | 1381373672 41 | 2136511328 42 | 3694931569 43 | 7463417111 44 | 1319128137 45 | 1359912421 46 | 3125421639 47 | 1293138521 48 | 2311944581 49 | ''' 50 | EXPECTED = 40 51 | 52 | 53 | @pytest.mark.parametrize( 54 | ('input_s', 'expected'), 55 | ( 56 | (INPUT_S, EXPECTED), 57 | ), 58 | ) 59 | def test(input_s: str, expected: int) -> None: 60 | assert compute(input_s) == expected 61 | 62 | 63 | def main() -> int: 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 66 | args = parser.parse_args() 67 | 68 | with open(args.data_file) as f, support.timing(): 69 | print(compute(f.read())) 70 | 71 | return 0 72 | 73 | 74 | if __name__ == '__main__': 75 | raise SystemExit(main()) 76 | -------------------------------------------------------------------------------- /day15/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 | 14 | def weird_mod(n: int) -> int: 15 | while n > 9: 16 | n -= 9 17 | return n 18 | 19 | 20 | def compute(s: str) -> int: 21 | coords = support.parse_coords_int(s) 22 | width, height = max(coords) 23 | width, height = width + 1, height + 1 24 | 25 | coords = { 26 | (x_i * width + x, y_i * height + y): weird_mod(n + x_i + y_i) 27 | for (x, y), n in tuple(coords.items()) 28 | for y_i in range(5) 29 | for x_i in range(5) 30 | } 31 | 32 | end = max(coords) 33 | 34 | seen = set() 35 | todo = [(0, (0, 0))] 36 | while todo: 37 | cost, last_coord = heapq.heappop(todo) 38 | 39 | if last_coord == end: 40 | return cost 41 | elif last_coord in seen: 42 | continue 43 | else: 44 | seen.add(last_coord) 45 | 46 | for cand in support.adjacent_4(*last_coord): 47 | if cand in coords: 48 | heapq.heappush(todo, (cost + coords[cand], cand)) 49 | 50 | raise AssertionError('unreachable') 51 | 52 | 53 | INPUT_S = '''\ 54 | 1163751742 55 | 1381373672 56 | 2136511328 57 | 3694931569 58 | 7463417111 59 | 1319128137 60 | 1359912421 61 | 3125421639 62 | 1293138521 63 | 2311944581 64 | ''' 65 | EXPECTED = 315 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 | -------------------------------------------------------------------------------- /day16/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day16/__init__.py -------------------------------------------------------------------------------- /day16/input.txt: -------------------------------------------------------------------------------- 1 | 020D790050D26C13EC1348326D336ACE00EC299E6A8B929ED59C880502E00A526B969F62BF35CB4FB15B93A6311F67F813300470037500043E2D4218FA000864538E905A39CAF77386E35AB01802FC01BA00021118617C1F00043A3F4748A53CF66008D00481272D73308334EDB0ED304E200D4E94CF612A49B40036C98A7CF24A53CA94C6370FBDCC9018029600ACD529CA9A4F62ACD2B5F928802F0D2665CA7D6CC013919E78A3800D3CF7794A8FC938280473057394AFF15099BA23CDD37A08400E2A5F7297F916C9F97F82D2DFA734BC600D4E3BC89CCBABCBE2B77D200412599244D4C0138C780120CC67E9D351C5AB4E1D4C981802980080CDB84E034C5767C60124F3BC984CD1E479201232C016100662D45089A00087C1084F12A724752BEFEA9C51500566759BF9BE6C5080217910CD00525B6350E8C00E9272200DCE4EF4C1DD003952F7059BCF675443005680103976997699795E830C02E4CBCE72EFC6A6218C88C9DF2F3351FCEF2D83CADB779F59A052801F2BAACDAE7F52A8190073937FE1D700439234DBB4F7374DC0CC804CF1006A0D47B8A4200F445865170401F8251662D100909401AB8803313217C680004320D43F871308D140C010E0069E7EDD1796AFC8255800052E20043E0F42A8B6400864258E51088010B85910A0F4ECE1DFE069C0229AE63D0B8DC6F82529403203305C00E1002C80AF5602908400A20240100852401E98400830021400D30029004B6100294008400B9D0023240061C000D19CACCD9005F694AEF6493D3F9948DEB3B4CC273FFD5E9AD85CFDFF6978B80050392AC3D98D796449BE304FE7F0C13CD716656BD0A6002A67E61A400F6E8029300B300B11480463D004C401889B1CA31800211162204679621200FCAC01791CF6B1AFCF2473DAC6BF3A9F1700016A3D90064D359B35D003430727A7DC464E6401594A57C93A0084CC56A662B8C00AA424989F2A9112 2 | -------------------------------------------------------------------------------- /day16/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | from typing import Protocol 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 _Packet(Protocol): 16 | @property 17 | def version(self) -> int: ... 18 | @property 19 | def type_id(self) -> int: ... 20 | @property 21 | def n(self) -> int: ... 22 | @property 23 | def packets(self) -> tuple[_Packet, ...]: ... 24 | 25 | 26 | class Packet(NamedTuple): 27 | version: int 28 | type_id: int 29 | n: int = -1 30 | packets: tuple[_Packet, ...] = () 31 | 32 | 33 | def compute(s: str) -> int: 34 | bin_str = '' 35 | for c in s.strip(): 36 | bin_str += f'{int(c, 16):04b}' 37 | 38 | def parse_packet(i: int) -> tuple[int, _Packet]: 39 | def _read(n: int) -> int: 40 | nonlocal i 41 | ret = int(bin_str[i:i + n], 2) 42 | i += n 43 | return ret 44 | 45 | version = _read(3) 46 | type_id = _read(3) 47 | 48 | if type_id == 4: 49 | chunk = _read(5) 50 | n = chunk & 0b1111 51 | while chunk & 0b10000: 52 | chunk = _read(5) 53 | n <<= 4 54 | n += chunk & 0b1111 55 | 56 | return i, Packet(version=version, type_id=type_id, n=n) 57 | else: 58 | mode = _read(1) 59 | 60 | if mode == 0: 61 | bits_length = _read(15) 62 | j = i 63 | i = i + bits_length 64 | packets = [] 65 | while j < i: 66 | j, packet = parse_packet(j) 67 | packets.append(packet) 68 | 69 | ret = Packet( 70 | version=version, 71 | type_id=type_id, 72 | packets=tuple(packets), 73 | ) 74 | return i, ret 75 | else: 76 | sub_packets = _read(11) 77 | packets = [] 78 | for _ in range(sub_packets): 79 | i, packet = parse_packet(i) 80 | packets.append(packet) 81 | ret = Packet( 82 | version=version, 83 | type_id=type_id, 84 | packets=tuple(packets), 85 | ) 86 | return i, ret 87 | 88 | _, packet = parse_packet(0) 89 | todo = [packet] 90 | total = 0 91 | while todo: 92 | item = todo.pop() 93 | total += item.version 94 | todo.extend(item.packets) 95 | 96 | return total 97 | 98 | 99 | INPUT_S = '''\ 100 | 8A004A801A8002F478 101 | ''' 102 | EXPECTED = 16 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 | -------------------------------------------------------------------------------- /day16/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | from typing import Protocol 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 _Packet(Protocol): 16 | @property 17 | def version(self) -> int: ... 18 | @property 19 | def type_id(self) -> int: ... 20 | @property 21 | def n(self) -> int: ... 22 | @property 23 | def packets(self) -> tuple[_Packet, ...]: ... 24 | 25 | 26 | class Packet(NamedTuple): 27 | version: int 28 | type_id: int 29 | n: int = -1 30 | packets: tuple[_Packet, ...] = () 31 | 32 | 33 | def compute(s: str) -> int: 34 | bin_str = '' 35 | for c in s.strip(): 36 | bin_str += f'{int(c, 16):04b}' 37 | 38 | def parse_packet(i: int) -> tuple[int, _Packet]: 39 | def _read(n: int) -> int: 40 | nonlocal i 41 | ret = int(bin_str[i:i + n], 2) 42 | i += n 43 | return ret 44 | 45 | version = _read(3) 46 | type_id = _read(3) 47 | 48 | if type_id == 4: 49 | chunk = _read(5) 50 | n = chunk & 0b1111 51 | while chunk & 0b10000: 52 | chunk = _read(5) 53 | n <<= 4 54 | n += chunk & 0b1111 55 | 56 | return i, Packet(version=version, type_id=type_id, n=n) 57 | else: 58 | mode = _read(1) 59 | 60 | if mode == 0: 61 | bits_length = _read(15) 62 | j = i 63 | i = i + bits_length 64 | packets = [] 65 | while j < i: 66 | j, packet = parse_packet(j) 67 | packets.append(packet) 68 | 69 | ret = Packet( 70 | version=version, 71 | type_id=type_id, 72 | packets=tuple(packets), 73 | ) 74 | return i, ret 75 | else: 76 | sub_packets = _read(11) 77 | packets = [] 78 | for _ in range(sub_packets): 79 | i, packet = parse_packet(i) 80 | packets.append(packet) 81 | ret = Packet( 82 | version=version, 83 | type_id=type_id, 84 | packets=tuple(packets), 85 | ) 86 | return i, ret 87 | 88 | def val(packet: _Packet) -> int: 89 | if packet.type_id == 0: 90 | return sum(val(sub_packet) for sub_packet in packet.packets) 91 | elif packet.type_id == 1: 92 | res = 1 93 | for sub_packet in packet.packets: 94 | res *= val(sub_packet) 95 | return res 96 | elif packet.type_id == 2: 97 | return min(val(sub_packet) for sub_packet in packet.packets) 98 | elif packet.type_id == 3: 99 | return max(val(sub_packet) for sub_packet in packet.packets) 100 | elif packet.type_id == 4: 101 | return packet.n 102 | elif packet.type_id == 5: 103 | return val(packet.packets[0]) > val(packet.packets[1]) 104 | elif packet.type_id == 6: 105 | return val(packet.packets[0]) < val(packet.packets[1]) 106 | elif packet.type_id == 7: 107 | return val(packet.packets[0]) == val(packet.packets[1]) 108 | else: 109 | raise AssertionError(packet) 110 | 111 | _, packet = parse_packet(0) 112 | return val(packet) 113 | 114 | 115 | INPUT_S = '''\ 116 | C200B40A82 117 | ''' 118 | EXPECTED = 3 119 | 120 | 121 | @pytest.mark.parametrize( 122 | ('input_s', 'expected'), 123 | ( 124 | (INPUT_S, EXPECTED), 125 | ), 126 | ) 127 | def test(input_s: str, expected: int) -> None: 128 | assert compute(input_s) == expected 129 | 130 | 131 | def main() -> int: 132 | parser = argparse.ArgumentParser() 133 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 134 | args = parser.parse_args() 135 | 136 | with open(args.data_file) as f, support.timing(): 137 | print(compute(f.read())) 138 | 139 | return 0 140 | 141 | 142 | if __name__ == '__main__': 143 | raise SystemExit(main()) 144 | -------------------------------------------------------------------------------- /day17/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day17/__init__.py -------------------------------------------------------------------------------- /day17/input.txt: -------------------------------------------------------------------------------- 1 | target area: x=155..215, y=-132..-72 2 | -------------------------------------------------------------------------------- /day17/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | _, _, x_s, y_s = s.split() 15 | x_s = x_s[2:-1] 16 | y_s = y_s[2:] 17 | 18 | y1_s, _ = y_s.split('..') 19 | y1 = int(y1_s) 20 | 21 | y0 = abs(y1) - 1 # also t! 22 | 23 | return y0 * y0 - (y0 - 1) * y0 // 2 24 | 25 | 26 | INPUT_S = '''\ 27 | target area: x=20..30, y=-10..-5 28 | ''' 29 | EXPECTED = 45 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 | -------------------------------------------------------------------------------- /day17/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 | _, _, x_s, y_s = s.split() 15 | x_s = x_s[2:-1] 16 | y_s = y_s[2:] 17 | 18 | x1_s, x2_s = x_s.split('..') 19 | y1_s, y2_s = y_s.split('..') 20 | 21 | x1, x2 = int(x1_s), int(x2_s) 22 | y1, y2 = int(y1_s), int(y2_s) 23 | 24 | total = 0 25 | for x in range(1, x2 + 1): 26 | for y in range(y1, abs(y1)): 27 | vx, vy = x, y 28 | x_p = y_p = 0 29 | for _ in range(2 * abs(y1) + 1): 30 | x_p += vx 31 | y_p += vy 32 | vx = max(vx - 1, 0) 33 | vy -= 1 34 | 35 | if y1 <= y_p <= y2 and x1 <= x_p <= x2: 36 | total += 1 37 | break 38 | elif y_p < y1 or x_p > x2: 39 | break 40 | 41 | return total 42 | 43 | 44 | INPUT_S = '''\ 45 | target area: x=20..30, y=-10..-5 46 | ''' 47 | EXPECTED = 112 48 | 49 | 50 | @pytest.mark.parametrize( 51 | ('input_s', 'expected'), 52 | ( 53 | (INPUT_S, EXPECTED), 54 | ), 55 | ) 56 | def test(input_s: str, expected: int) -> None: 57 | assert compute(input_s) == expected 58 | 59 | 60 | def main() -> int: 61 | parser = argparse.ArgumentParser() 62 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 63 | args = parser.parse_args() 64 | 65 | with open(args.data_file) as f, support.timing(): 66 | print(compute(f.read())) 67 | 68 | return 0 69 | 70 | 71 | if __name__ == '__main__': 72 | raise SystemExit(main()) 73 | -------------------------------------------------------------------------------- /day18/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day18/__init__.py -------------------------------------------------------------------------------- /day18/input.txt: -------------------------------------------------------------------------------- 1 | [[[4,[8,0]],[[6,9],[0,3]]],[[[2,9],[6,3]],[1,[9,9]]]] 2 | [[[2,[5,0]],[[5,6],4]],[[[2,0],[4,8]],[7,8]]] 3 | [[[5,[0,6]],[9,6]],4] 4 | [[9,[5,3]],[[4,9],[8,2]]] 5 | [[5,[6,2]],[[9,3],[4,[3,2]]]] 6 | [[[9,[9,8]],[2,9]],[[4,[6,5]],[[6,2],9]]] 7 | [[[9,8],7],[[0,[2,1]],[[9,5],[4,5]]]] 8 | [[4,[0,1]],[[[3,6],5],[4,9]]] 9 | [[0,9],[[3,1],3]] 10 | [[[3,[7,4]],[9,8]],5] 11 | [[6,[2,8]],[0,[[6,9],[8,1]]]] 12 | [[1,[[1,1],[9,4]]],[[3,[2,9]],[7,[5,4]]]] 13 | [[[4,[9,3]],[4,[3,8]]],[[[1,3],[7,1]],5]] 14 | [[[[2,9],8],7],[4,[5,4]]] 15 | [[8,[[1,3],5]],[[6,5],6]] 16 | [[[[5,5],[3,1]],[[6,8],2]],[[8,5],3]] 17 | [[[7,[7,3]],7],0] 18 | [[7,[[1,0],0]],[2,[9,2]]] 19 | [[3,[1,7]],7] 20 | [[2,[6,3]],[[[1,6],7],[9,3]]] 21 | [5,5] 22 | [[[[5,1],[1,8]],[[3,0],2]],[[8,2],[9,0]]] 23 | [[[[4,1],[0,8]],[9,[9,7]]],[[[2,4],3],[8,9]]] 24 | [[3,[8,2]],9] 25 | [[7,1],[[[5,8],[9,9]],2]] 26 | [[[[2,3],[7,7]],[[6,3],7]],[8,[[1,8],[1,5]]]] 27 | [[0,[0,5]],[[[1,5],6],0]] 28 | [8,[[[7,9],[2,9]],6]] 29 | [[5,[3,[8,7]]],[3,4]] 30 | [[[8,2],[3,[5,2]]],4] 31 | [[[[9,6],[3,3]],[3,[1,8]]],[[[6,1],4],[[1,3],2]]] 32 | [[[[5,7],[3,6]],[0,[6,4]]],[[[0,2],8],3]] 33 | [[[2,4],[3,[9,1]]],6] 34 | [[[[9,6],[2,0]],[4,0]],[[5,[0,9]],[[5,3],[6,6]]]] 35 | [[[3,5],9],[[7,[8,1]],[[2,6],[0,6]]]] 36 | [[[9,2],[[3,2],8]],[4,4]] 37 | [[[4,[5,6]],5],[[7,[8,7]],2]] 38 | [[[4,8],[3,[7,1]]],1] 39 | [8,[[1,[9,4]],7]] 40 | [[[[2,3],5],[7,[4,9]]],[[4,8],[[8,1],[3,1]]]] 41 | [[[[4,2],4],[1,[0,7]]],[[1,[4,5]],[9,[3,6]]]] 42 | [[[[7,2],[4,9]],[6,2]],[[6,7],[2,[0,2]]]] 43 | [[4,5],[[[4,1],3],5]] 44 | [[[9,[2,2]],[[0,1],[3,2]]],2] 45 | [[2,[[7,5],3]],[[[1,0],[7,4]],0]] 46 | [6,[[3,[7,2]],[[6,5],[4,7]]]] 47 | [[4,[[5,3],[1,8]]],[[[6,0],3],[2,7]]] 48 | [[[3,[4,3]],[8,1]],[8,[3,[0,7]]]] 49 | [[[[8,5],[0,5]],[[8,0],9]],[[[7,7],[3,0]],[4,[2,7]]]] 50 | [[4,[[2,0],[5,7]]],[8,2]] 51 | [[[[6,3],[8,9]],[[7,5],[4,3]]],[9,[2,4]]] 52 | [9,[[[2,1],[9,7]],[5,[3,8]]]] 53 | [[0,[[6,3],[7,8]]],[[7,[8,2]],3]] 54 | [[7,[[2,4],4]],6] 55 | [[3,[2,8]],[[[1,8],1],0]] 56 | [[1,[[5,0],1]],6] 57 | [[5,[2,[5,4]]],[[[8,5],9],[8,[7,2]]]] 58 | [[[[5,0],[6,1]],0],[[7,9],[2,3]]] 59 | [[[[9,6],[0,0]],[0,[5,2]]],5] 60 | [[[[6,6],6],[0,[9,4]]],[[[0,7],[3,8]],[8,5]]] 61 | [6,8] 62 | [[6,[7,1]],5] 63 | [[[[2,9],1],[[7,6],5]],[[9,2],[[9,5],2]]] 64 | [[2,[6,5]],[2,[0,8]]] 65 | [[[4,9],8],0] 66 | [[3,[[8,4],[3,5]]],[[0,3],[2,8]]] 67 | [[[3,[1,6]],1],[3,[[7,4],4]]] 68 | [[[1,[0,8]],6],[[2,5],[6,[1,2]]]] 69 | [[7,[[5,9],5]],[[7,9],7]] 70 | [[[3,[9,9]],[[5,0],2]],[[8,[6,6]],9]] 71 | [[9,4],[[2,[6,1]],6]] 72 | [[2,[[3,2],5]],[9,8]] 73 | [[[1,[5,7]],4],[9,[[7,2],3]]] 74 | [[[[4,0],[3,9]],[[2,4],[9,4]]],0] 75 | [[[6,5],8],[[[1,7],3],7]] 76 | [[[[5,9],4],6],[[[3,3],[0,4]],[3,[2,2]]]] 77 | [[[[3,5],[7,4]],[[7,2],[3,2]]],1] 78 | [[[0,9],[1,[4,6]]],[3,[[6,9],9]]] 79 | [[[[3,8],4],[8,[5,6]]],[6,[[0,1],8]]] 80 | [[5,[[4,3],5]],[[2,[2,8]],[5,[5,7]]]] 81 | [[[4,[2,7]],0],[[7,6],[[5,8],[4,4]]]] 82 | [[[2,[3,3]],[6,[1,7]]],[[[2,8],[9,1]],[[2,7],[9,2]]]] 83 | [[9,[3,5]],[[[9,4],[1,8]],[[7,2],[9,6]]]] 84 | [[5,[4,[4,0]]],[[5,5],[[8,0],7]]] 85 | [0,[[[1,9],9],[7,[0,3]]]] 86 | [[[[5,3],8],1],[[[7,3],[5,4]],9]] 87 | [[[[4,0],4],[9,[1,9]]],[[[8,9],7],[[5,9],[0,3]]]] 88 | [[[0,8],[[7,2],7]],[[1,[8,4]],[[8,3],2]]] 89 | [[[[2,9],[0,0]],[0,[2,2]]],[6,9]] 90 | [2,[6,2]] 91 | [[[[9,9],[8,1]],5],6] 92 | [[4,1],[[[5,9],[3,2]],[0,[4,0]]]] 93 | [[[[8,9],3],[8,0]],[[[4,6],[2,3]],1]] 94 | [[[0,5],[3,[8,2]]],[3,2]] 95 | [[[[3,3],[1,8]],[[5,8],[2,7]]],[[[1,5],9],[4,2]]] 96 | [[[[7,0],5],2],[1,[[5,1],6]]] 97 | [3,[[9,[9,3]],[1,[2,8]]]] 98 | [[5,[[7,4],[0,3]]],[4,[[4,4],[6,8]]]] 99 | [[[8,7],[5,1]],[[4,5],[7,[3,8]]]] 100 | [[[[4,5],[5,5]],[[2,7],[0,5]]],[[5,[7,0]],[[9,6],5]]] 101 | -------------------------------------------------------------------------------- /day18/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import ast 5 | import math 6 | import os.path 7 | import re 8 | from typing import Any 9 | from typing import Match 10 | 11 | import pytest 12 | 13 | import support 14 | 15 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 16 | 17 | PAIR_RE = re.compile(r'\[(\d+),(\d+)\]') 18 | NUM_LEFT_RE = re.compile(r'\d+(?!.*\d)') 19 | NUM_RE = re.compile(r'\d+') 20 | GT_10 = re.compile(r'\d\d+') 21 | 22 | 23 | def add_number(s1: str, s2: str) -> str: 24 | return f'[{s1},{s2}]' 25 | 26 | 27 | def reduce_number(s: str) -> str: 28 | while True: 29 | continue_outer = False 30 | for pair in PAIR_RE.finditer(s): 31 | before = s[:pair.start()] 32 | if before.count('[') - before.count(']') >= 4: 33 | def left_cb(match: Match[str]) -> str: 34 | return str(int(match[0]) + int(pair[1])) 35 | 36 | def right_cb(match: Match[str]) -> str: 37 | return str(int(match[0]) + int(pair[2])) 38 | 39 | start = NUM_LEFT_RE.sub(left_cb, s[:pair.start()], count=1) 40 | end = NUM_RE.sub(right_cb, s[pair.end():], count=1) 41 | s = f'{start}0{end}' 42 | 43 | continue_outer = True 44 | break 45 | 46 | if continue_outer: 47 | continue 48 | 49 | gt_10_match = GT_10.search(s) 50 | if gt_10_match: 51 | def match_cb(match: Match[str]) -> str: 52 | val = int(match[0]) 53 | return f'[{math.floor(val/2)},{math.ceil(val/2)}]' 54 | 55 | s = GT_10.sub(match_cb, s, count=1) 56 | continue 57 | 58 | return s 59 | 60 | 61 | def compute_sum(s: str) -> int: 62 | def compute_val(v: int | Any) -> int: 63 | if isinstance(v, int): 64 | return v 65 | else: 66 | assert len(v) == 2 67 | return 3 * compute_val(v[0]) + 2 * compute_val(v[1]) 68 | 69 | return compute_val(ast.literal_eval(s)) 70 | 71 | 72 | def compute(s: str) -> int: 73 | lines = s.splitlines() 74 | lines = [reduce_number(line) for line in lines] 75 | 76 | res = lines[0] 77 | for other in lines[1:]: 78 | res = reduce_number(add_number(res, other)) 79 | 80 | res = reduce_number(res) 81 | return compute_sum(res) 82 | 83 | 84 | INPUT_S = '''\ 85 | [[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]] 86 | [[[5,[2,8]],4],[5,[[9,9],0]]] 87 | [6,[[[6,2],[5,6]],[[7,6],[4,7]]]] 88 | [[[6,[0,7]],[0,9]],[4,[9,[9,0]]]] 89 | [[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]] 90 | [[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]] 91 | [[[[5,4],[7,7]],8],[[8,3],8]] 92 | [[9,3],[[9,9],[6,[4,9]]]] 93 | [[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]] 94 | [[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]] 95 | ''' 96 | EXPECTED = 4140 97 | 98 | 99 | @pytest.mark.parametrize( 100 | ('input_s', 'expected'), 101 | ( 102 | ('[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]', 1384), 103 | ('[[1,2],[[3,4],5]]', 143), 104 | ( 105 | '[[[[0,7],4],[[7,8],[6,0]]],[8,1]]', 106 | 1384, 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 | -------------------------------------------------------------------------------- /day18/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import ast 5 | import math 6 | import os.path 7 | import re 8 | from typing import Any 9 | from typing import Match 10 | 11 | import pytest 12 | 13 | import support 14 | 15 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 16 | 17 | PAIR_RE = re.compile(r'\[(\d+),(\d+)\]') 18 | NUM_LEFT_RE = re.compile(r'\d+(?!.*\d)') 19 | NUM_RE = re.compile(r'\d+') 20 | GT_10 = re.compile(r'\d\d+') 21 | 22 | 23 | def add_number(s1: str, s2: str) -> str: 24 | return f'[{s1},{s2}]' 25 | 26 | 27 | def reduce_number(s: str) -> str: 28 | while True: 29 | continue_outer = False 30 | for pair in PAIR_RE.finditer(s): 31 | before = s[:pair.start()] 32 | if before.count('[') - before.count(']') >= 4: 33 | def left_cb(match: Match[str]) -> str: 34 | return str(int(match[0]) + int(pair[1])) 35 | 36 | def right_cb(match: Match[str]) -> str: 37 | return str(int(match[0]) + int(pair[2])) 38 | 39 | start = NUM_LEFT_RE.sub(left_cb, s[:pair.start()], count=1) 40 | end = NUM_RE.sub(right_cb, s[pair.end():], count=1) 41 | s = f'{start}0{end}' 42 | 43 | continue_outer = True 44 | break 45 | 46 | if continue_outer: 47 | continue 48 | 49 | gt_10_match = GT_10.search(s) 50 | if gt_10_match: 51 | def match_cb(match: Match[str]) -> str: 52 | val = int(match[0]) 53 | return f'[{math.floor(val/2)},{math.ceil(val/2)}]' 54 | 55 | s = GT_10.sub(match_cb, s, count=1) 56 | continue 57 | 58 | return s 59 | 60 | 61 | def compute_sum(s: str) -> int: 62 | def compute_val(v: int | Any) -> int: 63 | if isinstance(v, int): 64 | return v 65 | else: 66 | assert len(v) == 2 67 | return 3 * compute_val(v[0]) + 2 * compute_val(v[1]) 68 | 69 | return compute_val(ast.literal_eval(s)) 70 | 71 | 72 | def compute(s: str) -> int: 73 | lines = s.splitlines() 74 | 75 | maximum = 0 76 | for i, line in enumerate(lines): 77 | for other in lines[i + 1:]: 78 | maximum = max( 79 | maximum, 80 | compute_sum(reduce_number(add_number(line, other))), 81 | ) 82 | maximum = max( 83 | maximum, 84 | compute_sum(reduce_number(add_number(other, line))), 85 | ) 86 | 87 | return maximum 88 | 89 | 90 | INPUT_S = '''\ 91 | [[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]] 92 | [[[5,[2,8]],4],[5,[[9,9],0]]] 93 | [6,[[[6,2],[5,6]],[[7,6],[4,7]]]] 94 | [[[6,[0,7]],[0,9]],[4,[9,[9,0]]]] 95 | [[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]] 96 | [[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]] 97 | [[[[5,4],[7,7]],8],[[8,3],8]] 98 | [[9,3],[[9,9],[6,[4,9]]]] 99 | [[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]] 100 | [[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]] 101 | ''' 102 | EXPECTED = 3993 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 | -------------------------------------------------------------------------------- /day19/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day19/__init__.py -------------------------------------------------------------------------------- /day19/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from typing import NamedTuple 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | class Scanner(NamedTuple): 16 | sid: int 17 | points: list[tuple[int, int, int]] 18 | 19 | @classmethod 20 | def from_str(cls, s: str) -> Scanner: 21 | lines = s.splitlines() 22 | _, _, sid_s, _ = lines[0].split() 23 | points = [] 24 | for line in lines[1:]: 25 | x, y, z = support.parse_numbers_comma(line) 26 | points.append((x, y, z)) 27 | return cls(int(sid_s), points) 28 | 29 | 30 | class AxisInfo(NamedTuple): 31 | axis: int 32 | sign: int 33 | diff: int 34 | 35 | 36 | def x_edges_from( 37 | src: Scanner, 38 | scanners_by_id: dict[int, Scanner], 39 | ) -> dict[int, AxisInfo]: 40 | x_edges = {} 41 | for other in scanners_by_id.values(): 42 | for axis in (0, 1, 2): 43 | for sign in (-1, 1): 44 | d_x: collections.Counter[int] = collections.Counter() 45 | for x, _, _ in src.points: 46 | for other_pt in other.points: 47 | d_x[x - other_pt[axis] * sign] += 1 48 | 49 | (x_diff, n), = d_x.most_common(1) 50 | if n >= 12: 51 | x_edges[other.sid] = AxisInfo( 52 | axis=axis, 53 | sign=sign, 54 | diff=x_diff, 55 | ) 56 | return x_edges 57 | 58 | 59 | def yz_edges_from( 60 | src: Scanner, 61 | x_edges: dict[int, AxisInfo], 62 | scanners_by_id: dict[int, Scanner], 63 | ) -> tuple[dict[int, AxisInfo], dict[int, AxisInfo]]: 64 | y_edges = {} 65 | z_edges = {} 66 | 67 | for dst_id in x_edges: 68 | other = scanners_by_id[dst_id] 69 | for axis in (0, 1, 2): 70 | for sign in (-1, 1): 71 | d_y: collections.Counter[int] = collections.Counter() 72 | d_z: collections.Counter[int] = collections.Counter() 73 | for _, y, z in src.points: 74 | for other_pt in other.points: 75 | d_y[y - other_pt[axis] * sign] += 1 76 | d_z[z - other_pt[axis] * sign] += 1 77 | 78 | (y_diff, y_n), = d_y.most_common(1) 79 | if y_n >= 12: 80 | y_edges[dst_id] = AxisInfo( 81 | axis=axis, 82 | sign=sign, 83 | diff=y_diff, 84 | ) 85 | 86 | (z_diff, z_n), = d_z.most_common(1) 87 | if z_n >= 12: 88 | z_edges[dst_id] = AxisInfo( 89 | axis=axis, 90 | sign=sign, 91 | diff=z_diff, 92 | ) 93 | 94 | return y_edges, z_edges 95 | 96 | 97 | def compute(s: str) -> int: 98 | scanners = [Scanner.from_str(part) for part in s.split('\n\n')] 99 | scanners_by_id = {scanner.sid: scanner for scanner in scanners} 100 | scanner_positions = {0: (0, 0, 0)} 101 | all_points = set(scanners_by_id[0].points) 102 | 103 | todo = [scanners_by_id.pop(0)] 104 | while todo: 105 | src = todo.pop() 106 | 107 | x_edges = x_edges_from(src, scanners_by_id) 108 | y_edges, z_edges = yz_edges_from(src, x_edges, scanners_by_id) 109 | 110 | for k in x_edges: 111 | dst_x = x_edges[k].diff 112 | dst_y = y_edges[k].diff 113 | dst_z = z_edges[k].diff 114 | 115 | scanner_positions[k] = (dst_x, dst_y, dst_z) 116 | 117 | next_scanner = scanners_by_id.pop(k) 118 | next_scanner.points[:] = [ 119 | ( 120 | dst_x + x_edges[k].sign * pt[x_edges[k].axis], 121 | dst_y + y_edges[k].sign * pt[y_edges[k].axis], 122 | dst_z + z_edges[k].sign * pt[z_edges[k].axis], 123 | ) 124 | for pt in next_scanner.points 125 | ] 126 | all_points.update(next_scanner.points) 127 | 128 | todo.append(next_scanner) 129 | 130 | return len(all_points) 131 | 132 | 133 | INPUT_S = '''\ 134 | --- scanner 0 --- 135 | 404,-588,-901 136 | 528,-643,409 137 | -838,591,734 138 | 390,-675,-793 139 | -537,-823,-458 140 | -485,-357,347 141 | -345,-311,381 142 | -661,-816,-575 143 | -876,649,763 144 | -618,-824,-621 145 | 553,345,-567 146 | 474,580,667 147 | -447,-329,318 148 | -584,868,-557 149 | 544,-627,-890 150 | 564,392,-477 151 | 455,729,728 152 | -892,524,684 153 | -689,845,-530 154 | 423,-701,434 155 | 7,-33,-71 156 | 630,319,-379 157 | 443,580,662 158 | -789,900,-551 159 | 459,-707,401 160 | 161 | --- scanner 1 --- 162 | 686,422,578 163 | 605,423,415 164 | 515,917,-361 165 | -336,658,858 166 | 95,138,22 167 | -476,619,847 168 | -340,-569,-846 169 | 567,-361,727 170 | -460,603,-452 171 | 669,-402,600 172 | 729,430,532 173 | -500,-761,534 174 | -322,571,750 175 | -466,-666,-811 176 | -429,-592,574 177 | -355,545,-477 178 | 703,-491,-529 179 | -328,-685,520 180 | 413,935,-424 181 | -391,539,-444 182 | 586,-435,557 183 | -364,-763,-893 184 | 807,-499,-711 185 | 755,-354,-619 186 | 553,889,-390 187 | 188 | --- scanner 2 --- 189 | 649,640,665 190 | 682,-795,504 191 | -784,533,-524 192 | -644,584,-595 193 | -588,-843,648 194 | -30,6,44 195 | -674,560,763 196 | 500,723,-460 197 | 609,671,-379 198 | -555,-800,653 199 | -675,-892,-343 200 | 697,-426,-610 201 | 578,704,681 202 | 493,664,-388 203 | -671,-858,530 204 | -667,343,800 205 | 571,-461,-707 206 | -138,-166,112 207 | -889,563,-600 208 | 646,-828,498 209 | 640,759,510 210 | -630,509,768 211 | -681,-892,-333 212 | 673,-379,-804 213 | -742,-814,-386 214 | 577,-820,562 215 | 216 | --- scanner 3 --- 217 | -589,542,597 218 | 605,-692,669 219 | -500,565,-823 220 | -660,373,557 221 | -458,-679,-417 222 | -488,449,543 223 | -626,468,-788 224 | 338,-750,-386 225 | 528,-832,-391 226 | 562,-778,733 227 | -938,-730,414 228 | 543,643,-506 229 | -524,371,-870 230 | 407,773,750 231 | -104,29,83 232 | 378,-903,-323 233 | -778,-728,485 234 | 426,699,580 235 | -438,-605,-362 236 | -469,-447,-387 237 | 509,732,623 238 | 647,635,-688 239 | -868,-804,481 240 | 614,-800,639 241 | 595,780,-596 242 | 243 | --- scanner 4 --- 244 | 727,592,562 245 | -293,-554,779 246 | 441,611,-461 247 | -714,465,-776 248 | -743,427,-804 249 | -660,-479,-426 250 | 832,-632,460 251 | 927,-485,-438 252 | 408,393,-506 253 | 466,436,-512 254 | 110,16,151 255 | -258,-428,682 256 | -393,719,612 257 | -211,-452,876 258 | 808,-476,-593 259 | -575,615,604 260 | -485,667,467 261 | -680,325,-822 262 | -627,-443,-432 263 | 872,-547,-609 264 | 833,512,582 265 | 807,604,487 266 | 839,-516,451 267 | 891,-625,532 268 | -652,-548,-490 269 | 30,-46,-14 270 | ''' 271 | EXPECTED = 79 272 | 273 | 274 | @pytest.mark.parametrize( 275 | ('input_s', 'expected'), 276 | ( 277 | (INPUT_S, EXPECTED), 278 | ), 279 | ) 280 | def test(input_s: str, expected: int) -> None: 281 | assert compute(input_s) == expected 282 | 283 | 284 | def main() -> int: 285 | parser = argparse.ArgumentParser() 286 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 287 | args = parser.parse_args() 288 | 289 | with open(args.data_file) as f, support.timing(): 290 | print(compute(f.read())) 291 | 292 | return 0 293 | 294 | 295 | if __name__ == '__main__': 296 | raise SystemExit(main()) 297 | -------------------------------------------------------------------------------- /day19/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | from typing import NamedTuple 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | class Scanner(NamedTuple): 16 | sid: int 17 | points: list[tuple[int, int, int]] 18 | 19 | @classmethod 20 | def from_str(cls, s: str) -> Scanner: 21 | lines = s.splitlines() 22 | _, _, sid_s, _ = lines[0].split() 23 | points = [] 24 | for line in lines[1:]: 25 | x, y, z = support.parse_numbers_comma(line) 26 | points.append((x, y, z)) 27 | return cls(int(sid_s), points) 28 | 29 | 30 | class AxisInfo(NamedTuple): 31 | axis: int 32 | sign: int 33 | diff: int 34 | 35 | 36 | def x_edges_from( 37 | src: Scanner, 38 | scanners_by_id: dict[int, Scanner], 39 | ) -> dict[int, AxisInfo]: 40 | x_edges = {} 41 | for other in scanners_by_id.values(): 42 | for axis in (0, 1, 2): 43 | for sign in (-1, 1): 44 | d_x: collections.Counter[int] = collections.Counter() 45 | for x, _, _ in src.points: 46 | for other_pt in other.points: 47 | d_x[x - other_pt[axis] * sign] += 1 48 | 49 | (x_diff, n), = d_x.most_common(1) 50 | if n >= 12: 51 | x_edges[other.sid] = AxisInfo( 52 | axis=axis, 53 | sign=sign, 54 | diff=x_diff, 55 | ) 56 | return x_edges 57 | 58 | 59 | def yz_edges_from( 60 | src: Scanner, 61 | x_edges: dict[int, AxisInfo], 62 | scanners_by_id: dict[int, Scanner], 63 | ) -> tuple[dict[int, AxisInfo], dict[int, AxisInfo]]: 64 | y_edges = {} 65 | z_edges = {} 66 | 67 | for dst_id in x_edges: 68 | other = scanners_by_id[dst_id] 69 | for axis in (0, 1, 2): 70 | for sign in (-1, 1): 71 | d_y: collections.Counter[int] = collections.Counter() 72 | d_z: collections.Counter[int] = collections.Counter() 73 | for _, y, z in src.points: 74 | for other_pt in other.points: 75 | d_y[y - other_pt[axis] * sign] += 1 76 | d_z[z - other_pt[axis] * sign] += 1 77 | 78 | (y_diff, y_n), = d_y.most_common(1) 79 | if y_n >= 12: 80 | y_edges[dst_id] = AxisInfo( 81 | axis=axis, 82 | sign=sign, 83 | diff=y_diff, 84 | ) 85 | 86 | (z_diff, z_n), = d_z.most_common(1) 87 | if z_n >= 12: 88 | z_edges[dst_id] = AxisInfo( 89 | axis=axis, 90 | sign=sign, 91 | diff=z_diff, 92 | ) 93 | 94 | return y_edges, z_edges 95 | 96 | 97 | def compute(s: str) -> int: 98 | scanners = [Scanner.from_str(part) for part in s.split('\n\n')] 99 | scanners_by_id = {scanner.sid: scanner for scanner in scanners} 100 | scanner_positions = {0: (0, 0, 0)} 101 | 102 | todo = [scanners_by_id.pop(0)] 103 | while todo: 104 | src = todo.pop() 105 | 106 | x_edges = x_edges_from(src, scanners_by_id) 107 | y_edges, z_edges = yz_edges_from(src, x_edges, scanners_by_id) 108 | 109 | for k in x_edges: 110 | dst_x = x_edges[k].diff 111 | dst_y = y_edges[k].diff 112 | dst_z = z_edges[k].diff 113 | 114 | scanner_positions[k] = (dst_x, dst_y, dst_z) 115 | 116 | next_scanner = scanners_by_id.pop(k) 117 | next_scanner.points[:] = [ 118 | ( 119 | dst_x + x_edges[k].sign * pt[x_edges[k].axis], 120 | dst_y + y_edges[k].sign * pt[y_edges[k].axis], 121 | dst_z + z_edges[k].sign * pt[z_edges[k].axis], 122 | ) 123 | for pt in next_scanner.points 124 | ] 125 | 126 | todo.append(next_scanner) 127 | 128 | max_dist = 0 129 | positions = list(scanner_positions.values()) 130 | for i, (x1, y1, z1) in enumerate(positions): 131 | for x2, y2, z2 in positions[i:]: 132 | max_dist = max( 133 | abs(x2 - x1) + abs(y2 - y1) + abs(z2 - z1), 134 | max_dist, 135 | ) 136 | 137 | return max_dist 138 | 139 | 140 | INPUT_S = '''\ 141 | --- scanner 0 --- 142 | 404,-588,-901 143 | 528,-643,409 144 | -838,591,734 145 | 390,-675,-793 146 | -537,-823,-458 147 | -485,-357,347 148 | -345,-311,381 149 | -661,-816,-575 150 | -876,649,763 151 | -618,-824,-621 152 | 553,345,-567 153 | 474,580,667 154 | -447,-329,318 155 | -584,868,-557 156 | 544,-627,-890 157 | 564,392,-477 158 | 455,729,728 159 | -892,524,684 160 | -689,845,-530 161 | 423,-701,434 162 | 7,-33,-71 163 | 630,319,-379 164 | 443,580,662 165 | -789,900,-551 166 | 459,-707,401 167 | 168 | --- scanner 1 --- 169 | 686,422,578 170 | 605,423,415 171 | 515,917,-361 172 | -336,658,858 173 | 95,138,22 174 | -476,619,847 175 | -340,-569,-846 176 | 567,-361,727 177 | -460,603,-452 178 | 669,-402,600 179 | 729,430,532 180 | -500,-761,534 181 | -322,571,750 182 | -466,-666,-811 183 | -429,-592,574 184 | -355,545,-477 185 | 703,-491,-529 186 | -328,-685,520 187 | 413,935,-424 188 | -391,539,-444 189 | 586,-435,557 190 | -364,-763,-893 191 | 807,-499,-711 192 | 755,-354,-619 193 | 553,889,-390 194 | 195 | --- scanner 2 --- 196 | 649,640,665 197 | 682,-795,504 198 | -784,533,-524 199 | -644,584,-595 200 | -588,-843,648 201 | -30,6,44 202 | -674,560,763 203 | 500,723,-460 204 | 609,671,-379 205 | -555,-800,653 206 | -675,-892,-343 207 | 697,-426,-610 208 | 578,704,681 209 | 493,664,-388 210 | -671,-858,530 211 | -667,343,800 212 | 571,-461,-707 213 | -138,-166,112 214 | -889,563,-600 215 | 646,-828,498 216 | 640,759,510 217 | -630,509,768 218 | -681,-892,-333 219 | 673,-379,-804 220 | -742,-814,-386 221 | 577,-820,562 222 | 223 | --- scanner 3 --- 224 | -589,542,597 225 | 605,-692,669 226 | -500,565,-823 227 | -660,373,557 228 | -458,-679,-417 229 | -488,449,543 230 | -626,468,-788 231 | 338,-750,-386 232 | 528,-832,-391 233 | 562,-778,733 234 | -938,-730,414 235 | 543,643,-506 236 | -524,371,-870 237 | 407,773,750 238 | -104,29,83 239 | 378,-903,-323 240 | -778,-728,485 241 | 426,699,580 242 | -438,-605,-362 243 | -469,-447,-387 244 | 509,732,623 245 | 647,635,-688 246 | -868,-804,481 247 | 614,-800,639 248 | 595,780,-596 249 | 250 | --- scanner 4 --- 251 | 727,592,562 252 | -293,-554,779 253 | 441,611,-461 254 | -714,465,-776 255 | -743,427,-804 256 | -660,-479,-426 257 | 832,-632,460 258 | 927,-485,-438 259 | 408,393,-506 260 | 466,436,-512 261 | 110,16,151 262 | -258,-428,682 263 | -393,719,612 264 | -211,-452,876 265 | 808,-476,-593 266 | -575,615,604 267 | -485,667,467 268 | -680,325,-822 269 | -627,-443,-432 270 | 872,-547,-609 271 | 833,512,582 272 | 807,604,487 273 | 839,-516,451 274 | 891,-625,532 275 | -652,-548,-490 276 | 30,-46,-14 277 | ''' 278 | EXPECTED = 3621 279 | 280 | 281 | @pytest.mark.parametrize( 282 | ('input_s', 'expected'), 283 | ( 284 | (INPUT_S, EXPECTED), 285 | ), 286 | ) 287 | def test(input_s: str, expected: int) -> None: 288 | assert compute(input_s) == expected 289 | 290 | 291 | def main() -> int: 292 | parser = argparse.ArgumentParser() 293 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 294 | args = parser.parse_args() 295 | 296 | with open(args.data_file) as f, support.timing(): 297 | print(compute(f.read())) 298 | 299 | return 0 300 | 301 | 302 | if __name__ == '__main__': 303 | raise SystemExit(main()) 304 | -------------------------------------------------------------------------------- /day20/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day20/__init__.py -------------------------------------------------------------------------------- /day20/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def to_int(coords: dict[tuple[int, int], int], x: int, y: int) -> int: 15 | numbers = [ 16 | coords[x-1, y-1], coords[x, y-1], coords[x+1, y-1], 17 | coords[x-1, y], coords[x, y], coords[x+1, y], 18 | coords[x-1, y+1], coords[x, y+1], coords[x+1, y+1], 19 | ] 20 | return int(''.join(str(int(n)) for n in numbers), 2) 21 | 22 | 23 | def compute(s: str) -> int: 24 | p1, p2 = s.split('\n\n') 25 | 26 | part1 = collections.defaultdict(int) 27 | for i, c in enumerate(p1.strip()): 28 | if c == '#': 29 | part1[i] = 1 30 | 31 | part2_coords_s = support.parse_coords_hash(p2) 32 | part2_coords_d = collections.defaultdict( 33 | int, 34 | dict.fromkeys(part2_coords_s, 1), 35 | ) 36 | 37 | invert = int(part1[0] == 1 and part1[511] == 0) 38 | 39 | minx = min(x for x, _ in part2_coords_d) 40 | maxx = max(x for x, _ in part2_coords_d) 41 | miny = min(y for _, y in part2_coords_d) 42 | maxy = max(y for _, y in part2_coords_d) 43 | 44 | for _ in range(1): 45 | coords1 = collections.defaultdict( 46 | lambda: invert, 47 | { 48 | (x, y): part1[to_int(part2_coords_d, x, y)] 49 | for y in range(miny - 1, maxy + 2) 50 | for x in range(minx - 1, maxx + 2) 51 | }, 52 | ) 53 | part2_coords_d = collections.defaultdict( 54 | int, 55 | { 56 | (x, y): part1[to_int(coords1, x, y)] 57 | for y in range(miny - 2, maxy + 3) 58 | for x in range(minx - 2, maxx + 3) 59 | }, 60 | ) 61 | minx -= 2 62 | miny -= 2 63 | maxx += 2 64 | maxy += 2 65 | 66 | return sum(part2_coords_d.values()) 67 | 68 | 69 | INPUT_S = '''\ 70 | ..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..# 71 | 72 | #..#. 73 | #.... 74 | ##..# 75 | ..#.. 76 | ..### 77 | ''' 78 | EXPECTED = 35 79 | 80 | 81 | @pytest.mark.parametrize( 82 | ('input_s', 'expected'), 83 | ( 84 | (INPUT_S, EXPECTED), 85 | ), 86 | ) 87 | def test(input_s: str, expected: int) -> None: 88 | assert compute(input_s) == expected 89 | 90 | 91 | def main() -> int: 92 | parser = argparse.ArgumentParser() 93 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 94 | args = parser.parse_args() 95 | 96 | with open(args.data_file) as f, support.timing(): 97 | print(compute(f.read())) 98 | 99 | return 0 100 | 101 | 102 | if __name__ == '__main__': 103 | raise SystemExit(main()) 104 | -------------------------------------------------------------------------------- /day20/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def to_int(coords: dict[tuple[int, int], int], x: int, y: int) -> int: 15 | numbers = [ 16 | coords[x-1, y-1], coords[x, y-1], coords[x+1, y-1], 17 | coords[x-1, y], coords[x, y], coords[x+1, y], 18 | coords[x-1, y+1], coords[x, y+1], coords[x+1, y+1], 19 | ] 20 | return int(''.join(str(int(n)) for n in numbers), 2) 21 | 22 | 23 | def compute(s: str) -> int: 24 | p1, p2 = s.split('\n\n') 25 | 26 | part1 = collections.defaultdict(int) 27 | for i, c in enumerate(p1.strip()): 28 | if c == '#': 29 | part1[i] = 1 30 | 31 | part2_coords_s = support.parse_coords_hash(p2) 32 | part2_coords_d = collections.defaultdict( 33 | int, 34 | dict.fromkeys(part2_coords_s, 1), 35 | ) 36 | 37 | invert = int(part1[0] == 1 and part1[511] == 0) 38 | 39 | minx = min(x for x, _ in part2_coords_d) 40 | maxx = max(x for x, _ in part2_coords_d) 41 | miny = min(y for _, y in part2_coords_d) 42 | maxy = max(y for _, y in part2_coords_d) 43 | 44 | for _ in range(25): 45 | coords1 = collections.defaultdict( 46 | lambda: invert, 47 | { 48 | (x, y): part1[to_int(part2_coords_d, x, y)] 49 | for y in range(miny - 1, maxy + 2) 50 | for x in range(minx - 1, maxx + 2) 51 | }, 52 | ) 53 | part2_coords_d = collections.defaultdict( 54 | int, 55 | { 56 | (x, y): part1[to_int(coords1, x, y)] 57 | for y in range(miny - 2, maxy + 3) 58 | for x in range(minx - 2, maxx + 3) 59 | }, 60 | ) 61 | minx -= 2 62 | miny -= 2 63 | maxx += 2 64 | maxy += 2 65 | 66 | return sum(part2_coords_d.values()) 67 | 68 | 69 | INPUT_S = '''\ 70 | ..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..# 71 | 72 | #..#. 73 | #.... 74 | ##..# 75 | ..#.. 76 | ..### 77 | ''' 78 | EXPECTED = 3351 79 | 80 | 81 | @pytest.mark.parametrize( 82 | ('input_s', 'expected'), 83 | ( 84 | (INPUT_S, EXPECTED), 85 | ), 86 | ) 87 | def test(input_s: str, expected: int) -> None: 88 | assert compute(input_s) == expected 89 | 90 | 91 | def main() -> int: 92 | parser = argparse.ArgumentParser() 93 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 94 | args = parser.parse_args() 95 | 96 | with open(args.data_file) as f, support.timing(): 97 | print(compute(f.read())) 98 | 99 | return 0 100 | 101 | 102 | if __name__ == '__main__': 103 | raise SystemExit(main()) 104 | -------------------------------------------------------------------------------- /day21/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day21/__init__.py -------------------------------------------------------------------------------- /day21/input.txt: -------------------------------------------------------------------------------- 1 | Player 1 starting position: 5 2 | Player 2 starting position: 9 3 | -------------------------------------------------------------------------------- /day21/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import itertools 5 | import os.path 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | def weird_mod(n: int) -> int: 15 | while n > 10: 16 | n -= 10 17 | return n 18 | 19 | 20 | def compute(s: str) -> int: 21 | lines = s.splitlines() 22 | _, _, _, _, p0_s = lines[0].split() 23 | _, _, _, _, p1_s = lines[1].split() 24 | p1, p2 = int(p0_s), int(p1_s) 25 | 26 | die_count = 0 27 | die = itertools.cycle(range(1, 101)) 28 | p1_score = p2_score = 0 29 | 30 | while True: 31 | p1 = weird_mod(p1 + next(die) + next(die) + next(die)) 32 | die_count += 3 33 | p1_score += p1 34 | 35 | if p1_score >= 1000: 36 | break 37 | 38 | p2 = weird_mod(p2 + next(die) + next(die) + next(die)) 39 | die_count += 3 40 | p2_score += p2 41 | 42 | if p2_score >= 1000: 43 | break 44 | 45 | return die_count * min(p1_score, p2_score) 46 | 47 | 48 | INPUT_S = '''\ 49 | Player 1 starting position: 4 50 | Player 2 starting position: 8 51 | ''' 52 | EXPECTED = 739785 53 | 54 | 55 | @pytest.mark.parametrize( 56 | ('input_s', 'expected'), 57 | ( 58 | (INPUT_S, EXPECTED), 59 | ), 60 | ) 61 | def test(input_s: str, expected: int) -> None: 62 | assert compute(input_s) == expected 63 | 64 | 65 | def main() -> int: 66 | parser = argparse.ArgumentParser() 67 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 68 | args = parser.parse_args() 69 | 70 | with open(args.data_file) as f, support.timing(): 71 | print(compute(f.read())) 72 | 73 | return 0 74 | 75 | 76 | if __name__ == '__main__': 77 | raise SystemExit(main()) 78 | -------------------------------------------------------------------------------- /day21/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import collections 5 | import functools 6 | import os.path 7 | 8 | import pytest 9 | 10 | import support 11 | 12 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 13 | 14 | 15 | def weird_mod(n: int) -> int: 16 | while n > 10: 17 | n -= 10 18 | return n 19 | 20 | 21 | def compute(s: str) -> int: 22 | lines = s.splitlines() 23 | _, _, _, _, p0_s = lines[0].split() 24 | _, _, _, _, p1_s = lines[1].split() 25 | p1, p2 = int(p0_s), int(p1_s) 26 | 27 | # when rolling 3 dice they overlap 28 | die_rolls = collections.Counter( 29 | i + j + k for i in (1, 2, 3) for j in (1, 2, 3) for k in (1, 2, 3) 30 | ) 31 | 32 | @functools.lru_cache(maxsize=None) 33 | def compute_win_count( 34 | p1_pos: int, 35 | p1_score: int, 36 | p2_pos: int, 37 | p2_score: int, 38 | ) -> tuple[int, int]: 39 | p1_wins = p2_wins = 0 40 | for k, ct in die_rolls.items(): 41 | new_p1_pos = weird_mod(p1_pos + k) 42 | new_p1_score = p1_score + new_p1_pos 43 | if new_p1_score >= 21: 44 | p1_wins += ct 45 | else: 46 | tmp_p2_wins, tmp_p1_wins = compute_win_count( 47 | p2_pos, 48 | p2_score, 49 | new_p1_pos, 50 | new_p1_score, 51 | ) 52 | p1_wins += tmp_p1_wins * ct 53 | p2_wins += tmp_p2_wins * ct 54 | 55 | return p1_wins, p2_wins 56 | 57 | p1_win, p2_win = compute_win_count(p1, 0, p2, 0) 58 | return max(p1_win, p2_win) 59 | 60 | 61 | INPUT_S = '''\ 62 | Player 1 starting position: 4 63 | Player 2 starting position: 8 64 | ''' 65 | EXPECTED = 444356092776315 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 | -------------------------------------------------------------------------------- /day22/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day22/__init__.py -------------------------------------------------------------------------------- /day22/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | class Reboot(NamedTuple): 15 | on: bool 16 | x: tuple[int, int] 17 | y: tuple[int, int] 18 | z: tuple[int, int] 19 | 20 | @classmethod 21 | def parse(cls, s: str) -> Reboot: 22 | status, rest = s.split() 23 | x, y, z = rest.split(',') 24 | x, y, z = x[2:], y[2:], z[2:] 25 | x_0_s, x_1_s = x.split('..') 26 | y_0_s, y_1_s = y.split('..') 27 | z_0_s, z_1_s = z.split('..') 28 | 29 | return cls( 30 | status == 'on', 31 | (int(x_0_s), int(x_1_s)), 32 | (int(y_0_s), int(y_1_s)), 33 | (int(z_0_s), int(z_1_s)), 34 | ) 35 | 36 | 37 | def compute(s: str) -> int: 38 | reboots = [Reboot.parse(line) for line in s.splitlines()] 39 | 40 | coords = set() 41 | for step in reboots: 42 | new_coords = { 43 | (x, y, z) 44 | for x in range(max(step.x[0], -50), min(step.x[1], 50) + 1) 45 | for y in range(max(step.y[0], -50), min(step.y[1], 50) + 1) 46 | for z in range(max(step.z[0], -50), min(step.z[1], 50) + 1) 47 | } 48 | 49 | if step.on: 50 | coords |= new_coords 51 | else: 52 | coords -= new_coords 53 | 54 | return len(coords) 55 | 56 | 57 | INPUT_S = '''\ 58 | on x=-20..26,y=-36..17,z=-47..7 59 | on x=-20..33,y=-21..23,z=-26..28 60 | on x=-22..28,y=-29..23,z=-38..16 61 | on x=-46..7,y=-6..46,z=-50..-1 62 | on x=-49..1,y=-3..46,z=-24..28 63 | on x=2..47,y=-22..22,z=-23..27 64 | on x=-27..23,y=-28..26,z=-21..29 65 | on x=-39..5,y=-6..47,z=-3..44 66 | on x=-30..21,y=-8..43,z=-13..34 67 | on x=-22..26,y=-27..20,z=-29..19 68 | off x=-48..-32,y=26..41,z=-47..-37 69 | on x=-12..35,y=6..50,z=-50..-2 70 | off x=-48..-32,y=-32..-16,z=-15..-5 71 | on x=-18..26,y=-33..15,z=-7..46 72 | off x=-40..-22,y=-38..-28,z=23..41 73 | on x=-16..35,y=-41..10,z=-47..6 74 | off x=-32..-23,y=11..30,z=-14..3 75 | on x=-49..-5,y=-3..45,z=-29..18 76 | off x=18..30,y=-20..-8,z=-3..13 77 | on x=-41..9,y=-7..43,z=-33..15 78 | on x=-54112..-39298,y=-85059..-49293,z=-27449..7877 79 | on x=967..23432,y=45373..81175,z=27513..53682 80 | ''' 81 | EXPECTED = 590784 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 | -------------------------------------------------------------------------------- /day22/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | from typing import NamedTuple 6 | 7 | import pytest 8 | 9 | import support 10 | 11 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 12 | 13 | 14 | class Cube(NamedTuple): 15 | x0: int 16 | x1: int 17 | y0: int 18 | y1: int 19 | z0: int 20 | z1: int 21 | 22 | @property 23 | def size(self) -> int: 24 | return ( 25 | (self.x1 - self.x0) * 26 | (self.y1 - self.y0) * 27 | (self.z1 - self.z0) 28 | ) 29 | 30 | def intersects(self, other: Cube) -> bool: 31 | return ( 32 | self.x0 <= other.x1 - 1 and 33 | self.x1 - 1 >= other.x0 and 34 | self.y0 <= other.y1 - 1 and 35 | self.y1 - 1 >= other.y0 and 36 | self.z0 <= other.z1 - 1 and 37 | self.z1 - 1 >= other.z0 38 | ) 39 | 40 | def contains(self, other: Cube) -> bool: 41 | return ( 42 | self.x0 <= other.x0 and 43 | self.x1 >= other.x1 and 44 | self.y0 <= other.y0 and 45 | self.y1 >= other.y1 and 46 | self.z0 <= other.z0 and 47 | self.z1 >= other.z1 48 | ) 49 | 50 | def subtract(self, other: Cube) -> list[Cube]: 51 | if not self.intersects(other): 52 | return [self] 53 | elif other.contains(self): 54 | return [] 55 | 56 | xs = sorted((self.x0, self.x1, other.x0, other.x1)) 57 | ys = sorted((self.y0, self.y1, other.y0, other.y1)) 58 | zs = sorted((self.z0, self.z1, other.z0, other.z1)) 59 | 60 | ret = [] 61 | for x0, x1 in zip(xs, xs[1:]): 62 | for y0, y1 in zip(ys, ys[1:]): 63 | for z0, z1 in zip(zs, zs[1:]): 64 | cube = Cube(x0, x1, y0, y1, z0, z1) 65 | if self.contains(cube) and not cube.intersects(other): 66 | ret.append(cube) 67 | return ret 68 | 69 | @classmethod 70 | def parse(cls, x: str, y: str, z: str) -> Cube: 71 | x_0_s, x_1_s = x.split('..') 72 | y_0_s, y_1_s = y.split('..') 73 | z_0_s, z_1_s = z.split('..') 74 | return cls( 75 | int(x_0_s), int(x_1_s) + 1, 76 | int(y_0_s), int(y_1_s) + 1, 77 | int(z_0_s), int(z_1_s) + 1, 78 | ) 79 | 80 | 81 | class Reboot(NamedTuple): 82 | on: bool 83 | cube: Cube 84 | 85 | @classmethod 86 | def parse(cls, s: str) -> Reboot: 87 | status, rest = s.split() 88 | x, y, z = rest.split(',') 89 | x, y, z = x[2:], y[2:], z[2:] 90 | 91 | return cls(status == 'on', Cube.parse(x, y, z)) 92 | 93 | 94 | def compute(s: str) -> int: 95 | reboots = [Reboot.parse(line) for line in s.splitlines()] 96 | 97 | cubes: list[Cube] = [] 98 | for step in reboots: 99 | cubes = [ 100 | part 101 | for cube in cubes 102 | for part in cube.subtract(step.cube) 103 | ] 104 | if step.on: 105 | cubes.append(step.cube) 106 | 107 | return sum(cube.size for cube in cubes) 108 | 109 | 110 | INPUT_S = '''\ 111 | on x=-5..47,y=-31..22,z=-19..33 112 | on x=-44..5,y=-27..21,z=-14..35 113 | on x=-49..-1,y=-11..42,z=-10..38 114 | on x=-20..34,y=-40..6,z=-44..1 115 | off x=26..39,y=40..50,z=-2..11 116 | on x=-41..5,y=-41..6,z=-36..8 117 | off x=-43..-33,y=-45..-28,z=7..25 118 | on x=-33..15,y=-32..19,z=-34..11 119 | off x=35..47,y=-46..-34,z=-11..5 120 | on x=-14..36,y=-6..44,z=-16..29 121 | on x=-57795..-6158,y=29564..72030,z=20435..90618 122 | on x=36731..105352,y=-21140..28532,z=16094..90401 123 | on x=30999..107136,y=-53464..15513,z=8553..71215 124 | on x=13528..83982,y=-99403..-27377,z=-24141..23996 125 | on x=-72682..-12347,y=18159..111354,z=7391..80950 126 | on x=-1060..80757,y=-65301..-20884,z=-103788..-16709 127 | on x=-83015..-9461,y=-72160..-8347,z=-81239..-26856 128 | on x=-52752..22273,y=-49450..9096,z=54442..119054 129 | on x=-29982..40483,y=-108474..-28371,z=-24328..38471 130 | on x=-4958..62750,y=40422..118853,z=-7672..65583 131 | on x=55694..108686,y=-43367..46958,z=-26781..48729 132 | on x=-98497..-18186,y=-63569..3412,z=1232..88485 133 | on x=-726..56291,y=-62629..13224,z=18033..85226 134 | on x=-110886..-34664,y=-81338..-8658,z=8914..63723 135 | on x=-55829..24974,y=-16897..54165,z=-121762..-28058 136 | on x=-65152..-11147,y=22489..91432,z=-58782..1780 137 | on x=-120100..-32970,y=-46592..27473,z=-11695..61039 138 | on x=-18631..37533,y=-124565..-50804,z=-35667..28308 139 | on x=-57817..18248,y=49321..117703,z=5745..55881 140 | on x=14781..98692,y=-1341..70827,z=15753..70151 141 | on x=-34419..55919,y=-19626..40991,z=39015..114138 142 | on x=-60785..11593,y=-56135..2999,z=-95368..-26915 143 | on x=-32178..58085,y=17647..101866,z=-91405..-8878 144 | on x=-53655..12091,y=50097..105568,z=-75335..-4862 145 | on x=-111166..-40997,y=-71714..2688,z=5609..50954 146 | on x=-16602..70118,y=-98693..-44401,z=5197..76897 147 | on x=16383..101554,y=4615..83635,z=-44907..18747 148 | off x=-95822..-15171,y=-19987..48940,z=10804..104439 149 | on x=-89813..-14614,y=16069..88491,z=-3297..45228 150 | on x=41075..99376,y=-20427..49978,z=-52012..13762 151 | on x=-21330..50085,y=-17944..62733,z=-112280..-30197 152 | on x=-16478..35915,y=36008..118594,z=-7885..47086 153 | off x=-98156..-27851,y=-49952..43171,z=-99005..-8456 154 | off x=2032..69770,y=-71013..4824,z=7471..94418 155 | on x=43670..120875,y=-42068..12382,z=-24787..38892 156 | off x=37514..111226,y=-45862..25743,z=-16714..54663 157 | off x=25699..97951,y=-30668..59918,z=-15349..69697 158 | off x=-44271..17935,y=-9516..60759,z=49131..112598 159 | on x=-61695..-5813,y=40978..94975,z=8655..80240 160 | off x=-101086..-9439,y=-7088..67543,z=33935..83858 161 | off x=18020..114017,y=-48931..32606,z=21474..89843 162 | off x=-77139..10506,y=-89994..-18797,z=-80..59318 163 | off x=8476..79288,y=-75520..11602,z=-96624..-24783 164 | on x=-47488..-1262,y=24338..100707,z=16292..72967 165 | off x=-84341..13987,y=2429..92914,z=-90671..-1318 166 | off x=-37810..49457,y=-71013..-7894,z=-105357..-13188 167 | off x=-27365..46395,y=31009..98017,z=15428..76570 168 | off x=-70369..-16548,y=22648..78696,z=-1892..86821 169 | on x=-53470..21291,y=-120233..-33476,z=-44150..38147 170 | off x=-93533..-4276,y=-16170..68771,z=-104985..-24507 171 | ''' 172 | EXPECTED = 2758514936282235 173 | SMALL_INPUT_S = '''\ 174 | on x=10..12,y=10..12,z=10..12 175 | on x=11..13,y=11..13,z=11..13 176 | off x=9..11,y=9..11,z=9..11 177 | on x=10..10,y=10..10,z=10..10 178 | ''' 179 | SMALL_EXPECTED = 39 180 | 181 | 182 | @pytest.mark.parametrize( 183 | ('input_s', 'expected'), 184 | ( 185 | (SMALL_INPUT_S, SMALL_EXPECTED), 186 | (INPUT_S, EXPECTED), 187 | ), 188 | ) 189 | def test(input_s: str, expected: int) -> None: 190 | assert compute(input_s) == expected 191 | 192 | 193 | def main() -> int: 194 | parser = argparse.ArgumentParser() 195 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 196 | args = parser.parse_args() 197 | 198 | with open(args.data_file) as f, support.timing(): 199 | print(compute(f.read())) 200 | 201 | return 0 202 | 203 | 204 | if __name__ == '__main__': 205 | raise SystemExit(main()) 206 | -------------------------------------------------------------------------------- /day23/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day23/__init__.py -------------------------------------------------------------------------------- /day23/input.txt: -------------------------------------------------------------------------------- 1 | ############# 2 | #...........# 3 | ###D#A#D#C### 4 | #B#C#B#A# 5 | ######### 6 | -------------------------------------------------------------------------------- /day23/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import heapq 5 | import os.path 6 | from typing import Generator 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 | POS = {'A': 0, 'B': 1, 'C': 2, 'D': 3} 16 | 17 | 18 | class State(NamedTuple): 19 | top: dict[int, int | None] 20 | row1: dict[int, int | None] 21 | row2: dict[int, int | None] 22 | 23 | def __hash__(self) -> int: 24 | return hash(( 25 | tuple(self.top.items()), 26 | tuple(self.row1.items()), 27 | tuple(self.row2.items()), 28 | )) 29 | 30 | def __lt__(self, other: object) -> bool: 31 | if not isinstance(other, State): 32 | return NotImplemented 33 | else: 34 | return id(self) < id(other) 35 | 36 | @property 37 | def completed(self) -> bool: 38 | return ( 39 | all(k == v for k, v in self.row1.items()) and 40 | all(k == v for k, v in self.row2.items()) 41 | ) 42 | 43 | @classmethod 44 | def parse(cls, s: str) -> State: 45 | lines = s.splitlines() 46 | return cls( 47 | dict.fromkeys((1, 3, 5, 7, 9), None), 48 | { 49 | 0: POS[lines[2][3]], 50 | 1: POS[lines[2][5]], 51 | 2: POS[lines[2][7]], 52 | 3: POS[lines[2][9]], 53 | }, 54 | { 55 | 0: POS[lines[3][3]], 56 | 1: POS[lines[3][5]], 57 | 2: POS[lines[3][7]], 58 | 3: POS[lines[3][9]], 59 | }, 60 | ) 61 | 62 | def __repr__(self) -> str: 63 | return ( 64 | f'State(\n' 65 | f' top={self.top!r},\n' 66 | f' row1={self.row1!r},\n' 67 | f' row2={self.row2!r},\n' 68 | f')' 69 | ) 70 | 71 | 72 | def next_states( 73 | score: int, 74 | state: State, 75 | ) -> Generator[tuple[int, State], None, None]: 76 | for k, v in state.top.items(): 77 | if v is None: 78 | continue 79 | 80 | target_col = 2 + v * 2 81 | max_c = max(target_col, k) 82 | min_c = min(target_col, k) 83 | to_move_top = max_c - min_c 84 | 85 | if all( 86 | k2 <= min_c or k2 >= max_c or v2 is None 87 | for k2, v2 in state.top.items() 88 | ): 89 | if state.row2[v] is None: 90 | yield ( 91 | score + (to_move_top + 2) * 10 ** v, 92 | state._replace( 93 | top={**state.top, k: None}, 94 | row2={**state.row2, v: v}, 95 | ) 96 | ) 97 | elif state.row2[v] == v and state.row1[v] is None: 98 | yield ( 99 | score + (to_move_top + 1) * 10 ** v, 100 | state._replace( 101 | top={**state.top, k: None}, 102 | row1={**state.row1, v: v}, 103 | ), 104 | ) 105 | 106 | potential_targets = {k for k, v in state.top.items() if v is None} 107 | for i in range(4): 108 | row1_val = state.row1[i] 109 | row2_val = state.row2[i] 110 | # this row is done! do not move! 111 | if row1_val == i and row2_val == i: 112 | continue 113 | 114 | for target in potential_targets: 115 | src_col = 2 + i * 2 116 | max_c = max(src_col, target) 117 | min_c = min(src_col, target) 118 | to_move_top = max_c - min_c 119 | 120 | if all( 121 | k2 <= min_c or k2 >= max_c or v2 is None 122 | for k2, v2 in state.top.items() 123 | ): 124 | if row1_val is not None: 125 | yield ( 126 | score + (to_move_top + 1) * 10 ** row1_val, 127 | state._replace( 128 | top={**state.top, target: row1_val}, 129 | row1={**state.row1, i: None}, 130 | ) 131 | ) 132 | elif row2_val != i and row2_val is not None: 133 | yield ( 134 | score + (to_move_top + 2) * 10 ** row2_val, 135 | state._replace( 136 | top={**state.top, target: row2_val}, 137 | row2={**state.row2, i: None}, 138 | ), 139 | ) 140 | 141 | 142 | def compute(s: str) -> int: 143 | initial = State.parse(s) 144 | 145 | seen = set() 146 | todo = [(0, initial)] 147 | while todo: 148 | score, state = heapq.heappop(todo) 149 | 150 | if state.completed: 151 | return score 152 | elif state in seen: 153 | continue 154 | else: 155 | seen.add(state) 156 | 157 | for tp in next_states(score, state): 158 | heapq.heappush(todo, tp) 159 | 160 | raise AssertionError('unreachable!') 161 | 162 | 163 | INPUT_S = '''\ 164 | ############# 165 | #...........# 166 | ###B#C#B#D### 167 | #A#D#C#A# 168 | ######### 169 | ''' 170 | EXPECTED = 12521 171 | 172 | 173 | @pytest.mark.parametrize( 174 | ('input_s', 'expected'), 175 | ( 176 | (INPUT_S, EXPECTED), 177 | ), 178 | ) 179 | def test(input_s: str, expected: int) -> None: 180 | assert compute(input_s) == expected 181 | 182 | 183 | def main() -> int: 184 | parser = argparse.ArgumentParser() 185 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 186 | args = parser.parse_args() 187 | 188 | with open(args.data_file) as f, support.timing(): 189 | print(compute(f.read())) 190 | 191 | return 0 192 | 193 | 194 | if __name__ == '__main__': 195 | raise SystemExit(main()) 196 | -------------------------------------------------------------------------------- /day24/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anthonywritescode/aoc2021/ff3f13a1f9c8068a6680c0698056eca43a315b3d/day24/__init__.py -------------------------------------------------------------------------------- /day24/input.txt: -------------------------------------------------------------------------------- 1 | inp w 2 | mul x 0 3 | add x z 4 | mod x 26 5 | div z 1 6 | add x 15 7 | eql x w 8 | eql x 0 9 | mul y 0 10 | add y 25 11 | mul y x 12 | add y 1 13 | mul z y 14 | mul y 0 15 | add y w 16 | add y 4 17 | mul y x 18 | add z y 19 | inp w 20 | mul x 0 21 | add x z 22 | mod x 26 23 | div z 1 24 | add x 14 25 | eql x w 26 | eql x 0 27 | mul y 0 28 | add y 25 29 | mul y x 30 | add y 1 31 | mul z y 32 | mul y 0 33 | add y w 34 | add y 16 35 | mul y x 36 | add z y 37 | inp w 38 | mul x 0 39 | add x z 40 | mod x 26 41 | div z 1 42 | add x 11 43 | eql x w 44 | eql x 0 45 | mul y 0 46 | add y 25 47 | mul y x 48 | add y 1 49 | mul z y 50 | mul y 0 51 | add y w 52 | add y 14 53 | mul y x 54 | add z y 55 | inp w 56 | mul x 0 57 | add x z 58 | mod x 26 59 | div z 26 60 | add x -13 61 | eql x w 62 | eql x 0 63 | mul y 0 64 | add y 25 65 | mul y x 66 | add y 1 67 | mul z y 68 | mul y 0 69 | add y w 70 | add y 3 71 | mul y x 72 | add z y 73 | inp w 74 | mul x 0 75 | add x z 76 | mod x 26 77 | div z 1 78 | add x 14 79 | eql x w 80 | eql x 0 81 | mul y 0 82 | add y 25 83 | mul y x 84 | add y 1 85 | mul z y 86 | mul y 0 87 | add y w 88 | add y 11 89 | mul y x 90 | add z y 91 | inp w 92 | mul x 0 93 | add x z 94 | mod x 26 95 | div z 1 96 | add x 15 97 | eql x w 98 | eql x 0 99 | mul y 0 100 | add y 25 101 | mul y x 102 | add y 1 103 | mul z y 104 | mul y 0 105 | add y w 106 | add y 13 107 | mul y x 108 | add z y 109 | inp w 110 | mul x 0 111 | add x z 112 | mod x 26 113 | div z 26 114 | add x -7 115 | eql x w 116 | eql x 0 117 | mul y 0 118 | add y 25 119 | mul y x 120 | add y 1 121 | mul z y 122 | mul y 0 123 | add y w 124 | add y 11 125 | mul y x 126 | add z y 127 | inp w 128 | mul x 0 129 | add x z 130 | mod x 26 131 | div z 1 132 | add x 10 133 | eql x w 134 | eql x 0 135 | mul y 0 136 | add y 25 137 | mul y x 138 | add y 1 139 | mul z y 140 | mul y 0 141 | add y w 142 | add y 7 143 | mul y x 144 | add z y 145 | inp w 146 | mul x 0 147 | add x z 148 | mod x 26 149 | div z 26 150 | add x -12 151 | eql x w 152 | eql x 0 153 | mul y 0 154 | add y 25 155 | mul y x 156 | add y 1 157 | mul z y 158 | mul y 0 159 | add y w 160 | add y 12 161 | mul y x 162 | add z y 163 | inp w 164 | mul x 0 165 | add x z 166 | mod x 26 167 | div z 1 168 | add x 15 169 | eql x w 170 | eql x 0 171 | mul y 0 172 | add y 25 173 | mul y x 174 | add y 1 175 | mul z y 176 | mul y 0 177 | add y w 178 | add y 15 179 | mul y x 180 | add z y 181 | inp w 182 | mul x 0 183 | add x z 184 | mod x 26 185 | div z 26 186 | add x -16 187 | eql x w 188 | eql x 0 189 | mul y 0 190 | add y 25 191 | mul y x 192 | add y 1 193 | mul z y 194 | mul y 0 195 | add y w 196 | add y 13 197 | mul y x 198 | add z y 199 | inp w 200 | mul x 0 201 | add x z 202 | mod x 26 203 | div z 26 204 | add x -9 205 | eql x w 206 | eql x 0 207 | mul y 0 208 | add y 25 209 | mul y x 210 | add y 1 211 | mul z y 212 | mul y 0 213 | add y w 214 | add y 1 215 | mul y x 216 | add z y 217 | inp w 218 | mul x 0 219 | add x z 220 | mod x 26 221 | div z 26 222 | add x -8 223 | eql x w 224 | eql x 0 225 | mul y 0 226 | add y 25 227 | mul y x 228 | add y 1 229 | mul z y 230 | mul y 0 231 | add y w 232 | add y 15 233 | mul y x 234 | add z y 235 | inp w 236 | mul x 0 237 | add x z 238 | mod x 26 239 | div z 26 240 | add x -8 241 | eql x w 242 | eql x 0 243 | mul y 0 244 | add y 25 245 | mul y x 246 | add y 1 247 | mul z y 248 | mul y 0 249 | add y w 250 | add y 4 251 | mul y x 252 | add z y 253 | -------------------------------------------------------------------------------- /day24/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import operator 5 | import os.path 6 | from typing import Any 7 | from typing import Protocol 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 | 19 | class _Expr(Protocol): 20 | @property 21 | def left(self) -> int | _Expr | Var: ... 22 | @property 23 | def op(self) -> str: ... 24 | @property 25 | def right(self) -> int | _Expr | Var: ... 26 | def __add__(self, other: object) -> _Expr: ... 27 | def __radd__(self, other: object) -> _Expr: ... 28 | def __mul__(self, other: object) -> int | _Expr: ... 29 | def __rmul__(self, other: object) -> int | _Expr: ... 30 | def __floordiv__(self, other: object) -> _Expr: ... 31 | def __rfloordiv__(self, other: object) -> _Expr: ... 32 | def __mod__(self, other: object) -> _Expr: ... 33 | def __rmod__(self, other: object) -> _Expr: ... 34 | 35 | 36 | class Expr: 37 | def __init__( 38 | self, 39 | left: int | _Expr | Var, 40 | op: str, 41 | right: int | _Expr | Var, 42 | ) -> None: 43 | self.left = left 44 | self.op = op 45 | self.right = right 46 | 47 | def __repr__(self) -> str: 48 | return ( 49 | f'{type(self).__name__}(' 50 | f'left={self.left!r}, ' 51 | f'op={self.op!r}, ' 52 | f'right={self.right!r})' 53 | ) 54 | 55 | def __add__(self, other: object) -> _Expr: 56 | if other == 0: 57 | return self 58 | elif isinstance(other, (int, Expr, Var)): 59 | return type(self)(self, '+', other) 60 | else: 61 | raise AssertionError('unexpected', self, other) 62 | 63 | __radd__ = __add__ 64 | 65 | def __mul__(self, other: object) -> int | _Expr: 66 | if other == 0: 67 | return 0 68 | elif other == 1: 69 | return self 70 | elif isinstance(other, (int, Expr)): 71 | return type(self)(self, '*', other) 72 | else: 73 | raise AssertionError('unexpected', self, other) 74 | 75 | __rmul__ = __mul__ 76 | 77 | def __floordiv__(self, other: object) -> _Expr: 78 | if other == 1: 79 | return self 80 | elif isinstance(other, (int, Expr)): 81 | return type(self)(self, '//', other) 82 | else: 83 | raise AssertionError('unexpected', self, other) 84 | 85 | def __mod__(self, other: object) -> _Expr: 86 | if isinstance(other, (int, Expr)): 87 | return type(self)(self, '%', other) 88 | else: 89 | raise AssertionError('unexpected', self, other) 90 | 91 | def not_supported(self, other: object) -> _Expr: 92 | raise AssertionError('unexpected', self, other) 93 | 94 | __rfloordiv__ = __rmod__ = not_supported 95 | 96 | def __eq__(self, other: object) -> bool: 97 | if isinstance(other, int): 98 | assert other in (0, 1), other 99 | return False 100 | assert self.op == '+' 101 | assert isinstance(self.right, int), self.right 102 | assert self.right < 1 or self.right >= 10 103 | assert isinstance(other, Var) 104 | return False 105 | 106 | 107 | class Var: 108 | def __init__(self, digit: int) -> None: 109 | self.digit = digit 110 | 111 | def __repr__(self) -> str: 112 | return f'{type(self).__name__}({self.digit})' 113 | 114 | def __add__(self, other: object) -> Var | Expr: 115 | if other == 0: 116 | return self 117 | elif isinstance(other, (int, Expr)): 118 | return Expr(other, '+', self) 119 | else: 120 | raise AssertionError(self, other) 121 | 122 | __radd__ = __add__ 123 | 124 | def __eq__(self, other: object) -> bool: 125 | if other is self: 126 | return True 127 | else: 128 | assert isinstance(other, int), (self, other) 129 | assert other < 1 or other >= 10 130 | return False 131 | 132 | def not_supported(self, other: object) -> bool: 133 | raise AssertionError('unreachable!', self, other) 134 | 135 | __mul__ = __rmul__ = not_supported 136 | __floordiv__ = __rfloordiv__ = not_supported 137 | __mod__ = __rmod__ = not_supported 138 | 139 | 140 | def compute(s: str) -> int: 141 | variables: dict[str, int | _Expr | Var] = { 142 | 'x': 0, 143 | 'y': 0, 144 | 'z': 0, 145 | } 146 | var_w = Var(-1) 147 | 148 | def lookup(s: str) -> int | _Expr | Var: 149 | if s == 'w': 150 | return var_w 151 | elif s.isalpha(): 152 | return variables[s] 153 | else: 154 | return int(s) 155 | 156 | ops = { 157 | '+': operator.add, 158 | '*': operator.mul, 159 | '//': operator.truediv, 160 | '%': operator.mod, 161 | } 162 | 163 | def _to_z3(expr: int | _Expr | Var) -> Any: 164 | if isinstance(expr, bool): 165 | return int(expr) 166 | elif isinstance(expr, int): 167 | return expr 168 | elif isinstance(expr, Var): 169 | return digits[expr.digit] 170 | else: 171 | return ops[expr.op](_to_z3(expr.left), _to_z3(expr.right)) 172 | 173 | digits = [Int(f'W_{i}') for i in range(14)] 174 | o = Optimize() 175 | for digit in digits: 176 | o.add(1 <= digit) 177 | o.add(digit < 10) 178 | 179 | for line in s.splitlines(): 180 | cmd = line.split() 181 | if cmd[0] == 'inp': 182 | var_w = Var(var_w.digit + 1) 183 | elif cmd[0] == 'add': 184 | if cmd[2].startswith('-'): 185 | o.add( 186 | digits[var_w.digit] == _to_z3(variables['x'] + int(cmd[2])) 187 | ) 188 | variables['x'] = var_w 189 | else: 190 | variables[cmd[1]] += lookup(cmd[2]) 191 | elif cmd[0] == 'mul': 192 | if cmd[2] == '0': 193 | variables[cmd[1]] = 0 194 | else: 195 | variables[cmd[1]] *= lookup(cmd[2]) 196 | elif cmd[0] == 'div': 197 | variables[cmd[1]] //= lookup(cmd[2]) 198 | elif cmd[0] == 'mod': 199 | variables[cmd[1]] %= lookup(cmd[2]) 200 | elif cmd[0] == 'eql': 201 | variables[cmd[1]] = (variables[cmd[1]] == lookup(cmd[2])) 202 | else: 203 | raise AssertionError(cmd) 204 | 205 | sum_v = Int('Sum') 206 | o.add(sum_v == sum(var * 10 ** (13 - i) for i, var in enumerate(digits))) 207 | o.maximize(sum_v) 208 | assert o.check() == sat 209 | model = o.model() 210 | return model[sum_v] 211 | 212 | 213 | INPUT_S = '''\ 214 | 215 | ''' 216 | EXPECTED = 1 217 | 218 | 219 | @pytest.mark.parametrize( 220 | ('input_s', 'expected'), 221 | ( 222 | ), 223 | ) 224 | def test(input_s: str, expected: int) -> None: 225 | assert compute(input_s) == expected 226 | 227 | 228 | def main() -> int: 229 | parser = argparse.ArgumentParser() 230 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 231 | args = parser.parse_args() 232 | 233 | with open(args.data_file) as f, support.timing(): 234 | print(compute(f.read())) 235 | 236 | return 0 237 | 238 | 239 | if __name__ == '__main__': 240 | raise SystemExit(main()) 241 | -------------------------------------------------------------------------------- /day24/part2.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import operator 5 | import os.path 6 | from typing import Any 7 | from typing import Protocol 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 | 19 | class _Expr(Protocol): 20 | @property 21 | def left(self) -> int | _Expr | Var: ... 22 | @property 23 | def op(self) -> str: ... 24 | @property 25 | def right(self) -> int | _Expr | Var: ... 26 | def __add__(self, other: object) -> _Expr: ... 27 | def __radd__(self, other: object) -> _Expr: ... 28 | def __mul__(self, other: object) -> int | _Expr: ... 29 | def __rmul__(self, other: object) -> int | _Expr: ... 30 | def __floordiv__(self, other: object) -> _Expr: ... 31 | def __rfloordiv__(self, other: object) -> _Expr: ... 32 | def __mod__(self, other: object) -> _Expr: ... 33 | def __rmod__(self, other: object) -> _Expr: ... 34 | 35 | 36 | class Expr: 37 | def __init__( 38 | self, 39 | left: int | _Expr | Var, 40 | op: str, 41 | right: int | _Expr | Var, 42 | ) -> None: 43 | self.left = left 44 | self.op = op 45 | self.right = right 46 | 47 | def __repr__(self) -> str: 48 | return ( 49 | f'{type(self).__name__}(' 50 | f'left={self.left!r}, ' 51 | f'op={self.op!r}, ' 52 | f'right={self.right!r})' 53 | ) 54 | 55 | def __add__(self, other: object) -> _Expr: 56 | if other == 0: 57 | return self 58 | elif isinstance(other, (int, Expr, Var)): 59 | return type(self)(self, '+', other) 60 | else: 61 | raise AssertionError('unexpected', self, other) 62 | 63 | __radd__ = __add__ 64 | 65 | def __mul__(self, other: object) -> int | _Expr: 66 | if other == 0: 67 | return 0 68 | elif other == 1: 69 | return self 70 | elif isinstance(other, (int, Expr)): 71 | return type(self)(self, '*', other) 72 | else: 73 | raise AssertionError('unexpected', self, other) 74 | 75 | __rmul__ = __mul__ 76 | 77 | def __floordiv__(self, other: object) -> _Expr: 78 | if other == 1: 79 | return self 80 | elif isinstance(other, (int, Expr)): 81 | return type(self)(self, '//', other) 82 | else: 83 | raise AssertionError('unexpected', self, other) 84 | 85 | def __mod__(self, other: object) -> _Expr: 86 | if isinstance(other, (int, Expr)): 87 | return type(self)(self, '%', other) 88 | else: 89 | raise AssertionError('unexpected', self, other) 90 | 91 | def not_supported(self, other: object) -> _Expr: 92 | raise AssertionError('unexpected', self, other) 93 | 94 | __rfloordiv__ = __rmod__ = not_supported 95 | 96 | def __eq__(self, other: object) -> bool: 97 | if isinstance(other, int): 98 | assert other in (0, 1), other 99 | return False 100 | assert self.op == '+' 101 | assert isinstance(self.right, int), self.right 102 | assert self.right < 1 or self.right >= 10 103 | assert isinstance(other, Var) 104 | return False 105 | 106 | 107 | class Var: 108 | def __init__(self, digit: int) -> None: 109 | self.digit = digit 110 | 111 | def __repr__(self) -> str: 112 | return f'{type(self).__name__}({self.digit})' 113 | 114 | def __add__(self, other: object) -> Var | Expr: 115 | if other == 0: 116 | return self 117 | elif isinstance(other, (int, Expr)): 118 | return Expr(other, '+', self) 119 | else: 120 | raise AssertionError(self, other) 121 | 122 | __radd__ = __add__ 123 | 124 | def __eq__(self, other: object) -> bool: 125 | if other is self: 126 | return True 127 | else: 128 | assert isinstance(other, int), (self, other) 129 | assert other < 1 or other >= 10 130 | return False 131 | 132 | def not_supported(self, other: object) -> bool: 133 | raise AssertionError('unreachable!', self, other) 134 | 135 | __mul__ = __rmul__ = not_supported 136 | __floordiv__ = __rfloordiv__ = not_supported 137 | __mod__ = __rmod__ = not_supported 138 | 139 | 140 | def compute(s: str) -> int: 141 | variables: dict[str, int | _Expr | Var] = { 142 | 'x': 0, 143 | 'y': 0, 144 | 'z': 0, 145 | } 146 | var_w = Var(-1) 147 | 148 | def lookup(s: str) -> int | _Expr | Var: 149 | if s == 'w': 150 | return var_w 151 | elif s.isalpha(): 152 | return variables[s] 153 | else: 154 | return int(s) 155 | 156 | ops = { 157 | '+': operator.add, 158 | '*': operator.mul, 159 | '//': operator.truediv, 160 | '%': operator.mod, 161 | } 162 | 163 | def _to_z3(expr: int | _Expr | Var) -> Any: 164 | if isinstance(expr, bool): 165 | return int(expr) 166 | elif isinstance(expr, int): 167 | return expr 168 | elif isinstance(expr, Var): 169 | return digits[expr.digit] 170 | else: 171 | return ops[expr.op](_to_z3(expr.left), _to_z3(expr.right)) 172 | 173 | digits = [Int(f'W_{i}') for i in range(14)] 174 | o = Optimize() 175 | for digit in digits: 176 | o.add(1 <= digit) 177 | o.add(digit < 10) 178 | 179 | for line in s.splitlines(): 180 | cmd = line.split() 181 | if cmd[0] == 'inp': 182 | var_w = Var(var_w.digit + 1) 183 | elif cmd[0] == 'add': 184 | if cmd[2].startswith('-'): 185 | o.add( 186 | digits[var_w.digit] == _to_z3(variables['x'] + int(cmd[2])) 187 | ) 188 | variables['x'] = var_w 189 | else: 190 | variables[cmd[1]] += lookup(cmd[2]) 191 | elif cmd[0] == 'mul': 192 | if cmd[2] == '0': 193 | variables[cmd[1]] = 0 194 | else: 195 | variables[cmd[1]] *= lookup(cmd[2]) 196 | elif cmd[0] == 'div': 197 | variables[cmd[1]] //= lookup(cmd[2]) 198 | elif cmd[0] == 'mod': 199 | variables[cmd[1]] %= lookup(cmd[2]) 200 | elif cmd[0] == 'eql': 201 | variables[cmd[1]] = (variables[cmd[1]] == lookup(cmd[2])) 202 | else: 203 | raise AssertionError(cmd) 204 | 205 | sum_v = Int('Sum') 206 | o.add(sum_v == sum(var * 10 ** (13 - i) for i, var in enumerate(digits))) 207 | o.minimize(sum_v) 208 | assert o.check() == sat 209 | model = o.model() 210 | return model[sum_v] 211 | 212 | 213 | INPUT_S = '''\ 214 | 215 | ''' 216 | EXPECTED = 1 217 | 218 | 219 | @pytest.mark.parametrize( 220 | ('input_s', 'expected'), 221 | ( 222 | ), 223 | ) 224 | def test(input_s: str, expected: int) -> None: 225 | assert compute(input_s) == expected 226 | 227 | 228 | def main() -> int: 229 | parser = argparse.ArgumentParser() 230 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 231 | args = parser.parse_args() 232 | 233 | with open(args.data_file) as f, support.timing(): 234 | print(compute(f.read())) 235 | 236 | return 0 237 | 238 | 239 | if __name__ == '__main__': 240 | raise SystemExit(main()) 241 | -------------------------------------------------------------------------------- /day25/part1.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import os.path 5 | 6 | import pytest 7 | 8 | import support 9 | 10 | INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt') 11 | 12 | 13 | def compute(s: str) -> int: 14 | lines = s.splitlines() 15 | coords: dict[tuple[int, int], support.Direction4] = {} 16 | for y, line in enumerate(lines): 17 | for x, c in enumerate(line): 18 | if c == 'v': 19 | coords[(x, y)] = support.Direction4.DOWN 20 | elif c == '>': 21 | coords[(x, y)] = support.Direction4.RIGHT 22 | 23 | i = 0 24 | while True: 25 | i += 1 26 | 27 | new_coords_1: dict[tuple[int, int], support.Direction4] = {} 28 | for (x, y), direction in coords.items(): 29 | if direction is support.Direction4.RIGHT: 30 | new_x, new_y = direction.apply(x, y) 31 | new_x %= len(lines[0]) 32 | new_y %= len(lines) 33 | if (new_x, new_y) not in coords: 34 | new_coords_1[(new_x, new_y)] = direction 35 | else: 36 | new_coords_1[(x, y)] = direction 37 | else: 38 | new_coords_1[(x, y)] = direction 39 | 40 | new_coords_2: dict[tuple[int, int], support.Direction4] = {} 41 | for (x, y), direction in new_coords_1.items(): 42 | if direction is support.Direction4.DOWN: 43 | new_x, new_y = direction.apply(x, y) 44 | new_x %= len(lines[0]) 45 | new_y %= len(lines) 46 | if (new_x, new_y) not in new_coords_1: 47 | new_coords_2[(new_x, new_y)] = direction 48 | else: 49 | new_coords_2[(x, y)] = direction 50 | else: 51 | new_coords_2[(x, y)] = direction 52 | 53 | if new_coords_2 == coords: 54 | break 55 | else: 56 | coords = new_coords_2 57 | 58 | return i 59 | 60 | 61 | INPUT_S = '''\ 62 | v...>>.vv> 63 | .vv>>.vv.. 64 | >>.>v>...v 65 | >>v>>.>.v. 66 | v>v.vv.v.. 67 | >.>>..v... 68 | .vv..>.>v. 69 | v.v..>>v.v 70 | ....v..v.> 71 | ''' 72 | EXPECTED = 58 73 | 74 | 75 | @pytest.mark.parametrize( 76 | ('input_s', 'expected'), 77 | ( 78 | (INPUT_S, EXPECTED), 79 | ), 80 | ) 81 | def test(input_s: str, expected: int) -> None: 82 | assert compute(input_s) == expected 83 | 84 | 85 | def main() -> int: 86 | parser = argparse.ArgumentParser() 87 | parser.add_argument('data_file', nargs='?', default=INPUT_TXT) 88 | args = parser.parse_args() 89 | 90 | with open(args.data_file) as f, support.timing(): 91 | print(compute(f.read())) 92 | 93 | return 0 94 | 95 | 96 | if __name__ == '__main__': 97 | raise SystemExit(main()) 98 | -------------------------------------------------------------------------------- /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 | no_implicit_optional = true 9 | warn_redundant_casts = true 10 | warn_unused_ignores = true 11 | -------------------------------------------------------------------------------- /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.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import argparse 4 | import contextlib 5 | import enum 6 | import os.path 7 | import re 8 | import sys 9 | import time 10 | import urllib.error 11 | import urllib.parse 12 | import urllib.request 13 | from typing import Generator 14 | 15 | HERE = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | 18 | @contextlib.contextmanager 19 | def timing(name: str = '') -> Generator[None, None, None]: 20 | before = time.time() 21 | try: 22 | yield 23 | finally: 24 | after = time.time() 25 | t = (after - before) * 1000 26 | unit = 'ms' 27 | if t < 100: 28 | t *= 1000 29 | unit = 'μs' 30 | if name: 31 | name = f' ({name})' 32 | print(f'> {int(t)} {unit}{name}', file=sys.stderr, flush=True) 33 | 34 | 35 | def _get_cookie_headers() -> dict[str, str]: 36 | with open(os.path.join(HERE, '../.env')) as f: 37 | contents = f.read().strip() 38 | return {'Cookie': contents} 39 | 40 | 41 | def get_input(year: int, day: int) -> str: 42 | url = f'https://adventofcode.com/{year}/day/{day}/input' 43 | req = urllib.request.Request(url, headers=_get_cookie_headers()) 44 | return urllib.request.urlopen(req).read().decode() 45 | 46 | 47 | def get_year_day() -> tuple[int, int]: 48 | cwd = os.getcwd() 49 | day_s = os.path.basename(cwd) 50 | year_s = os.path.basename(os.path.dirname(cwd)) 51 | 52 | if not day_s.startswith('day') or not year_s.startswith('aoc'): 53 | raise AssertionError(f'unexpected working dir: {cwd}') 54 | 55 | return int(year_s[len('aoc'):]), int(day_s[len('day'):]) 56 | 57 | 58 | def download_input() -> int: 59 | parser = argparse.ArgumentParser() 60 | parser.parse_args() 61 | 62 | year, day = get_year_day() 63 | 64 | for i in range(5): 65 | try: 66 | s = get_input(year, day) 67 | except urllib.error.URLError as e: 68 | print(f'zzz: not ready yet: {e}') 69 | time.sleep(1) 70 | else: 71 | break 72 | else: 73 | raise SystemExit('timed out after attempting many times') 74 | 75 | with open('input.txt', 'w') as f: 76 | f.write(s) 77 | 78 | lines = s.splitlines() 79 | if len(lines) > 10: 80 | for line in lines[:10]: 81 | print(line) 82 | print('...') 83 | else: 84 | print(lines[0][:80]) 85 | print('...') 86 | 87 | return 0 88 | 89 | 90 | TOO_QUICK = re.compile('You gave an answer too recently.*to wait.') 91 | WRONG = re.compile(r"That's not the right answer.*?\.") 92 | RIGHT = "That's the right answer!" 93 | ALREADY_DONE = re.compile(r"You don't seem to be solving.*\?") 94 | 95 | 96 | def _post_answer(year: int, day: int, part: int, answer: int) -> str: 97 | params = urllib.parse.urlencode({'level': part, 'answer': answer}) 98 | req = urllib.request.Request( 99 | f'https://adventofcode.com/{year}/day/{day}/answer', 100 | method='POST', 101 | data=params.encode(), 102 | headers=_get_cookie_headers(), 103 | ) 104 | resp = urllib.request.urlopen(req) 105 | 106 | return resp.read().decode() 107 | 108 | 109 | def submit_solution() -> int: 110 | parser = argparse.ArgumentParser() 111 | parser.add_argument('--part', type=int, required=True) 112 | args = parser.parse_args() 113 | 114 | year, day = get_year_day() 115 | answer = int(sys.stdin.read()) 116 | 117 | print(f'answer: {answer}') 118 | 119 | contents = _post_answer(year, day, args.part, answer) 120 | 121 | for error_regex in (WRONG, TOO_QUICK, ALREADY_DONE): 122 | error_match = error_regex.search(contents) 123 | if error_match: 124 | print(f'\033[41m{error_match[0]}\033[m') 125 | return 1 126 | 127 | if RIGHT in contents: 128 | print(f'\033[42m{RIGHT}\033[m') 129 | return 0 130 | else: 131 | # unexpected output? 132 | print(contents) 133 | return 1 134 | 135 | 136 | def submit_25_pt2() -> int: 137 | parser = argparse.ArgumentParser() 138 | parser.parse_args() 139 | 140 | year, day = get_year_day() 141 | 142 | assert day == 25, day 143 | contents = _post_answer(year, day, part=2, answer=0) 144 | 145 | if 'Congratulations!' in contents: 146 | print('\033[42mCongratulations!\033[m') 147 | return 0 148 | else: 149 | print(contents) 150 | return 1 151 | 152 | 153 | def adjacent_4(x: int, y: int) -> Generator[tuple[int, int], None, None]: 154 | yield x, y - 1 155 | yield x + 1, y 156 | yield x, y + 1 157 | yield x - 1, y 158 | 159 | 160 | def adjacent_8(x: int, y: int) -> Generator[tuple[int, int], None, None]: 161 | for y_d in (-1, 0, 1): 162 | for x_d in (-1, 0, 1): 163 | if y_d == x_d == 0: 164 | continue 165 | yield x + x_d, y + y_d 166 | 167 | 168 | def parse_coords_int(s: str) -> dict[tuple[int, int], int]: 169 | coords = {} 170 | for y, line in enumerate(s.splitlines()): 171 | for x, c in enumerate(line): 172 | coords[(x, y)] = int(c) 173 | return coords 174 | 175 | 176 | def parse_coords_hash(s: str) -> set[tuple[int, int]]: 177 | coords = set() 178 | for y, line in enumerate(s.splitlines()): 179 | for x, c in enumerate(line): 180 | if c == '#': 181 | coords.add((x, y)) 182 | return coords 183 | 184 | 185 | def parse_numbers_split(s: str) -> list[int]: 186 | return [int(x) for x in s.split()] 187 | 188 | 189 | def parse_numbers_comma(s: str) -> list[int]: 190 | return [int(x) for x in s.strip().split(',')] 191 | 192 | 193 | def format_coords_hash(coords: set[tuple[int, int]]) -> str: 194 | min_x = min(x for x, _ in coords) 195 | max_x = max(x for x, _ in coords) 196 | min_y = min(y for _, y in coords) 197 | max_y = max(y for _, y in coords) 198 | return '\n'.join( 199 | ''.join( 200 | '#' if (x, y) in coords else ' ' 201 | for x in range(min_x, max_x + 1) 202 | ) 203 | for y in range(min_y, max_y + 1) 204 | ) 205 | 206 | 207 | def print_coords_hash(coords: set[tuple[int, int]]) -> None: 208 | print(format_coords_hash(coords)) 209 | 210 | 211 | class Direction4(enum.Enum): 212 | UP = (0, -1) 213 | RIGHT = (1, 0) 214 | DOWN = (0, 1) 215 | LEFT = (-1, 0) 216 | 217 | def __init__(self, x: int, y: int) -> None: 218 | self.x, self.y = x, y 219 | 220 | @property 221 | def _vals(self) -> tuple[Direction4, ...]: 222 | return tuple(type(self).__members__.values()) 223 | 224 | @property 225 | def cw(self) -> Direction4: 226 | vals = self._vals 227 | return vals[(vals.index(self) + 1) % len(vals)] 228 | 229 | @property 230 | def ccw(self) -> Direction4: 231 | vals = self._vals 232 | return vals[(vals.index(self) - 1) % len(vals)] 233 | 234 | @property 235 | def opposite(self) -> Direction4: 236 | vals = self._vals 237 | return vals[(vals.index(self) + 2) % len(vals)] 238 | 239 | def apply(self, x: int, y: int) -> tuple[int, int]: 240 | return self.x + x, self.y + y 241 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------