├── .gitignore ├── .vscode └── settings.json ├── README.md ├── advent ├── blog ├── .gitignore ├── .node-version ├── README.md ├── astro.config.mjs ├── justfile ├── package-lock.json ├── package.json ├── public │ ├── apple-icon-180.png │ ├── python-ascii-banner.png │ └── robots.txt ├── src │ ├── components │ │ ├── About.md │ │ ├── Breadcrumbs.astro │ │ ├── NavControls.astro │ │ └── Welcome.md │ ├── content │ │ ├── config.ts │ │ └── writeups │ │ │ ├── 2020 │ │ │ ├── 1 │ │ │ │ └── README.md │ │ │ ├── 2 │ │ │ │ └── README.md │ │ │ ├── 3 │ │ │ │ └── README.md │ │ │ ├── 4 │ │ │ │ └── README.md │ │ │ ├── 5 │ │ │ │ └── README.md │ │ │ ├── 6 │ │ │ │ └── README.md │ │ │ ├── 7 │ │ │ │ └── README.md │ │ │ ├── 8 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── dataclass-init.png │ │ │ ├── 9 │ │ │ │ └── README.md │ │ │ ├── 10 │ │ │ │ └── README.md │ │ │ ├── 11 │ │ │ │ └── README.md │ │ │ ├── 12 │ │ │ │ └── README.md │ │ │ ├── 13 │ │ │ │ └── README.md │ │ │ ├── 14 │ │ │ │ └── README.md │ │ │ ├── 15 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── running.gif │ │ │ ├── 16 │ │ │ │ └── README.md │ │ │ ├── 17 │ │ │ │ └── README.md │ │ │ ├── 18 │ │ │ │ └── README.md │ │ │ ├── 19 │ │ │ │ └── README.md │ │ │ ├── 20 │ │ │ │ └── README.md │ │ │ ├── 21 │ │ │ │ └── README.md │ │ │ ├── 22 │ │ │ │ └── README.md │ │ │ ├── 23 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ ├── finish.png │ │ │ │ │ ├── pickup.png │ │ │ │ │ └── start.png │ │ │ ├── 24 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── hexagons.png │ │ │ └── 25 │ │ │ │ └── README.md │ │ │ ├── 2021 │ │ │ ├── 1 │ │ │ │ └── README.md │ │ │ ├── 2 │ │ │ │ └── README.md │ │ │ ├── 3 │ │ │ │ └── README.md │ │ │ ├── 4 │ │ │ │ └── README.md │ │ │ ├── 5 │ │ │ │ └── README.md │ │ │ ├── 6 │ │ │ │ └── README.md │ │ │ ├── 7 │ │ │ │ └── README.md │ │ │ ├── 8 │ │ │ │ └── README.md │ │ │ ├── 9 │ │ │ │ └── README.md │ │ │ ├── 10 │ │ │ │ └── README.md │ │ │ ├── 11 │ │ │ │ └── README.md │ │ │ ├── 12 │ │ │ │ └── README.md │ │ │ ├── 13 │ │ │ │ └── README.md │ │ │ ├── 14 │ │ │ │ └── README.md │ │ │ ├── 15 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── animation.gif │ │ │ ├── 16 │ │ │ │ └── README.md │ │ │ ├── 17 │ │ │ │ └── README.md │ │ │ ├── 18 │ │ │ │ └── README.md │ │ │ ├── 19 │ │ │ │ └── README.md │ │ │ ├── 20 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── meme.png │ │ │ ├── 21 │ │ │ │ └── README.md │ │ │ ├── 22 │ │ │ │ └── README.md │ │ │ ├── 23 │ │ │ │ └── README.md │ │ │ ├── 24 │ │ │ │ └── README.md │ │ │ └── 25 │ │ │ │ └── README.md │ │ │ ├── 2022 │ │ │ ├── 1 │ │ │ │ └── README.md │ │ │ ├── 2 │ │ │ │ └── README.md │ │ │ ├── 3 │ │ │ │ └── README.md │ │ │ ├── 4 │ │ │ │ └── README.md │ │ │ ├── 5 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── rotate-meme.png │ │ │ ├── 6 │ │ │ │ └── README.md │ │ │ ├── 7 │ │ │ │ └── README.md │ │ │ ├── 8 │ │ │ │ └── README.md │ │ │ ├── 9 │ │ │ │ └── README.md │ │ │ ├── 10 │ │ │ │ └── README.md │ │ │ ├── 11 │ │ │ │ └── README.md │ │ │ ├── 12 │ │ │ │ └── README.md │ │ │ ├── 13 │ │ │ │ └── README.md │ │ │ ├── 14 │ │ │ │ └── README.md │ │ │ ├── 15 │ │ │ │ └── README.md │ │ │ ├── 16 │ │ │ │ └── README.md │ │ │ ├── 17 │ │ │ │ └── README.md │ │ │ ├── 18 │ │ │ │ └── README.md │ │ │ ├── 19 │ │ │ │ └── README.md │ │ │ ├── 20 │ │ │ │ └── README.md │ │ │ ├── 21 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ └── wolfram.png │ │ │ ├── 22 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ │ ├── ascii-map.png │ │ │ │ │ ├── cube-faces-1.png │ │ │ │ │ ├── flag-cube.png │ │ │ │ │ └── rotations.png │ │ │ ├── 23 │ │ │ │ └── README.md │ │ │ ├── 24 │ │ │ │ └── README.md │ │ │ └── 25 │ │ │ │ ├── README.md │ │ │ │ └── images │ │ │ │ └── base-5-conversions.png │ │ │ ├── 2023 │ │ │ ├── 1 │ │ │ │ └── README.md │ │ │ ├── 2 │ │ │ │ └── index.md │ │ │ ├── 3 │ │ │ │ └── index.md │ │ │ ├── 4 │ │ │ │ └── index.md │ │ │ ├── 5 │ │ │ │ └── index.md │ │ │ ├── 6 │ │ │ │ └── index.md │ │ │ ├── 7 │ │ │ │ └── index.md │ │ │ ├── 8 │ │ │ │ └── index.md │ │ │ ├── 9 │ │ │ │ └── index.md │ │ │ ├── 10 │ │ │ │ └── index.md │ │ │ ├── 11 │ │ │ │ └── index.md │ │ │ ├── 12 │ │ │ │ └── index.md │ │ │ ├── 13 │ │ │ │ └── index.md │ │ │ ├── 14 │ │ │ │ └── index.md │ │ │ ├── 15 │ │ │ │ └── index.md │ │ │ ├── 16 │ │ │ │ └── index.md │ │ │ ├── 17 │ │ │ │ └── index.md │ │ │ ├── 18 │ │ │ │ └── index.md │ │ │ ├── 19 │ │ │ │ ├── images │ │ │ │ │ ├── ranges-sankey.png │ │ │ │ │ ├── type-part-1.png │ │ │ │ │ ├── type-part-2.png │ │ │ │ │ └── workflows-flowchart.png │ │ │ │ └── index.md │ │ │ ├── 20 │ │ │ │ ├── images │ │ │ │ │ └── mermaid.svg │ │ │ │ └── index.md │ │ │ ├── 21 │ │ │ │ ├── images │ │ │ │ │ ├── growing.png │ │ │ │ │ ├── one-step-grid-corners.png │ │ │ │ │ └── one-step-grid.png │ │ │ │ └── index.md │ │ │ ├── 22 │ │ │ │ └── index.md │ │ │ ├── 23 │ │ │ │ └── index.md │ │ │ ├── 24 │ │ │ │ └── index.md │ │ │ └── 25 │ │ │ │ └── index.md │ │ │ └── 2024 │ │ │ ├── 1 │ │ │ └── index.md │ │ │ ├── 2 │ │ │ └── index.md │ │ │ ├── 3 │ │ │ └── index.md │ │ │ ├── 4 │ │ │ └── index.md │ │ │ ├── 5 │ │ │ └── index.md │ │ │ ├── 6 │ │ │ └── index.md │ │ │ ├── 7 │ │ │ └── index.md │ │ │ ├── 8 │ │ │ └── index.md │ │ │ ├── 9 │ │ │ └── index.md │ │ │ ├── 10 │ │ │ └── index.md │ │ │ ├── 11 │ │ │ └── index.md │ │ │ ├── 12 │ │ │ └── index.md │ │ │ ├── 13 │ │ │ └── index.md │ │ │ ├── 14 │ │ │ ├── images │ │ │ │ ├── highlighted.png │ │ │ │ ├── pages.png │ │ │ │ └── tree.png │ │ │ └── index.md │ │ │ └── 15 │ │ │ └── index.md │ ├── env.d.ts │ ├── layouts │ │ └── Layout.astro │ ├── pages │ │ ├── index.astro │ │ ├── rss.xml.ts │ │ └── writeups │ │ │ ├── [...slug].astro │ │ │ ├── [year].astro │ │ │ └── index.astro │ └── utils │ │ ├── collections.ts │ │ ├── pathTools.test.ts │ │ └── pathTools.ts ├── tsconfig.json └── vitest.config.ts ├── justfile ├── misc ├── date_utils.py ├── example_solution.py.tmpl └── move_readmes.py ├── pyproject.toml ├── requirements.txt ├── solutions ├── 2017 │ ├── day_01 │ │ └── solution.py │ ├── day_02 │ │ └── solution.py │ ├── day_03 │ │ └── solution.py │ ├── day_04 │ │ └── solution.py │ ├── day_05 │ │ └── solution.py │ ├── day_06 │ │ └── solution.py │ ├── day_07 │ │ └── solution.py │ ├── day_08 │ │ └── solution.py │ ├── day_09 │ │ └── solution.py │ ├── day_10 │ │ └── solution.py │ ├── day_11 │ │ └── solution.py │ ├── day_12 │ │ └── solution.py │ ├── day_13 │ │ └── solution.py │ ├── day_14 │ │ └── solution.py │ ├── day_15 │ │ └── solution.py │ ├── day_16 │ │ └── solution.py │ ├── day_17 │ │ └── solution.py │ └── day_18 │ │ └── solution.py ├── 2018 │ ├── day_01 │ │ └── solution.py │ ├── day_02 │ │ └── solution.py │ ├── day_03 │ │ └── solution.py │ ├── day_04 │ │ └── solution.py │ ├── day_05 │ │ └── solution.py │ ├── day_06 │ │ └── solution.py │ ├── day_07 │ │ └── solution.py │ └── day_08 │ │ └── solution.py ├── 2019 │ ├── __init__.py │ ├── day_01 │ │ └── solution.py │ ├── day_02 │ │ └── solution.py │ ├── day_03 │ │ └── solution.py │ ├── day_04 │ │ └── solution.py │ ├── day_05 │ │ └── solution.py │ ├── day_06 │ │ └── solution.py │ ├── day_07 │ │ └── solution.py │ ├── day_08 │ │ └── solution.py │ ├── day_09 │ │ └── solution.py │ ├── day_10 │ │ └── solution.py │ ├── day_11 │ │ └── solution.py │ ├── day_12 │ │ └── solution.py │ ├── day_13 │ │ └── solution.py │ ├── day_14 │ │ └── solution.py │ ├── day_15 │ │ └── solution.py │ ├── day_16 │ │ └── solution.py │ ├── day_17 │ │ └── solution.py │ ├── day_19 │ │ └── solution.py │ ├── day_22 │ │ └── solution.py │ ├── day_23 │ │ └── solution.py │ ├── day_24 │ │ └── solution.py │ ├── day_25 │ │ ├── mem_dump.py │ │ └── solution.py │ └── intcode.py ├── 2020 │ ├── day_01 │ │ ├── README.md │ │ └── solution.py │ ├── day_02 │ │ ├── README.md │ │ └── solution.py │ ├── day_03 │ │ ├── README.md │ │ └── solution.py │ ├── day_04 │ │ ├── README.md │ │ └── solution.py │ ├── day_05 │ │ ├── README.md │ │ └── solution.py │ ├── day_06 │ │ ├── README.md │ │ └── solution.py │ ├── day_07 │ │ ├── README.md │ │ └── solution.py │ ├── day_08 │ │ ├── README.md │ │ └── solution.py │ ├── day_09 │ │ ├── README.md │ │ └── solution.py │ ├── day_10 │ │ ├── README.md │ │ └── solution.py │ ├── day_11 │ │ ├── README.md │ │ └── solution.py │ ├── day_12 │ │ ├── README.md │ │ └── solution.py │ ├── day_13 │ │ ├── README.md │ │ └── solution.py │ ├── day_14 │ │ ├── README.md │ │ └── solution.py │ ├── day_15 │ │ ├── README.md │ │ └── solution.py │ ├── day_16 │ │ ├── README.md │ │ └── solution.py │ ├── day_17 │ │ ├── README.md │ │ └── solution.py │ ├── day_18 │ │ ├── README.md │ │ └── solution.py │ ├── day_19 │ │ ├── README.md │ │ └── solution.py │ ├── day_20 │ │ ├── README.md │ │ └── solution.py │ ├── day_21 │ │ ├── README.md │ │ └── solution.py │ ├── day_22 │ │ ├── README.md │ │ └── solution.py │ ├── day_23 │ │ ├── README.md │ │ └── solution.py │ ├── day_24 │ │ ├── README.md │ │ └── solution.py │ └── day_25 │ │ ├── README.md │ │ └── solution.py ├── 2021 │ ├── day_01 │ │ ├── README.md │ │ └── solution.py │ ├── day_02 │ │ ├── README.md │ │ └── solution.py │ ├── day_03 │ │ ├── README.md │ │ └── solution.py │ ├── day_04 │ │ ├── README.md │ │ └── solution.py │ ├── day_05 │ │ ├── README.md │ │ └── solution.py │ ├── day_06 │ │ ├── README.md │ │ └── solution.py │ ├── day_07 │ │ ├── README.md │ │ └── solution.py │ ├── day_08 │ │ ├── README.md │ │ └── solution.py │ ├── day_09 │ │ ├── README.md │ │ └── solution.py │ ├── day_10 │ │ ├── README.md │ │ └── solution.py │ ├── day_11 │ │ ├── README.md │ │ └── solution.py │ ├── day_12 │ │ ├── README.md │ │ └── solution.py │ ├── day_13 │ │ ├── README.md │ │ └── solution.py │ ├── day_14 │ │ ├── README.md │ │ └── solution.py │ ├── day_15 │ │ ├── README.md │ │ └── solution.py │ ├── day_16 │ │ ├── README.md │ │ └── solution.py │ ├── day_17 │ │ ├── README.md │ │ └── solution.py │ ├── day_18 │ │ ├── README.md │ │ └── solution.py │ ├── day_19 │ │ ├── README.md │ │ └── solution.py │ ├── day_20 │ │ ├── README.md │ │ └── solution.py │ ├── day_21 │ │ ├── README.md │ │ └── solution.py │ ├── day_22 │ │ ├── README.md │ │ └── solution.py │ ├── day_23 │ │ ├── README.md │ │ └── solution.py │ ├── day_24 │ │ ├── README.md │ │ └── solution.py │ └── day_25 │ │ ├── README.md │ │ └── solution.py ├── 2022 │ ├── day_01 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_02 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_03 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_04 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_05 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_06 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_07 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_08 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_09 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_10 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_11 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_12 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_13 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_14 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_15 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_16 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_17 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_18 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_19 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_20 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_21 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_22 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_23 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_24 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ └── day_25 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py ├── 2023 │ ├── day_01 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_02 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_03 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_04 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_05 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_06 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_07 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_08 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_09 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_10 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_11 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_12 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_13 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_14 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_15 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_16 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_17 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_18 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_19 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_20 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_21 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_22 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_23 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_24 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ └── day_25 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py ├── 2024 │ ├── day_01 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_02 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_03 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_04 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_05 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_06 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_07 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_08 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_09 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_10 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_11 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_12 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_13 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ ├── day_14 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py │ └── day_15 │ │ ├── README.md │ │ ├── input.test.txt │ │ └── solution.py ├── base.py └── utils │ ├── graphs.py │ └── transformations.py └── start /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # End of https://www.gitignore.io/api/python 107 | 108 | # don't include actual puzzle inputs or misc output files 109 | input.txt 110 | output.txt 111 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // I gitignore my puzzle inputs so they're not shared, 3 | // but I want to see them in VSCode 4 | // any generated files I legit need to ignore will need to be added via 5 | // "files.exclude" 6 | "explorer.excludeGitIgnore": false, 7 | "search.useIgnoreFiles": false, 8 | "files.exclude": { 9 | ".ruff_cache": true, 10 | "blog/.astro": true, 11 | "blog/dist": true 12 | }, 13 | "markdown.copyFiles.destination": { 14 | // the trailing slash is important here! 15 | // also, images need to be at `./${documentDirName}`, but the `.` gets replaced with the full path 16 | // there's probably a way to improve that, but I didn't want to fiddle with VSCode snippet expansion 17 | "*": "${documentDirName}/images/" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # David's Python Advent of Code Solutions 2 | 3 | This repo is a collection of my [Advent of Code](https://adventofcode.com/) solutions written using modern Python. It's got working code, helpful utilities, and [step-by-step explanations](https://advent-of-code.xavd.id) for many puzzles. Note that it doesn't include my puzzle inputs, per [a request from the creator](https://old.reddit.com/r/adventofcode/wiki/troubleshooting/no_asking_for_inputs). 4 | 5 | The base class and all the helpers are [available as a GitHub template here](https://github.com/xavdid/advent-of-code-python-template), if you'd like to use them as well. That repo has documentation for the included programs and Python classes, as well as the [CHANGELOG](https://github.com/xavdid/advent-of-code-python-template/blob/main/CHANGELOG.md) for this project. 6 | 7 | ## Solutions 8 | 9 | Starting in 2020, I started writing a daily step-by-step writeup to explain my solution to the day's puzzle. They now live on a standalone blog: https://advent-of-code.xavd.id. There's more info about my approach to puzzle solving there. 10 | 11 | ## Feedback 12 | 13 | Feedback is encouraged and welcomed! Feel free to open [an issue](https://github.com/xavdid/advent-of-code/issues) on this repo or [get in touch elsewhere](https://xavd.id/contact/). If something could be explained better or is plain wrong, I'd love to hear about it. 14 | -------------------------------------------------------------------------------- /blog/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | -------------------------------------------------------------------------------- /blog/.node-version: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /blog/README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code Blog 2 | 3 | This is a static site, built with [Astro](https://astro.build/), that houses my step-by-step explanations of Advent of Code puzzles. You can see it live at https://advent-of-code.xavd.id. 4 | 5 | ## Attributions 6 | 7 | SEO Image from [the Python Discord](https://github.com/python-discord/branding/blob/cd0eedc98ffdaa1d0d8689e6e1205fe2969b3c5f/events/christmas/banners/banner.png), used under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). 8 | -------------------------------------------------------------------------------- /blog/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { rehypeHeadingIds } from "@astrojs/markdown-remark"; 2 | import sitemap from "@astrojs/sitemap"; 3 | import expressiveCode from "astro-expressive-code"; 4 | import { defineConfig } from "astro/config"; 5 | import rehypeAutolinkHeadings from "rehype-autolink-headings"; 6 | import rehypeExternalLinks from "rehype-external-links"; 7 | 8 | // https://astro.build/config 9 | export default defineConfig({ 10 | site: "https://advent-of-code.xavd.id", 11 | markdown: { 12 | rehypePlugins: [ 13 | rehypeHeadingIds, 14 | [ 15 | rehypeExternalLinks, 16 | { 17 | target: "_blank", 18 | // content: { 19 | // type: "element", 20 | // tagName: "span", 21 | // properties: { className: ["external-link-icon"] }, 22 | // children: [{ type: "text", value: " ↗" }], 23 | // }, 24 | }, 25 | ], 26 | [ 27 | rehypeAutolinkHeadings, 28 | { 29 | behavior: "wrap", 30 | }, 31 | ], 32 | ], 33 | }, 34 | integrations: [ 35 | sitemap(), 36 | expressiveCode({ 37 | themes: ["monokai"], 38 | frames: false, 39 | styleOverrides: { 40 | borderColor: "white", 41 | borderWidth: "1px", 42 | codeLineHeight: "1.4", 43 | }, 44 | }), 45 | ], 46 | }); 47 | -------------------------------------------------------------------------------- /blog/justfile: -------------------------------------------------------------------------------- 1 | # make installed binaries available at the top level 2 | export PATH := "./node_modules/.bin:" + env_var('PATH') 3 | 4 | @_default: 5 | just --list 6 | 7 | @dev: 8 | astro dev 9 | 10 | # general purpose handler for anstro commands 11 | @astro *options="": 12 | astro {{options}} 13 | 14 | # to any checks to ensure the site is ready to go 15 | @validate: 16 | astro check 17 | # don't need to test, since the only test so far is for an unused function 18 | # just test 19 | # eslint? 20 | 21 | # do a production build 22 | @build: validate 23 | astro build 24 | 25 | @test: 26 | vitest run 27 | 28 | @test-watch: 29 | vitest watch 30 | -------------------------------------------------------------------------------- /blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aoc-blog", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "dependencies": { 6 | "@astrojs/check": "0.3.1", 7 | "@astrojs/rss": "^3.0.0", 8 | "@astrojs/sitemap": "^3.0.1", 9 | "astro": "^3.3.0", 10 | "astro-expressive-code": "^0.29.2", 11 | "astro-seo": "0.8.0", 12 | "rehype-autolink-headings": "^7.0.0", 13 | "rehype-external-links": "^3.0.0", 14 | "typescript": "^5.2.2", 15 | "water.css": "^2.1.1" 16 | }, 17 | "devDependencies": { 18 | "just-install": "^1.0.11", 19 | "vitest": "^0.34.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /blog/public/apple-icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/public/apple-icon-180.png -------------------------------------------------------------------------------- /blog/public/python-ascii-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/public/python-ascii-banner.png -------------------------------------------------------------------------------- /blog/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Sitemap: https://advent-of-code.xavd.id/sitemap-index.xml 5 | -------------------------------------------------------------------------------- /blog/src/components/Breadcrumbs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | type Props = { 3 | page: string | number; // final, unlinked segment 4 | path?: string[]; 5 | }; 6 | 7 | const { page, path } = Astro.props; 8 | --- 9 | 10 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /blog/src/components/Welcome.md: -------------------------------------------------------------------------------- 1 | **Welcome!** This site houses one of my favorite projects: my step-by-step [Advent of Code](https://adventofcode.com) explanations. They're written in [Python](https://www.python.org/) and use [my custom solution template](https://github.com/xavdid/advent-of-code-python-template) (which handles input parsing, among other tasks). Dive right in by picking a year, or read on to learn more about the project. 2 | -------------------------------------------------------------------------------- /blog/src/content/config.ts: -------------------------------------------------------------------------------- 1 | // 1. Import utilities from `astro:content` 2 | import { defineCollection, z } from "astro:content"; 3 | 4 | const isProdBuild = import.meta.env.PROD; 5 | 6 | const prodOnlyRule = (c: boolean) => (isProdBuild ? c : true); 7 | 8 | // 2. Define a `type` and `schema` for each collection 9 | const writeups = defineCollection({ 10 | type: "content", 11 | schema: z.object({ 12 | title: z.string().refine((t) => prodOnlyRule(t !== "TKTK"), { 13 | message: "Missing post title during production build", 14 | }), 15 | day: z.number().gte(1).lte(25), 16 | year: z.number().gte(2015), 17 | // on newer Astro versions, can use z.string().date().optional() 18 | pub_date: z.optional(z.string().regex(/^\d{4}-\d{2}-\d{2}$/)), 19 | concepts: z.optional(z.array(z.string())), // basically true or undefined 20 | }), 21 | }); 22 | 23 | // 3. Export a single `collections` object to register your collection(s) 24 | export const collections = { writeups }; 25 | -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/1/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | year: 2020 3 | day: 1 4 | title: "Report Repair" 5 | slug: "2020/day/1" 6 | pub_date: "2020-12-01" 7 | --- 8 | 9 | ## Part 1 10 | 11 | The best season is finally upon us! It's Advent of Code time. Luckily, we start on a straightforward note. 12 | 13 | We're looking for a sum of 2020, so for each item in the list, we know exactly what our matching number will be. So, we can iterate through the list and return the result when we find the match: 14 | 15 | ```py 16 | for amount in self.input: 17 | target = 2020 - amount 18 | if target in self.input: 19 | return amount * target 20 | ``` 21 | 22 | The above uses `self.input` from the helper class in this repo, so your code will look a little different than mine, but the core idea is the same. 23 | 24 | This could be sped up by removing the `target in self.input` call, which has to look through the whole list every time. But, Python is fast and the program completed basically instantly, so I'm not worried about it. 25 | 26 | ## Part 2 27 | 28 | This looks a lot like part 1, but we need to look at each pair of numbers in the list and see if their third is present. Luckily, Python has a method that will do the hard part for us: `itertools.combinations`. It takes an iterable (aka anything that can be iterated over) and returns every possible pair of items. From [the docs](https://docs.python.org/3/library/itertools.html#itertools.combinations): 29 | 30 | ```py 31 | from itertools import combinations 32 | list(combinations('ABCD', 2)) 33 | # [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')] 34 | ``` 35 | 36 | Order doesn't matter, so `('B', 'A')` isn't in the list (it's the same as `('A', 'B')`). 37 | 38 | This lets us write basically the same loop from part 1 where we know exactly what number we need each time: 39 | 40 | ```py 41 | for a, b in combinations(self.input, 2): 42 | target = 2020 - a - b 43 | if target in self.input: 44 | return a * b * target 45 | ``` 46 | -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/15/images/running.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/15/images/running.gif -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/23/images/finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/23/images/finish.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/23/images/pickup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/23/images/pickup.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/23/images/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/23/images/start.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/24/images/hexagons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/24/images/hexagons.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/3/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | year: 2020 3 | day: 3 4 | title: "Toboggan Trajectory" 5 | slug: "2020/day/3" 6 | pub_date: "2020-12-03" 7 | --- 8 | 9 | ## Part 1 10 | 11 | This is simpler than it looked at first glance. We only look at each row once and we need to loop around when the reach the end of the list. The modulo operator (`%`) is perfect for this: 12 | 13 | ```py 14 | trees = 0 15 | width = len(self.input[0]) # only need to calculate once 16 | for index, line in enumerate(self.input): 17 | if line[(index * 3) % width] == "#": 18 | trees += 1 19 | return trees 20 | ``` 21 | 22 | ## Part 2 23 | 24 | At first I thought part 2 was as easy as making my function generic and adding a skip if `down > 1`: 25 | 26 | ```py 27 | def count_trees(self, right: int, down: int) -> int: 28 | width = len(self.input[0]) 29 | trees = 0 30 | 31 | for index, line in enumerate(self.input): 32 | if down > 1 and index % down != 0: 33 | continue 34 | 35 | if line[(index * right) % width] == "#": 36 | trees += 1 37 | return trees 38 | ``` 39 | 40 | For the example, this yielded `[2, 7, 3, 4, 1]` which is _nearly_ right. We got `1` for `Right 1, Down 2` instead of `2`. Previously, we could use `index` to see how many lines we've looked at, but now that we're skipping some lines, we'll have to account for that in our indexing: 41 | 42 | ```py 43 | def count_trees(self, right: int, down: int) -> int: 44 | width = len(self.input[0]) 45 | trees = 0 46 | 47 | for index, line in enumerate(self.input): 48 | if down > 1 and index % down != 0: 49 | continue 50 | 51 | if line[(index // down * right) % width] == "#": 52 | trees += 1 53 | 54 | return trees 55 | ``` 56 | 57 | This gives us a nice generic function that we can fulfill both parts of the puzzle with! 58 | -------------------------------------------------------------------------------- /blog/src/content/writeups/2020/8/images/dataclass-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2020/8/images/dataclass-init.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2021/15/images/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2021/15/images/animation.gif -------------------------------------------------------------------------------- /blog/src/content/writeups/2021/20/images/meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2021/20/images/meme.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/21/images/wolfram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/21/images/wolfram.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/22/images/ascii-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/22/images/ascii-map.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/22/images/cube-faces-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/22/images/cube-faces-1.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/22/images/flag-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/22/images/flag-cube.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/22/images/rotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/22/images/rotations.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/25/images/base-5-conversions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/25/images/base-5-conversions.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2022/5/images/rotate-meme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2022/5/images/rotate-meme.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/19/images/ranges-sankey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/19/images/ranges-sankey.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/19/images/type-part-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/19/images/type-part-1.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/19/images/type-part-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/19/images/type-part-2.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/19/images/workflows-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/19/images/workflows-flowchart.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/21/images/growing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/21/images/growing.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/21/images/one-step-grid-corners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/21/images/one-step-grid-corners.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2023/21/images/one-step-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2023/21/images/one-step-grid.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2024/14/images/highlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2024/14/images/highlighted.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2024/14/images/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2024/14/images/pages.png -------------------------------------------------------------------------------- /blog/src/content/writeups/2024/14/images/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/blog/src/content/writeups/2024/14/images/tree.png -------------------------------------------------------------------------------- /blog/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /blog/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Layout.astro"; 3 | import { Content as WelcomeContent } from "../components/Welcome.md"; 4 | import { Content as AboutContent } from "../components/About.md"; 5 | 6 | import { getWriteupsByYear } from "../utils/collections"; 7 | 8 | const NUM_RECENT_POSTS = 3; 9 | 10 | const posts = await getWriteupsByYear(); 11 | 12 | const recentYears = [...posts.keys()] 13 | .toSorted((a, b) => b - a) 14 | .slice(0, NUM_RECENT_POSTS); 15 | const thisYearsPosts = posts.get(recentYears[0]); 16 | const showEllipse = Number(thisYearsPosts?.length) > NUM_RECENT_POSTS; 17 | const newestPosts = thisYearsPosts?.toSorted((a,b) => a.data.day - b.data.day).slice(-NUM_RECENT_POSTS); 18 | --- 19 | 20 | 21 | 22 |
~
23 | └── writeups
24 | {recentYears.map((year, index) => (
25 |   <>
26 |     {"    ├── "}
27 |     {year}
{ 28 | index === 0 && 29 | ( 30 | <> 31 | {showEllipse &&
{" | ├── ..."}
} 32 | {newestPosts?.map((post, postIndex) => ( 33 | <> 34 | {postIndex === newestPosts.length-1 ? " | └── " : " | ├── "} 35 | Day {post.data.day} 36 |
37 | ))} 38 | 39 | ) 40 | } 41 | 42 | ))}{" └── ..."}
43 | 44 |
45 | -------------------------------------------------------------------------------- /blog/src/pages/rss.xml.ts: -------------------------------------------------------------------------------- 1 | import rss from "@astrojs/rss"; 2 | import type { APIRoute } from "astro"; 3 | 4 | import { getPublishedWriteups } from "../utils/collections"; 5 | 6 | export const GET: APIRoute = async (context) => { 7 | const writeups = await getPublishedWriteups(); 8 | 9 | return rss({ 10 | title: "@xavdid does Advent of Code", 11 | description: 12 | "Step-by-step puzzle explanations, written in Python, for Advent of Code puzzles.", 13 | site: context.site!, 14 | items: writeups 15 | .sort( 16 | (a, b) => 17 | // these are only the published posts 18 | new Date(b.data.pub_date!).valueOf() - 19 | new Date(a.data.pub_date!).valueOf() || 20 | // presume I did these in order in case of ties 21 | b.data.day - a.data.day 22 | ) 23 | .map(({ slug, data: { year, day, title, pub_date } }) => { 24 | return { 25 | title: `David's AoC ${year} Day ${day} Solution`, 26 | link: `/writeups/${slug}`, 27 | description: `David solves: ${title}`, 28 | pubDate: new Date(pub_date!), 29 | }; 30 | }), 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /blog/src/pages/writeups/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { CollectionEntry } from "astro:content"; 3 | 4 | import { getPublishedWriteups } from "../../utils/collections"; 5 | import BaseLayout from "../../layouts/Layout.astro"; 6 | import Breadcrumbs from "../../components/Breadcrumbs.astro"; 7 | import NavControls from "../../components/NavControls.astro"; 8 | 9 | export const getStaticPaths = async () => { 10 | const writeups = await getPublishedWriteups(); 11 | return writeups.map(({ slug, ...writeup }) => ({ 12 | params: { slug }, 13 | props: writeup, 14 | })); 15 | }; 16 | type Props = CollectionEntry<"writeups">; 17 | 18 | const writeup = Astro.props; 19 | const { Content } = await writeup.render(); 20 | const { 21 | data: { year, day, title, pub_date }, 22 | } = Astro.props; 23 | 24 | const writeupTitle = `Advent of Code ${year} Day ${day}: ${title}`; 25 | --- 26 | 27 | 35 | 36 |
37 |

38 | {title} 39 |

40 | 41 |
42 | Published: {pub_date || "TBD"} 43 | Original Prompt 46 | 47 |
48 | 49 | 50 |
51 |
52 | 53 |
54 | 55 | 64 | -------------------------------------------------------------------------------- /blog/src/pages/writeups/[year].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { type CollectionEntry } from "astro:content"; 3 | 4 | import BaseLayout from "../../layouts/Layout.astro"; 5 | import Breadcrumbs from "../../components/Breadcrumbs.astro"; 6 | import { getWriteupsByYear } from "../../utils/collections"; 7 | 8 | type Writeup = CollectionEntry<"writeups">; 9 | type Props = { year: number; writeups: Writeup[] }; 10 | 11 | // which pages to create and what data to pass to each page 12 | // this has to be typed separately until https://github.com/withastro/compiler/issues/554 is fixed 13 | type StaticPathResult = () => Promise< 14 | { params: { year: number }; props: Props }[] 15 | >; 16 | 17 | export const getStaticPaths: StaticPathResult = async () => { 18 | const years = await getWriteupsByYear(); 19 | 20 | return [...years.entries()].map(([year, writeups]) => ({ 21 | params: { year }, 22 | props: { 23 | year, 24 | writeups: writeups.toSorted((a, b) => a.data.day - b.data.day), 25 | }, 26 | })); 27 | }; 28 | 29 | const { year, writeups } = Astro.props; 30 | --- 31 | 32 | 38 | 39 |

40 | {year}'s Writeups 41 |

42 |
    43 | { 44 | writeups.map(({ data: { day, title } }) => ( 45 |
  • 46 | Day {day}: {title} 47 |
  • 48 | )) 49 | } 50 |
51 |
52 | -------------------------------------------------------------------------------- /blog/src/pages/writeups/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseLayout from "../../layouts/Layout.astro"; 3 | import Breadcrumbs from "../../components/Breadcrumbs.astro"; 4 | import { type Writeup, getWriteupsByYear } from "../../utils/collections"; 5 | 6 | type Props = { year: number; writeups: Writeup[] }; 7 | 8 | // which pages to create and what data to pass to each page 9 | const loadWriteups: () => Promise = async () => { 10 | const writeups = await getWriteupsByYear(); 11 | return [...writeups.entries()] 12 | .map(([year, writeups]) => ({ 13 | year, 14 | writeups: writeups.toSorted((a, b) => a.data.day - b.data.day), 15 | })) 16 | .sort((a, b) => b.year - a.year); 17 | }; 18 | 19 | const writeups = await loadWriteups(); 20 | --- 21 | 22 | 28 | 29 |

All Writeups

30 | { 31 | writeups.map(({ year, writeups }) => ( 32 | <> 33 |

34 | 35 | {" "} 36 | {year} 37 | 38 |

39 |
    40 | {writeups.map(({ data: { day, title } }) => ( 41 |
  • 42 | Day {day}: {title} 43 |
  • 44 | ))} 45 |
46 | 47 | )) 48 | } 49 |
50 | 51 | 56 | -------------------------------------------------------------------------------- /blog/src/utils/collections.ts: -------------------------------------------------------------------------------- 1 | import { getCollection, type CollectionEntry } from "astro:content"; 2 | 3 | export type Writeup = CollectionEntry<"writeups">; 4 | 5 | // https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries 6 | // everything in dev, published only in prod 7 | export const getPublishedWriteups = async () => 8 | getCollection("writeups", ({ data: { pub_date } }) => 9 | import.meta.env.PROD ? pub_date : true 10 | ); 11 | 12 | export const getWriteupsByYear = async () => { 13 | const writeups = await getPublishedWriteups(); 14 | 15 | return writeups.reduce((result, writeup) => { 16 | if (!result.has(writeup.data.year)) { 17 | result.set(writeup.data.year, []); 18 | } 19 | 20 | result.get(writeup.data.year)?.push(writeup); 21 | 22 | return result; 23 | }, new Map()); 24 | }; 25 | -------------------------------------------------------------------------------- /blog/src/utils/pathTools.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { pathToBreadcrumbs } from "./pathTools"; 3 | 4 | test("non-markdown pages have no breadcrumbs", () => { 5 | const { page, path } = pathToBreadcrumbs(); 6 | 7 | expect(page).toEqual(""); 8 | expect(path).toBeUndefined(); 9 | }); 10 | 11 | test("homepage has no breadcrumbs", () => { 12 | const { page, path } = pathToBreadcrumbs("/"); 13 | 14 | expect(page).toEqual(""); 15 | expect(path).toBeUndefined(); 16 | }); 17 | 18 | test("about page has a path", () => { 19 | const { page, path } = pathToBreadcrumbs("/about"); 20 | 21 | expect(page).toEqual("about"); 22 | expect(path).toBeUndefined(); 23 | }); 24 | 25 | test("writeup with year would have a path", () => { 26 | const { page, path } = pathToBreadcrumbs("/writeups/2022"); 27 | 28 | expect(page).toEqual("2022"); 29 | expect(path).toEqual(["writeups"]); 30 | }); 31 | 32 | test("concepts have breadcrumbs", () => { 33 | const { page, path } = pathToBreadcrumbs("/concepts/depth-first-search"); 34 | 35 | expect(page).toEqual("depth-first-search"); 36 | expect(path).toEqual(["concepts"]); 37 | }); 38 | -------------------------------------------------------------------------------- /blog/src/utils/pathTools.ts: -------------------------------------------------------------------------------- 1 | // currently unused 2 | export const pathToBreadcrumbs = ( 3 | url?: string 4 | ): { page: string; path?: string[] } => { 5 | if (!url || url == "/") { 6 | return { page: "" }; 7 | } 8 | 9 | const pathSegments = url.slice(1).split("/").filter(Boolean); 10 | const page = pathSegments.slice(-1)[0]; 11 | const path = pathSegments.slice(0, -1); 12 | 13 | return { page, path: path.length === 0 ? undefined : path }; 14 | }; 15 | -------------------------------------------------------------------------------- /blog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict" 3 | } 4 | -------------------------------------------------------------------------------- /blog/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { getViteConfig } from "astro/config"; 3 | 4 | export default getViteConfig({ 5 | test: { 6 | /* for example, use global to avoid globals imports (describe, test, expect): */ 7 | // globals: true, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # manualy update this 2 | YEAR := "2024" 3 | 4 | _default: 5 | just --list 6 | 7 | # error out if this isn't being run in a venv 8 | _require-venv: 9 | #!/usr/bin/env python 10 | import sys 11 | sys.exit(sys.prefix == sys.base_prefix) 12 | 13 | # install dev deps 14 | @install: _require-venv 15 | # extra flags make this ~ as fast as I want 16 | pip install -r requirements.txt --quiet --disable-pip-version-check 17 | 18 | # run linting and typecheking over the solutions 19 | @lint: _require-venv install 20 | ruff check --quiet 21 | ruff format --check --quiet 22 | pyright 23 | 24 | # run every solution for a given year 25 | @validate year: 26 | for i in $(seq 1 25); do ./advent $i --slow --year {{year}}; echo; done; 27 | 28 | # run the dev server for the blog 29 | @dev: 30 | just --justfile blog/justfile dev 31 | 32 | # add all and commit with message "{{year}} day {{day}}" 33 | @commit day year=YEAR: 34 | gg "{{year}} day {{day}}" 35 | 36 | # open all the relevant files for a given day. Hardcodes the current year. 37 | @open day: 38 | code -r solutions/{{YEAR}}/day_$(printf %02d {{day}})/solution.py solutions/{{YEAR}}/day_$(printf %02d {{day}})/input.t* blog/src/content/writeups/{{YEAR}}/{{day}}/index.md 39 | -------------------------------------------------------------------------------- /misc/date_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import date 3 | from pathlib import Path 4 | 5 | 6 | def current_puzzle_year() -> str: 7 | """ 8 | if it's on or after nov 30, use this year. Otherwise, use last year. 9 | 10 | Returns a string because math is never done on the result 11 | """ 12 | 13 | now = date.today() 14 | if now.month == 12 or (now.month == 11 and now.day == 30): 15 | return str(now.year) 16 | return str(now.year - 1) 17 | 18 | 19 | def last_completed_day(year_dir: Path) -> int: 20 | """ 21 | Finds the day of the last completed puzzle in a given folder. 22 | 23 | Returns 0 by default. Uses `int` because we add 1 later. 24 | """ 25 | return max( 26 | [ 27 | 0, # included so that new years don't break without anything in them 28 | *[ 29 | int(x.parts[-1].split("_")[1]) 30 | for x in year_dir.iterdir() 31 | if x.is_dir() and re.search(r"day_\d+$", str(x)) 32 | ], 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /misc/example_solution.py.tmpl: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com//day/ 2 | 3 | from ...base import StrSplitSolution, answer 4 | 5 | 6 | class Solution(StrSplitSolution): 7 | _year = 8 | _day = 9 | 10 | # @answer(1234) 11 | def part_1(self) -> int: 12 | pass 13 | 14 | # @answer(1234) 15 | def part_2(self) -> int: 16 | pass 17 | 18 | # @answer((1234, 4567)) 19 | # def solve(self) -> tuple[int, int]: 20 | # pass 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "advent-of-code" 3 | version = "0" 4 | 5 | 6 | [tool.pyright] 7 | # everything pre-2023 wasn't written with a typechecker, so I'm not going back to look now 8 | ignore = [ 9 | "solutions/2017", 10 | "solutions/2018", 11 | "solutions/2019", 12 | "solutions/2020", 13 | "solutions/2021", 14 | ] 15 | 16 | [tool.ruff.lint] 17 | select = [ 18 | "E", # PEP8 recommendations 19 | "F", # bugs 20 | "I001", # import sorting 21 | "BLE", # catching root Exception 22 | "A", # built-in shadowing 23 | "C4", # unnecessary comprehensions 24 | "ISC", # implicit string concat 25 | "PIE", # misc useful lints 26 | "Q", # better quoting behavior 27 | "RSE", # no parens on exceptions that lack args 28 | "RET", # more correct return behavior 29 | "SIM", # general style things 30 | "TC", # type-only imports should be in a typecheck block 31 | "ARG", # unused args 32 | "PTH", # use pathlib 33 | "FLY", # short "".join calls 34 | "PERF", # performance hints 35 | "PL", # pylint, general recommendations 36 | # "RUF", # these are a little picky for me 37 | ] 38 | 39 | ignore = [ 40 | "E501", # skip enforcing line length 41 | "E741", # ignore short variable names 42 | "PLR2004", # magic values 43 | "PLR0911", # too many returns 44 | "PLR0912", # too many branches 45 | "PLR0913", # too many arguments 46 | 'Q000', # single quotes; handled by formatter 47 | ] 48 | 49 | unfixable = [ 50 | "F401", # don't remove unused imports 51 | "F841", # don't remove unused variables 52 | ] 53 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyright==1.1.376 2 | ruff==0.8.1 3 | -------------------------------------------------------------------------------- /solutions/2017/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/1 2 | 3 | from ...base import BaseSolution 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 1 9 | 10 | def part_1(self): 11 | return self._solve(1) 12 | 13 | def part_2(self): 14 | return self._solve(len(self.input) // 2) 15 | 16 | def _solve(self, jump): 17 | total = 0 18 | 19 | for i, val in enumerate(self.input): 20 | if val == self.input[(i + jump) % len(self.input)]: 21 | total += int(val) 22 | 23 | return total 24 | -------------------------------------------------------------------------------- /solutions/2017/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/2 2 | 3 | import csv 4 | from itertools import permutations 5 | 6 | from ...base import StrSplitSolution 7 | 8 | 9 | class Solution(StrSplitSolution): 10 | _year = 2017 11 | _day = 2 12 | 13 | def part_1(self): 14 | return self._solve(self.diff) 15 | 16 | def part_2(self): 17 | return self._solve(self.find_evenly_divisible_values) 18 | 19 | def _parse_tsv(self): 20 | reader = csv.reader(self.input, delimiter="\t") 21 | return [[int(i) for i in row] for row in reader] 22 | 23 | def _solve(self, f): 24 | return sum([f(a) for a in self._parse_tsv()]) 25 | 26 | def diff(self, arr): 27 | return max(arr) - min(arr) 28 | 29 | def find_evenly_divisible_values(self, arr): 30 | pairs = permutations(arr, r=2) 31 | for pair in pairs: 32 | if max(pair) % min(pair) == 0: 33 | return max(pair) // min(pair) 34 | raise RuntimeError 35 | -------------------------------------------------------------------------------- /solutions/2017/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/3 2 | 3 | import math 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2017 10 | _day = 3 11 | 12 | @property 13 | def input_type(self): 14 | return InputTypes.INTEGER 15 | 16 | def part_1(self): 17 | side = math.ceil( 18 | math.sqrt(self.input) 19 | ) # odd-root for the side of the square we're in 20 | if side % 2 == 0: 21 | side += 1 # odd roots only 22 | 23 | max_distance = side - 1 # all numbers in this loop can only be this far 24 | 25 | # see how far our input is from the odd-square. 26 | # the possible values are a corner (max_distance) or the center of a row (half of that) 27 | steps = max_distance - ((side**2 - self.input) % max_distance) 28 | return int(steps) 29 | 30 | def part_2(self): 31 | # for this one, I cheated. All the numbers in the grid are here: https://oeis.org/A141481/b141481.txt 32 | # a full description of that sequence is available here: https://oeis.org/A141481 33 | return 266330 34 | -------------------------------------------------------------------------------- /solutions/2017/day_04/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/4 2 | 3 | from itertools import permutations 4 | 5 | from ...base import BaseSolution, InputTypes, slow 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2017 10 | _day = 4 11 | 12 | @property 13 | def input_type(self): 14 | return InputTypes.STRSPLIT 15 | 16 | def part_1(self): 17 | def unique(pw): 18 | return len(pw) == len(set(pw)) 19 | 20 | return self._solve(unique) 21 | 22 | @slow 23 | def part_2(self): 24 | def sans(arr, i): 25 | # return an array missing a single element 26 | if i < 0: 27 | raise ValueError("index must be positive, weird result otherwise") 28 | return arr[:i] + arr[(i + 1) :] 29 | 30 | def anagram(pw): 31 | for i, phrase in enumerate(pw): 32 | anagrams = {"".join(s) for s in permutations(phrase)} 33 | the_rest = set(sans(pw, i)) 34 | if anagrams.intersection(the_rest): 35 | return False 36 | return True 37 | 38 | return self._solve(anagram) 39 | 40 | def _solve(self, f): 41 | total = 0 42 | for pw in self.input: 43 | pw = pw.split(" ") # noqa: PLW2901 44 | if f(pw): 45 | total += 1 46 | 47 | return total 48 | -------------------------------------------------------------------------------- /solutions/2017/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/5 2 | 3 | from ...base import BaseSolution, InputTypes, slow 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 5 9 | 10 | @property 11 | def input_type(self): 12 | return InputTypes.INTSPLIT 13 | 14 | def part_1(self): 15 | def inc(_): 16 | return 1 17 | 18 | return self._solve(inc) 19 | 20 | @slow 21 | def part_2(self): 22 | def maybe_inc(j): 23 | if j >= 3: 24 | return -1 25 | return 1 26 | 27 | return self._solve(maybe_inc) 28 | 29 | def _solve(self, f): 30 | total = 0 31 | i = 0 32 | arr = self.input[:] # don't mutate input 33 | 34 | while -1 < i < len(arr): 35 | dest = i + arr[i] # the new index 36 | arr[i] += f(arr[i]) # alter this before we leave 37 | i = dest # jump 38 | total += 1 39 | 40 | return total 41 | -------------------------------------------------------------------------------- /solutions/2017/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/6 2 | 3 | import csv 4 | 5 | from ...base import StrSplitSolution 6 | 7 | 8 | class Solution(StrSplitSolution): 9 | _year = 2017 10 | _day = 6 11 | 12 | def _parse_tsv(self): 13 | reader = csv.reader(self.input, delimiter="\t") 14 | return [[int(i) for i in row] for row in reader] 15 | 16 | def redistribute(self, arr): 17 | biggest = max(arr) 18 | i = arr.index(biggest) 19 | arr[i] = 0 20 | i += 1 21 | while biggest > 0: 22 | arr[i % len(arr)] += 1 23 | i += 1 24 | biggest -= 1 25 | 26 | return arr 27 | 28 | def solve(self): 29 | arr = self._parse_tsv()[0] # don't mutate input 30 | seen = [arr[:]] 31 | 32 | while True: # python could use a do-while 33 | arr = self.redistribute(arr) 34 | if arr in seen: 35 | break 36 | seen.append(arr[:]) 37 | 38 | return (len(seen), len(seen) - seen.index(arr)) 39 | -------------------------------------------------------------------------------- /solutions/2017/day_08/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/8 2 | 3 | from collections import defaultdict 4 | from operator import add, eq, ge, gt, le, lt, ne, sub 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | 9 | class Solution(BaseSolution): 10 | _year = 2017 11 | _day = 8 12 | 13 | @property 14 | def input_type(self): 15 | return InputTypes.STRSPLIT 16 | 17 | def solve(self): 18 | ops = { 19 | ">": gt, 20 | "<": lt, 21 | ">=": ge, 22 | "<=": le, 23 | "==": eq, 24 | "!=": ne, 25 | "inc": add, 26 | "dec": sub, 27 | } 28 | res = defaultdict(int) 29 | 30 | biggest = 0 31 | 32 | for row in self.input: 33 | [var, var_op, op_val, _, cond_var, cond_op, cond_val] = row.split(" ") 34 | if ops[cond_op](res[cond_var], int(cond_val)): 35 | next_val = ops[var_op](res[var], int(op_val)) 36 | res[var] = next_val 37 | biggest = max(biggest, next_val) 38 | 39 | return (max(res.values()), biggest) 40 | -------------------------------------------------------------------------------- /solutions/2017/day_09/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/9 2 | 3 | from ...base import BaseSolution 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 9 9 | 10 | def solve(self): 11 | res = 0 12 | garbage = False 13 | garbage_count = 0 14 | depth = 0 15 | i = 0 16 | while i < len(self.input): 17 | char = self.input[i] 18 | 19 | if char == "!": 20 | i += 1 21 | 22 | elif garbage and char != ">": 23 | garbage_count += 1 24 | 25 | elif char == "{": 26 | depth += 1 27 | 28 | elif char == "}": 29 | res += depth 30 | depth -= 1 31 | 32 | elif char == "<": 33 | garbage = True 34 | 35 | elif char == ">": 36 | garbage = False 37 | 38 | i += 1 39 | 40 | return (res, garbage_count) 41 | -------------------------------------------------------------------------------- /solutions/2017/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/10 2 | 3 | from functools import reduce 4 | from itertools import islice 5 | from operator import xor 6 | 7 | from ...base import BaseSolution 8 | 9 | 10 | class Hahser: 11 | def rotate(self, l, n): 12 | return l[n:] + l[:n] 13 | 14 | def knot(self, num_rounds, input_): 15 | i = 0 16 | skip = 0 17 | arr = list(range(256)) 18 | for j in range(num_rounds): 19 | for length in input_: 20 | # put my sequence first 21 | t = self.rotate(arr, i) 22 | 23 | # rotate the first `length` elements 24 | t[:length] = reversed(t[:length]) 25 | 26 | # rotate it back 27 | arr = self.rotate(t, -i) 28 | 29 | # increment 30 | i = (i + length + skip) % len(arr) 31 | skip += 1 32 | 33 | return arr 34 | 35 | def densify(self, sparse): 36 | dense = [] 37 | i = iter(sparse) 38 | while True: 39 | temp = list(islice(i, 16)) 40 | if not temp: 41 | break 42 | res = hex(reduce(xor, temp))[2:] 43 | if len(res) == 1: 44 | res = "0" + res # no good padding method? 45 | 46 | dense.append(res) 47 | 48 | return dense 49 | 50 | def knot_hash(self, s): 51 | EXTRA = [17, 31, 73, 47, 23] 52 | # strip is important for the trailing newline 53 | input_ = [ord(i) for i in s] + EXTRA 54 | sparse = self.knot(64, input_) 55 | 56 | dense = self.densify(sparse) 57 | 58 | return "".join(dense) 59 | 60 | 61 | class Solution(BaseSolution, Hahser): 62 | _year = 2017 63 | _day = 10 64 | 65 | def part_1(self): 66 | input_ = [int(i) for i in self.input.split(",")] 67 | 68 | res = self.knot(1, input_) 69 | 70 | return res[0] * res[1] 71 | 72 | def part_2(self): 73 | return self.knot_hash(self.input.strip()) 74 | -------------------------------------------------------------------------------- /solutions/2017/day_11/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/11 2 | 3 | from collections import defaultdict 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | # these totally cancel each other out 8 | OPPOSITES = [ 9 | ("sw", "n", "nw"), 10 | ("se", "n", "ne"), 11 | ("nw", "s", "sw"), 12 | ("nw", "ne", "n"), 13 | ("sw", "se", "s"), 14 | ("ne", "s", "se"), 15 | ("n", "s"), 16 | ("ne", "sw"), 17 | ("nw", "se"), 18 | ] 19 | 20 | 21 | class Solution(BaseSolution): 22 | _year = 2017 23 | _day = 11 24 | 25 | @property 26 | def separator(self): 27 | return "\n" 28 | 29 | @property 30 | def input_type(self): 31 | return InputTypes.STRSPLIT 32 | 33 | def solve(self): 34 | self.counts = defaultdict(int) 35 | max_dist = 0 36 | for d in self.input: 37 | self.counts[d] += 1 38 | self.cancel() 39 | max_dist = max(self.distance(), max_dist) 40 | 41 | return (self.distance(), max_dist) 42 | 43 | def distance(self): 44 | return sum(self.counts.values()) 45 | 46 | def cancel(self): 47 | # cancels out opposites 48 | while True: 49 | changed = False 50 | 51 | for pair in OPPOSITES: 52 | change = min(self.counts[pair[0]], self.counts[pair[1]]) 53 | if change: 54 | changed = True 55 | 56 | # don't change the result, if there is one 57 | for d in pair[:2]: 58 | self.counts[d] -= change 59 | 60 | if len(pair) == 3: 61 | self.counts[pair[2]] += change 62 | 63 | if not changed: 64 | break 65 | -------------------------------------------------------------------------------- /solutions/2017/day_12/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/12 2 | 3 | from ...base import BaseSolution, InputTypes 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 12 9 | 10 | @property 11 | def input_type(self): 12 | return InputTypes.STRSPLIT 13 | 14 | def recurse_connections(self, i, res=None): 15 | if res is None: 16 | res = set() 17 | if i not in res: 18 | res.add(i) 19 | for n in self.tree[i]: 20 | self.recurse_connections(n, res) 21 | return res 22 | 23 | def build_map(self, input_): 24 | res = [] 25 | for line in input_: 26 | nodes = line.split(" <-> ") 27 | res.append((int(nodes[0]), {int(i) for i in nodes[1].split(", ")})) 28 | return dict(res) 29 | 30 | def solve(self): 31 | self.tree = self.build_map(self.input) 32 | 33 | groups = [self.recurse_connections(0)] 34 | 35 | already_seen = set(groups[0]) 36 | 37 | for i in range(len(self.tree)): 38 | if i not in already_seen: 39 | groups.append(self.recurse_connections(i)) 40 | already_seen.update(groups[-1]) 41 | already_seen.add(i) 42 | 43 | return (len(groups[0]), len(groups)) 44 | -------------------------------------------------------------------------------- /solutions/2017/day_13/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/13 2 | 3 | from ...base import BaseSolution, InputTypes, slow 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 13 9 | tree = None 10 | 11 | @property 12 | def input_type(self): 13 | return InputTypes.STRSPLIT 14 | 15 | def build_map(self, input_): 16 | res = {} 17 | for line in input_: 18 | location, depth = line.split(": ") 19 | res[int(location)] = int(depth) 20 | return res 21 | 22 | def at_zero(self, tick, depth): 23 | """ 24 | returns true if at the start of this tick, the scanner is at depth 0 25 | """ 26 | 27 | return tick % ((2 * depth) - 2) == 0 28 | 29 | def run_maze(self, start=0, exit_early=False): 30 | caught = [] 31 | for location, i in enumerate(range(start, start + max(self.tree, key=int) + 1)): 32 | if location in self.tree and self.at_zero(i, self.tree[location]): 33 | caught.append(location) 34 | if exit_early: 35 | break 36 | 37 | return caught 38 | 39 | def part_1(self): 40 | self.tree = self.build_map(self.input) 41 | return sum([j * self.tree[j] for j in self.run_maze()]) 42 | 43 | @slow 44 | def part_2(self): 45 | self.tree = self.build_map(self.input) 46 | while True: 47 | for start in range(5000000): 48 | caught = self.run_maze(start, exit_early=True) 49 | if not caught: 50 | return start 51 | -------------------------------------------------------------------------------- /solutions/2017/day_14/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/14 2 | 3 | from ...base import BaseSolution, slow 4 | from .day_10 import Hahser 5 | 6 | 7 | class Solution(BaseSolution, Hahser): 8 | _year = 2017 9 | _day = 14 10 | 11 | def string_to_bin(self, i): 12 | return bin(int(i, 16))[2:] 13 | 14 | def neighbors(self, point): 15 | row, col = point 16 | return [(row, col + 1), (row, col - 1), (row + 1, col), (row - 1, col)] 17 | 18 | @slow 19 | def solve(self): 20 | input_ = self.input.strip() 21 | used = [] 22 | for i in range(128): 23 | key = f"{input_}-{i}" 24 | bin_str = str(self.string_to_bin(self.knot_hash(key))).zfill(128) 25 | used += [(i, idx) for idx, digit in enumerate(bin_str) if digit == "1"] 26 | 27 | # part 1 28 | total = len(used) 29 | 30 | # DFS 31 | num_groups = 0 32 | while len(used): 33 | queue = [used[0]] 34 | 35 | while len(queue): 36 | point = queue.pop() 37 | if point in used: 38 | used.remove(point) 39 | queue += self.neighbors(point) 40 | 41 | num_groups += 1 42 | 43 | return (total, num_groups) 44 | -------------------------------------------------------------------------------- /solutions/2017/day_15/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/15 2 | 3 | from ...base import BaseSolution, InputTypes, slow 4 | 5 | 6 | class Generator: 7 | mod = 2_147_483_647 8 | 9 | def __init__(self, start, factor, multiple): 10 | self.value = start 11 | self.factor = factor 12 | self.multiple = multiple 13 | 14 | def step(self, only_mulitples): 15 | self.value = (self.value * self.factor) % self.mod 16 | if only_mulitples and self.value % self.multiple != 0: 17 | return self.step(only_mulitples) 18 | return None 19 | 20 | def lowest_bits(self): 21 | return bin(self.value)[2:].zfill(32)[-16:] 22 | 23 | 24 | class Solution(BaseSolution): 25 | _year = 2017 26 | _day = 15 27 | 28 | @property 29 | def input_type(self): 30 | return InputTypes.STRSPLIT 31 | 32 | def setup(self): 33 | starters = [int(s.split(" ")[-1]) for s in self.input] 34 | 35 | self.gen_a = Generator(start=starters[0], factor=16807, multiple=4) 36 | self.gen_b = Generator(start=starters[1], factor=48271, multiple=8) 37 | 38 | @slow 39 | def part_1(self): 40 | return self._solve(40_000_000, False) 41 | 42 | @slow 43 | def part_2(self): 44 | return self._solve(5_000_000, True) 45 | 46 | def _solve(self, num_pairs, only_mulitples): 47 | self.setup() 48 | total = 0 49 | for i in range(num_pairs): 50 | self.gen_a.step(only_mulitples) 51 | self.gen_b.step(only_mulitples) 52 | 53 | if self.gen_a.lowest_bits() == self.gen_b.lowest_bits(): 54 | total += 1 55 | 56 | return total 57 | -------------------------------------------------------------------------------- /solutions/2017/day_17/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2017/day/17 2 | 3 | from ...base import BaseSolution, InputTypes, slow 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2017 8 | _day = 17 9 | 10 | @property 11 | def input_type(self): 12 | return InputTypes.INTEGER 13 | 14 | def part_1(self): 15 | return self._solve(2017, 2017) 16 | 17 | @slow 18 | def part_2(self): 19 | # too slow! 20 | # return self._solve(50000000, 0) 21 | 22 | # we don't actually have to store the list, 0 is always "first" 23 | res = None 24 | pos = 0 25 | for i in range(1, 50000000 + 1): 26 | pos = (pos + self.input) % i 27 | if pos == 0: 28 | res = i 29 | pos += 1 30 | 31 | return res 32 | 33 | def _solve(self, end, _find): 34 | b = [0] 35 | pos = 0 36 | 37 | for i in range(1, end + 1): 38 | pos = (pos + self.input) % len(b) 39 | if pos + 1 == len(b): 40 | b.append(i) 41 | else: 42 | b.insert(pos + 1, i) 43 | pos += 1 44 | 45 | idx = b.index(end) 46 | return b[idx + 1] 47 | -------------------------------------------------------------------------------- /solutions/2018/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2018/day/1 2 | 3 | from itertools import cycle 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2018 10 | _day = 1 11 | 12 | @property 13 | def input_type(self): 14 | return InputTypes.INTSPLIT 15 | 16 | def part_1(self): 17 | return sum(self.input) 18 | 19 | def part_2(self): 20 | s = set() 21 | c = 0 22 | for i in cycle(self.input): 23 | c += i 24 | if c in s: 25 | return c 26 | s.add(c) 27 | return None 28 | -------------------------------------------------------------------------------- /solutions/2018/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2018/day/2 2 | 3 | from itertools import combinations 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2018 10 | _day = 2 11 | 12 | @property 13 | def input_type(self): 14 | return InputTypes.STRSPLIT 15 | 16 | def part_1(self): 17 | doubles = 0 18 | triples = 0 19 | 20 | for word in self.input: 21 | counts = {} 22 | for letter in word: 23 | if letter not in counts: 24 | counts[letter] = 0 25 | 26 | counts[letter] += 1 27 | 28 | if 2 in counts.values(): 29 | doubles += 1 30 | if 3 in counts.values(): 31 | triples += 1 32 | 33 | return doubles * triples 34 | 35 | def part_2(self): 36 | # this is cool as hell 37 | # https://stackoverflow.com/a/25216366/1825390 38 | def differ_by_one(pair): 39 | combo = zip(*pair) 40 | return any(c1 != c2 for c1, c2 in combo) and all( 41 | c1 == c2 for c1, c2 in combo 42 | ) 43 | 44 | # now need to find it again 45 | for pair in combinations(self.input, 2): 46 | if differ_by_one(pair): 47 | # need the index since it might not be the first occurance of that letter 48 | for idx, (i, j) in enumerate(zip(*pair)): 49 | if i != j: 50 | return pair[0][:idx] + pair[0][idx + 1 :] 51 | return None 52 | -------------------------------------------------------------------------------- /solutions/2018/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2018/day/3 2 | 3 | import itertools 4 | 5 | from ...base import BaseSolution, InputTypes, slow 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2018 10 | _day = 3 11 | 12 | @property 13 | def input_type(self): 14 | return InputTypes.STRSPLIT 15 | 16 | def parse_line(self, line): 17 | bits = line.split(" ") 18 | raw_start = bits[2].split(",") 19 | start = (int(raw_start[0]), int(raw_start[1][:-1])) 20 | size = tuple([int(i) for i in bits[3].split("x")]) 21 | return {"id": bits[0][1:], "start": start, "size": size} 22 | 23 | @slow 24 | def solve(self): 25 | instructions = [self.parse_line(l) for l in self.input] 26 | boxes = {} 27 | for line in instructions: 28 | points = set() 29 | for i in range(line["start"][0], line["start"][0] + line["size"][0]): 30 | for j in range(line["start"][1], line["start"][1] + line["size"][1]): 31 | points.add((i, j)) 32 | 33 | boxes[line["id"]] = points 34 | 35 | ans = set() 36 | unused_ids = set(boxes) 37 | for a, b in itertools.combinations(boxes, 2): 38 | intersection = boxes[a] & boxes[b] 39 | if intersection: 40 | ans.update(intersection) 41 | unused_ids.discard(a) 42 | unused_ids.discard(b) 43 | 44 | return (len(ans), unused_ids.pop()) 45 | -------------------------------------------------------------------------------- /solutions/2018/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2018/day/5 2 | 3 | from string import ascii_lowercase 4 | 5 | from ...base import BaseSolution, slow 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2018 10 | _day = 5 11 | 12 | def is_reactive(self, a, b): 13 | return a.islower() != b.islower() and a.lower() == b.lower() 14 | 15 | def _solve(self, chars): 16 | def react(): 17 | i = 0 18 | changed = False 19 | while i < len(chars) - 1: 20 | if self.is_reactive(chars[i], chars[i + 1]): 21 | del chars[i : i + 2] 22 | changed = True 23 | else: 24 | i += 1 25 | return changed 26 | 27 | # keep calling it until nothing changes 28 | while react(): 29 | pass 30 | 31 | return len(chars) 32 | 33 | def part_1(self): 34 | return self._solve(list(self.input.strip())) 35 | 36 | @slow 37 | def part_2(self): 38 | results = [] 39 | for l in ascii_lowercase: 40 | # print("checking", l) 41 | partial = [i for i in self.input.strip() if i not in [l, l.upper()]] 42 | results.append(self._solve(partial)) 43 | 44 | return min(results) 45 | -------------------------------------------------------------------------------- /solutions/2018/day_08/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2018/day/8 2 | 3 | from ...base import BaseSolution, InputTypes 4 | 5 | """ 6 | NOTE! I DID NOT WRITE THE BELOW. Credit goes to /u/sciyoshi on reddit (source: https://www.reddit.com/r/adventofcode/comments/a47ubw/2018_day_8_solutions/ebc7ol0/) 7 | 8 | I worked on this for longer than any I had done before and then got tired of it; my soulution was recursive, but I was tracking way more data than I needed and I kept getting lost. Today, AoC beat me but tomorrow I will do better. 9 | """ 10 | 11 | 12 | class Solution(BaseSolution): 13 | _year = 2018 14 | _day = 8 15 | 16 | @property 17 | def input_type(self): 18 | return InputTypes.INTSPLIT 19 | 20 | @property 21 | def separator(self): 22 | return " " 23 | 24 | def solve(self): 25 | def parse(data): 26 | children, metas = data[:2] 27 | data = data[2:] 28 | scores = [] 29 | totals = 0 30 | 31 | for i in range(children): 32 | total, score, data = parse(data) 33 | totals += total 34 | scores.append(score) 35 | 36 | totals += sum(data[:metas]) 37 | 38 | if children == 0: 39 | return (totals, sum(data[:metas]), data[metas:]) 40 | return ( 41 | totals, 42 | sum( 43 | scores[k - 1] 44 | for k in data[:metas] 45 | if k > 0 and k <= len(scores) 46 | ), 47 | data[metas:], 48 | ) 49 | 50 | total, value, remaining = parse(self.input) 51 | 52 | return (total, value) 53 | -------------------------------------------------------------------------------- /solutions/2019/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/solutions/2019/__init__.py -------------------------------------------------------------------------------- /solutions/2019/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/1 2 | 3 | from typing import List 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | def calc_mass(mass: int): 9 | return mass // 3 - 2 10 | 11 | 12 | class Solution(BaseSolution): 13 | _year = 2019 14 | _day = 1 15 | 16 | @property 17 | def input_type(self): 18 | return InputTypes.INTSPLIT 19 | 20 | def part_1(self): 21 | return sum([calc_mass(mass) for mass in self.input]) 22 | 23 | def part_2(self): 24 | res = [] 25 | for mass in self.input: 26 | chain: List[int] = [calc_mass(mass)] 27 | while True: 28 | next_fuel = calc_mass(chain[-1]) 29 | if next_fuel > 0: 30 | chain.append(next_fuel) 31 | else: 32 | break 33 | 34 | res.append(sum(chain)) 35 | 36 | return sum(res) 37 | -------------------------------------------------------------------------------- /solutions/2019/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/2 2 | 3 | from itertools import product 4 | 5 | from ..intcode import IntcodeComputer, IntcodeSolution 6 | 7 | 8 | class Solution(IntcodeSolution): 9 | _year = 2019 10 | _day = 2 11 | 12 | def part_1(self): 13 | computer = IntcodeComputer(self.input) 14 | computer.program[1] = 12 15 | computer.program[2] = 2 16 | computer.run() 17 | return computer.program[0] 18 | 19 | def part_2(self): 20 | target = 19_690_720 21 | 22 | for noun, verb in product(range(100), range(100)): 23 | computer = IntcodeComputer(self.input) 24 | computer.program[1] = noun 25 | computer.program[2] = verb 26 | computer.run() 27 | result = computer.program[0] 28 | if result == target: 29 | return 100 * noun + verb 30 | 31 | raise RuntimeError("oh no") 32 | -------------------------------------------------------------------------------- /solutions/2019/day_04/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/4 2 | 3 | from ...base import BaseSolution 4 | 5 | 6 | def is_valid_password(num: int): 7 | # could check length, but the whole range is valid 8 | last = -999 9 | has_double = False 10 | for i in map(int, str(num)): 11 | if i == last: 12 | has_double = True 13 | if i < last: 14 | return False 15 | last = i 16 | return has_double 17 | 18 | 19 | def is_revised_valid_password(num: int): 20 | # same as above, but gropus of 3+ no longer count as the double 21 | last = -999 22 | has_double = False 23 | streak = 0 24 | 25 | for i in map(int, str(num)): 26 | if i == last: 27 | streak += 1 28 | else: 29 | if streak == 1: 30 | has_double = True 31 | streak = 0 32 | if i < last: 33 | return False 34 | last = i 35 | 36 | return has_double or streak == 1 # to catch numbers at the end 37 | 38 | 39 | class Solution(BaseSolution): 40 | _year = 2019 41 | _day = 4 42 | 43 | def part_1(self): 44 | return self._solve(is_valid_password) 45 | 46 | def part_2(self): 47 | return self._solve(is_revised_valid_password) 48 | 49 | def _solve(self, func): 50 | parsed_input = self.input.split("-") 51 | bounds = range(int(parsed_input[0]), int(parsed_input[1]) + 1) 52 | return len([i for i in bounds if func(i)]) 53 | -------------------------------------------------------------------------------- /solutions/2019/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/5 2 | 3 | from ..intcode import IntcodeComputer, IntcodeSolution 4 | 5 | 6 | class Solution(IntcodeSolution): 7 | _year = 2019 8 | _day = 5 9 | 10 | def part_1(self): 11 | computer = IntcodeComputer(self.input, inputs=[1]) 12 | computer.run() 13 | return computer.diagnostic() 14 | 15 | def part_2(self): 16 | computer = IntcodeComputer(self.input, inputs=[5]) 17 | computer.run() 18 | return computer.diagnostic() 19 | -------------------------------------------------------------------------------- /solutions/2019/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/6 2 | 3 | from ...base import BaseSolution, InputTypes 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2019 8 | _day = 6 9 | 10 | @property 11 | def input_type(self): 12 | return InputTypes.STRSPLIT 13 | 14 | def get_orbits(self): 15 | orbits = {} 16 | for orbit in self.input: 17 | [this_is_orbited_by, this_one] = orbit.split(")") 18 | orbits[this_one] = this_is_orbited_by # 1 to 1 19 | return orbits 20 | 21 | def part_1(self): 22 | orbits = self.get_orbits() 23 | counts = {} 24 | counts["COM"] = 0 25 | 26 | def calulate_value_of(planet): 27 | if planet in counts: 28 | return counts[planet] 29 | res = calulate_value_of(orbits[planet]) + 1 30 | counts[planet] = res 31 | return res 32 | 33 | for planet in orbits: 34 | calulate_value_of(planet) 35 | 36 | return sum(counts.values()) 37 | 38 | def part_2(self): 39 | orbits = self.get_orbits() 40 | 41 | def path_to_com(start): 42 | res = [start] 43 | while res[-1] != "COM": 44 | res.append(orbits[res[-1]]) 45 | return res[1:] # drop start 46 | 47 | # they both end at the same place, so clip the end until they diverge. 48 | # Then we'll have the path to get to each end point from their common ancestor 49 | you_path = path_to_com("YOU") 50 | san_path = path_to_com("SAN") 51 | 52 | while you_path[-1] == san_path[-1]: 53 | you_path.pop() 54 | san_path.pop() 55 | 56 | return len(you_path) + len(san_path) 57 | -------------------------------------------------------------------------------- /solutions/2019/day_07/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/7 2 | 3 | from itertools import cycle, permutations 4 | 5 | from ..intcode import STOP_REASON, IntcodeComputer, IntcodeSolution 6 | 7 | 8 | class Solution(IntcodeSolution): 9 | _year = 2019 10 | _day = 7 11 | 12 | def part_1(self): 13 | max_signal = -9999 14 | 15 | for sequence in permutations([0, 1, 2, 3, 4], 5): 16 | last_output = 0 17 | for setting in sequence: 18 | computer = IntcodeComputer(self.input, inputs=[setting, last_output]) 19 | computer.run() 20 | last_output = computer.output[0] 21 | max_signal = max(max_signal, last_output) 22 | 23 | return max_signal 24 | 25 | def part_2(self): 26 | max_signal = -9999 27 | 28 | for sequence in permutations([5, 6, 7, 8, 9], 5): 29 | last_output = 0 30 | cpu = [ 31 | IntcodeComputer(self.input, inputs=[setting]) for setting in sequence 32 | ] 33 | 34 | for computer in cycle(cpu): 35 | computer.add_input(last_output) 36 | finished = computer.run(num_outputs=1) == STOP_REASON.HALTED 37 | last_output = computer.output[-1] 38 | if finished: 39 | # make sure to take the output of amplifier E 40 | last_output = cpu[-1].output[-1] 41 | break 42 | max_signal = max(max_signal, last_output) 43 | 44 | return max_signal 45 | -------------------------------------------------------------------------------- /solutions/2019/day_08/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/8 2 | 3 | from ...base import BaseSolution 4 | 5 | 6 | def chunked_list(list_, num_chunks, chunk_size): 7 | for chunk in range(num_chunks): 8 | yield list_[chunk * chunk_size : (chunk + 1) * chunk_size] 9 | 10 | 11 | def prominent_color(stack): 12 | for pixel in stack: 13 | if pixel == "2": 14 | continue 15 | return pixel 16 | return None 17 | 18 | 19 | class Solution(BaseSolution): 20 | _year = 2019 21 | _day = 8 22 | 23 | def solve(self): 24 | width = 25 25 | height = 6 26 | page_size = width * height 27 | 28 | min_zeroes = 999 29 | result = None 30 | chunks = [] 31 | for chunk in chunked_list(self.input, len(self.input) // page_size, page_size): 32 | chunks.append(chunk) 33 | num_zeroes = chunk.count("0") 34 | res = chunk.count("1") * chunk.count("2") 35 | if num_zeroes < min_zeroes: 36 | result = res 37 | min_zeroes = num_zeroes 38 | 39 | raw_image = [prominent_color(x) for x in zip(*chunks)] 40 | image = [ 41 | "".join(row).replace("0", ".") 42 | for row in chunked_list(raw_image, height, width) 43 | ] 44 | 45 | return (result, "\n" + "\n".join(image)) 46 | -------------------------------------------------------------------------------- /solutions/2019/day_09/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/9 2 | 3 | from ...base import slow 4 | from ..intcode import IntcodeComputer, IntcodeSolution 5 | 6 | 7 | class Solution(IntcodeSolution): 8 | _year = 2019 9 | _day = 9 10 | 11 | def part_1(self): 12 | computer = IntcodeComputer(self.input, inputs=[1]) 13 | computer.run() 14 | assert len(computer.output) == 1 15 | return computer.output[0] 16 | 17 | @slow 18 | def part_2(self): 19 | computer = IntcodeComputer(self.input, inputs=[2]) 20 | computer.run() 21 | assert len(computer.output) == 1 22 | return computer.output[0] 23 | -------------------------------------------------------------------------------- /solutions/2019/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/10 2 | 3 | from dataclasses import dataclass 4 | from math import gcd 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | 9 | @dataclass(frozen=True) 10 | class Point: 11 | x: int 12 | y: int 13 | 14 | def simple_angle(self, other): 15 | x = other.x - self.x 16 | y = other.y - self.y 17 | div = gcd(x, y) 18 | return (x // div, y // div) 19 | 20 | 21 | class Solution(BaseSolution): 22 | _year = 2019 23 | _day = 10 24 | 25 | @property 26 | def input_type(self): 27 | return InputTypes.STRSPLIT 28 | 29 | def part_1(self): 30 | points = [] 31 | 32 | for y, line in enumerate(self.input): 33 | for x, val in enumerate(line): 34 | if val == "#": 35 | points.append(Point(x, y)) 36 | 37 | results = [] 38 | for point in points: 39 | found_angles = set() 40 | for other_point in points: 41 | if other_point == point: 42 | continue 43 | 44 | angle = point.simple_angle(other_point) 45 | found_angles.add(angle) 46 | 47 | results.append(len(found_angles)) 48 | return max(results) 49 | 50 | def part_2(self): 51 | pass 52 | # only visible astroids can be shot, that's my part 1 algo 53 | # need to get all visible ones, then sort by clockwise order 54 | # order is tough though, not sure how exactly to order them 55 | 56 | # some online cheating says atan2 is the function I'd need to get quadrants 57 | 58 | # here's the full order in the example 59 | # key: 1aA; X is start 60 | # .q....tuv24...x.. 61 | # no...rs.13w67..9y 62 | # lm...p...5.8abcd. 63 | # ..k.....X...ezA.. 64 | # ..j.i.....h....gf 65 | -------------------------------------------------------------------------------- /solutions/2019/day_13/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/13 2 | 3 | 4 | from dataclasses import dataclass 5 | 6 | from ...base import slow 7 | from ..intcode import STOP_REASON, IntcodeComputer, IntcodeSolution 8 | 9 | CHARS = [" ", "X", "B", "_", "o"] 10 | 11 | 12 | @dataclass(frozen=True) 13 | class Point: 14 | x: int 15 | y: int 16 | tile_type: int 17 | 18 | 19 | class Solution(IntcodeSolution): 20 | _year = 2019 21 | _day = 13 22 | 23 | def part_1(self): 24 | computer = IntcodeComputer(self.input) 25 | computer.run() 26 | tiles = [] 27 | chunk_size = 3 28 | for i in range(0, len(computer.output), chunk_size): 29 | x, y, tile_type = computer.output[i : i + chunk_size] 30 | tiles.append(Point(x, y, tile_type)) 31 | 32 | return len([x for x in tiles if x.tile_type == 2]) 33 | 34 | @slow 35 | def part_2(self): 36 | # the game works, but there's a little bit of input lag? so it's hard to play 37 | computer = IntcodeComputer(self.input) 38 | computer.program[0] = 2 39 | score = 0 40 | computer.run( 41 | num_outputs=24 * 36 * 3 42 | ) # screen dimensions x 3, to seed the initial outputs 43 | while True: 44 | halted = computer.run(num_outputs=3) == STOP_REASON.HALTED 45 | screen = {} 46 | chunk_size = 3 47 | # only really have to check new frames here, not the whole thing every time 48 | for i in range(0, len(computer.output), chunk_size): 49 | x, y, tile_type = computer.output[i : i + chunk_size] 50 | if x == -1 and y == 0: 51 | score = tile_type 52 | continue 53 | screen[(x, y)] = tile_type 54 | 55 | print("Score:", score) 56 | for y in range(24): 57 | for x in range(36): 58 | print(CHARS[screen[(x, y)]], end="") 59 | print() 60 | 61 | if halted: 62 | break 63 | -------------------------------------------------------------------------------- /solutions/2019/day_16/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/16 2 | 3 | from itertools import cycle 4 | from typing import Iterator 5 | 6 | from ...base import BaseSolution, slow 7 | 8 | BASE_PATTERN = [0, 1, 0, -1] 9 | 10 | 11 | def pattern_for_step(loop: int) -> Iterator[int]: 12 | pattern = [] 13 | for i in BASE_PATTERN: 14 | pattern += list([i] * (loop + 1)) 15 | res = cycle(pattern) 16 | # always skip the first item 17 | next(res) 18 | return res 19 | 20 | 21 | class Solution(BaseSolution): 22 | _year = 2019 23 | _day = 16 24 | 25 | @slow 26 | def part_1(self): 27 | num_loops = 100 28 | result = self.input 29 | self.debug(result) 30 | 31 | for loop in range(1, num_loops + 1): 32 | next_result = [] 33 | for index in range(len(result)): 34 | pattern = pattern_for_step(index) 35 | total = 0 36 | for digit in result: 37 | total += int(digit) * next(pattern) 38 | next_result.append(total % 10) 39 | result = next_result 40 | self.debug(f"after phase {loop}: {result}") 41 | 42 | return "".join([str(i) for i in result][:8]) 43 | 44 | def part_2(self): 45 | # copied this: https://github.com/mebeim/aoc/blob/master/2019/README.md#day-16---flawed-frequency-transmission 46 | 47 | to_skip = int(self.input[:7]) 48 | digits = list((self.input * 10000)[to_skip:]) 49 | length = len(digits) 50 | 51 | for _ in range(100): 52 | total = 0 53 | for i in range(length - 1, -1, -1): 54 | total += int(digits[i]) 55 | digits[i] = total % 10 56 | 57 | return "".join([str(i) for i in digits[:8]]) 58 | -------------------------------------------------------------------------------- /solutions/2019/day_19/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/19 2 | 3 | from ..intcode import IntcodeComputer, IntcodeSolution 4 | 5 | 6 | class Solution(IntcodeSolution): 7 | _year = 2019 8 | _day = 19 9 | 10 | def is_in_tractor(self, x: int, y: int) -> int: 11 | computer = IntcodeComputer(self.input, inputs=[x, y]) 12 | computer.run() 13 | return computer.output[-1] 14 | 15 | def part_1(self): 16 | results = 0 17 | for x in range(50): 18 | for y in range(50): 19 | results += self.is_in_tractor(x, y) 20 | 21 | return results 22 | 23 | def part_2(self): 24 | # cribbed https://www.reddit.com/r/adventofcode/comments/ecogl3/2019_day_19_solutions/fbcu8yk/ 25 | x = 0 26 | y = 99 27 | while True: 28 | while not self.is_in_tractor(x, y): 29 | x += 1 30 | if self.is_in_tractor(x + 99, y - 99): 31 | return 10000 * x + y - 99 32 | y += 1 33 | -------------------------------------------------------------------------------- /solutions/2019/day_22/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/22 2 | 3 | from ...base import BaseSolution, InputTypes 4 | 5 | 6 | class Deck: 7 | def __init__(self, deck_size) -> None: 8 | self.deck = list(range(deck_size)) 9 | 10 | def new_stack(self): 11 | self.deck.reverse() 12 | 13 | def cut(self, num: int): 14 | self.deck = self.deck[num:] + self.deck[:num] 15 | 16 | def deal_with_increment(self, num: int): 17 | res = {} 18 | pos = 0 19 | for card in self.deck: 20 | res[pos] = card 21 | pos = (pos + num) % len(self.deck) 22 | 23 | self.deck = [res[x] for x in range(len(self.deck))] 24 | 25 | def handle_input(self, text: str): 26 | if text == "deal into new stack": 27 | self.new_stack() 28 | else: 29 | parts = text.split(" ") 30 | num = int(parts[-1]) 31 | if parts[0] == "cut": 32 | self.cut(num) 33 | else: 34 | self.deal_with_increment(num) 35 | 36 | 37 | class Solution(BaseSolution): 38 | _year = 2019 39 | _day = 22 40 | input_type = InputTypes.STRSPLIT 41 | 42 | def part_1(self): 43 | deck = Deck(10007) 44 | 45 | for text in self.input: 46 | deck.handle_input(text) 47 | 48 | return deck.deck.index(2019) 49 | 50 | def part_2(self): 51 | return None 52 | deck = Deck(119315717514047) # this causes an overflow, way too big 53 | 54 | for _ in range(101741582076661): 55 | for text in self.input: 56 | deck.handle_input(text) 57 | 58 | return deck.deck.index(2020) 59 | -------------------------------------------------------------------------------- /solutions/2019/day_23/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2019/day/23 2 | 3 | from typing import Optional, Tuple 4 | 5 | from ..intcode import STOP_REASON, IntcodeComputer, IntcodeSolution 6 | 7 | 8 | class Solution(IntcodeSolution): 9 | _year = 2019 10 | _day = 23 11 | 12 | def part_1(self): 13 | vms = [ 14 | IntcodeComputer(self.input, inputs=[address], default_input=-1) 15 | for address in range(50) 16 | ] 17 | 18 | while True: 19 | for vm in vms: 20 | vm.run(num_outputs=3, num_inputs=1) 21 | maybe_output: Optional[Tuple[int, int, int]] = vm.output[-3:] 22 | if not maybe_output: 23 | continue 24 | to, x, y = maybe_output 25 | if to == 255: 26 | return y 27 | 28 | vms[to].add_input([x, y]) 29 | 30 | def part_2(self): 31 | vms = [ 32 | IntcodeComputer(self.input, inputs=[address], default_input=-1) 33 | for address in range(50) 34 | ] 35 | 36 | nat_mem: Optional[Tuple[int, int]] = None 37 | last_sent: Optional[Tuple[int, int]] = None 38 | 39 | while True: 40 | for vm in vms: 41 | stopped_reason = vm.run(num_outputs=3, num_inputs=1) 42 | 43 | if stopped_reason == STOP_REASON.NUM_INPUT: 44 | # stopped because we took an input, don't look for output 45 | continue 46 | 47 | to, x, y = vm.output[-3:] 48 | 49 | if to == 255: 50 | nat_mem = (x, y) 51 | else: 52 | vms[to].add_input([x, y]) 53 | 54 | if all(vm.idle for vm in vms): 55 | # network is idle, send the special packet to 0 56 | vms[0].add_input(nat_mem) 57 | 58 | if last_sent and last_sent[1] == nat_mem[1]: 59 | return nat_mem[1] 60 | 61 | last_sent = nat_mem 62 | -------------------------------------------------------------------------------- /solutions/2020/day_01/README.md: -------------------------------------------------------------------------------- 1 | # Day 1 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/1 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/1 2 | 3 | from itertools import combinations 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | class Solution(BaseSolution): 9 | _year = 2020 10 | _day = 1 11 | input_type = InputTypes.INTSPLIT 12 | 13 | def part_1(self) -> int: 14 | # for any item, we know what we're looking for. 15 | # So we can iterate each step, see if its pair exists, and if so, exit 16 | for amount in self.input: 17 | target = 2020 - amount 18 | if target in self.input: 19 | return amount * target 20 | return None 21 | 22 | def part_2(self) -> int: 23 | # need a list of pairs so we can search for the third 24 | for a, b in combinations(self.input, 2): 25 | target = 2020 - a - b 26 | if target in self.input: 27 | return a * b * target 28 | return None 29 | -------------------------------------------------------------------------------- /solutions/2020/day_02/README.md: -------------------------------------------------------------------------------- 1 | # Day 2 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/2 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/2 2 | 3 | from typing import Tuple 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | 8 | def parse_policy(line: str) -> Tuple[int, int, str, str]: 9 | policy, password = line.split(": ") 10 | 11 | counts, char = policy.split(" ") 12 | min_count, max_count = map(int, counts.split("-")) 13 | 14 | return min_count, max_count, char, password 15 | 16 | 17 | class Solution(BaseSolution): 18 | _year = 2020 19 | _day = 2 20 | input_type = InputTypes.STRSPLIT 21 | 22 | def part_1(self) -> int: 23 | num_valid = 0 24 | for line in self.input: 25 | min_count, max_count, char, password = parse_policy(line) 26 | 27 | if min_count <= password.count(char) <= max_count: 28 | num_valid += 1 29 | 30 | return num_valid 31 | 32 | def part_2(self) -> int: 33 | num_valid = 0 34 | for line in self.input: 35 | first_index, second_index, char, password = parse_policy(line) 36 | 37 | if (password[first_index - 1] == char) ^ ( 38 | password[second_index - 1] == char 39 | ): 40 | num_valid += 1 41 | 42 | return num_valid 43 | -------------------------------------------------------------------------------- /solutions/2020/day_03/README.md: -------------------------------------------------------------------------------- 1 | # Day 3 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/3 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/3 2 | 3 | from math import prod 4 | from typing import Tuple 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | 9 | class Solution(BaseSolution): 10 | _year = 2020 11 | _day = 3 12 | input_type = InputTypes.STRSPLIT 13 | 14 | def count_trees(self, right: int, down: int) -> int: 15 | width = len(self.input[0]) 16 | trees = 0 17 | 18 | for index, line in enumerate(self.input): 19 | if down > 1 and index % down != 0: 20 | continue 21 | 22 | if line[(index // down * right) % width] == "#": 23 | trees += 1 24 | 25 | return trees 26 | 27 | def solve(self) -> Tuple[int, int]: 28 | slopes = [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)] 29 | 30 | collisions = [self.count_trees(*slope) for slope in slopes] 31 | 32 | return collisions[1], prod(collisions) 33 | -------------------------------------------------------------------------------- /solutions/2020/day_04/README.md: -------------------------------------------------------------------------------- 1 | # Day 4 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/4 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_05/README.md: -------------------------------------------------------------------------------- 1 | # Day 5 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/5 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/5 2 | 3 | from math import floor 4 | from typing import Tuple 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | 9 | def get_midpoint(low: int, high: int) -> int: 10 | return floor((low + high) / 2) 11 | 12 | 13 | def binary_search(instructions: int, top: int) -> int: 14 | low = 0 15 | high = top 16 | mid = floor((high + low) / 2) 17 | for c in instructions: 18 | if c in ("F", "L"): 19 | # bottom half 20 | high = mid 21 | mid = get_midpoint(low, high) 22 | else: 23 | # top half 24 | low = mid + 1 25 | mid = get_midpoint(low, high) 26 | 27 | return mid 28 | 29 | 30 | def calulate_score(assignment: str) -> int: 31 | return (binary_search(assignment[:7], 127) * 8) + binary_search(assignment[7:], 7) 32 | 33 | 34 | class Solution(BaseSolution): 35 | _year = 2020 36 | _day = 5 37 | input_type = InputTypes.STRSPLIT 38 | 39 | def solve(self) -> Tuple[int, int]: 40 | seat_ids = {calulate_score(assignment) for assignment in self.input} 41 | 42 | lowest = min(seat_ids) 43 | highest = max(seat_ids) 44 | 45 | full_range = set(range(lowest, highest + 1)) 46 | 47 | return highest, (full_range - seat_ids).pop() 48 | -------------------------------------------------------------------------------- /solutions/2020/day_06/README.md: -------------------------------------------------------------------------------- 1 | # Day 6 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/6 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/6 2 | 3 | from ...base import BaseSolution 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2020 8 | _day = 6 9 | 10 | def part_1(self) -> int: 11 | groups = self.input.split("\n\n") 12 | total = 0 13 | for group in groups: 14 | all_chars_as_str = "".join(group.split("\n")) 15 | total += len(set(all_chars_as_str)) 16 | return total 17 | 18 | def part_2(self) -> int: 19 | groups = self.input.split("\n\n") 20 | total = 0 21 | for group in groups: 22 | answers = [set(x) for x in group.split("\n")] 23 | total += len(answers[0].intersection(*answers[1:])) 24 | 25 | return total 26 | -------------------------------------------------------------------------------- /solutions/2020/day_07/README.md: -------------------------------------------------------------------------------- 1 | # Day 7 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/7 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_08/README.md: -------------------------------------------------------------------------------- 1 | # Day 8 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/8 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_09/README.md: -------------------------------------------------------------------------------- 1 | # Day 9 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/9 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_09/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/9 2 | 3 | from itertools import combinations 4 | from typing import Tuple 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | PREAMBLE_SIZE = 25 9 | 10 | 11 | class Solution(BaseSolution): 12 | _year = 2020 13 | _day = 9 14 | input_type = InputTypes.INTSPLIT 15 | 16 | def solve(self) -> Tuple[int, int]: 17 | bad_num: int = None 18 | 19 | for i in range(PREAMBLE_SIZE, len(self.input)): 20 | sums = { 21 | sum(pair) for pair in combinations(self.input[i - PREAMBLE_SIZE : i], 2) 22 | } 23 | if self.input[i] not in sums: 24 | bad_num = self.input[i] 25 | 26 | for range_start in range(len(self.input)): 27 | # starting at the range_start index, keep adding 28 | # subsequent numbers until either we hit our number or surpass it 29 | running_sum = self.input[range_start] 30 | for range_end in range(range_start + 1, len(self.input)): 31 | running_sum += self.input[range_end] 32 | if running_sum == bad_num: 33 | continuous_range = self.input[range_start : range_end + 1] 34 | return bad_num, min(continuous_range) + max(continuous_range) 35 | if running_sum > bad_num: 36 | break 37 | return None 38 | -------------------------------------------------------------------------------- /solutions/2020/day_10/README.md: -------------------------------------------------------------------------------- 1 | # Day 10 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/10 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/10 2 | 3 | from collections import defaultdict 4 | from typing import List 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | # from typing import Tuple 9 | 10 | 11 | class Solution(BaseSolution): 12 | _year = 2020 13 | _day = 10 14 | input_type = InputTypes.INTSPLIT 15 | 16 | def part_1(self) -> int: 17 | sorted_input: List[int] = sorted(self.input) 18 | adapters = [0, *sorted_input, sorted_input[-1] + 3] 19 | 20 | differences = defaultdict(int) 21 | for index, val in enumerate(adapters[:-1]): 22 | diff = adapters[index + 1] - val 23 | differences[diff] += 1 24 | 25 | return differences[1] * differences[3] 26 | 27 | def part_2(self) -> int: 28 | sorted_input: List[int] = sorted(self.input) 29 | max_value = sorted_input[-1] + 3 30 | adapters = [*sorted_input, max_value] 31 | num_paths = defaultdict(int, {0: 1}) 32 | 33 | for adapter in adapters: 34 | num_paths[adapter] = ( 35 | num_paths[adapter - 1] + num_paths[adapter - 2] + num_paths[adapter - 3] 36 | ) 37 | 38 | return num_paths[max_value] 39 | -------------------------------------------------------------------------------- /solutions/2020/day_11/README.md: -------------------------------------------------------------------------------- 1 | # Day 11 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/11 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_12/README.md: -------------------------------------------------------------------------------- 1 | # Day 12 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/12 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_13/README.md: -------------------------------------------------------------------------------- 1 | # Day 13 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/13 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_13/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/13 2 | 3 | from itertools import count 4 | from math import ceil 5 | 6 | from ...base import BaseSolution, InputTypes 7 | 8 | # from typing import Tuple 9 | 10 | 11 | class Solution(BaseSolution): 12 | _year = 2020 13 | _day = 13 14 | input_type = InputTypes.STRSPLIT 15 | 16 | def part_1(self) -> int: 17 | min_leave_time = int(self.input[0]) 18 | bus_ids = [int(x) for x in self.input[1].split(",") if x != "x"] 19 | departure_time, best_bus = sorted( 20 | [(ceil(min_leave_time / bus_id) * bus_id, bus_id) for bus_id in bus_ids] 21 | )[0] 22 | 23 | return (departure_time - min_leave_time) * best_bus 24 | 25 | def part_2(self) -> int: 26 | buses = [ 27 | (offset, int(bus_id)) 28 | for offset, bus_id in enumerate(self.input[1].split(",")) 29 | if bus_id != "x" 30 | ] 31 | 32 | # https://www.reddit.com/r/adventofcode/comments/kc4njx/2020_day_13_solutions/gfsc2gg/ 33 | 34 | step = 1 35 | timestamp = 0 36 | for offset, bus_id in buses: 37 | timestamp = next( 38 | ts for ts in count(timestamp, step) if (ts + offset) % bus_id == 0 39 | ) 40 | step *= bus_id 41 | return timestamp 42 | -------------------------------------------------------------------------------- /solutions/2020/day_14/README.md: -------------------------------------------------------------------------------- 1 | # Day 14 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/14 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_15/README.md: -------------------------------------------------------------------------------- 1 | # Day 15 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/15 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_15/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/15 2 | 3 | from ...base import BaseSolution, InputTypes, slow 4 | 5 | 6 | class Solution(BaseSolution): 7 | _year = 2020 8 | _day = 15 9 | input_type = InputTypes.INTSPLIT 10 | separator = "," 11 | 12 | def calculate_sequence(self, loops): 13 | # 1-index the turns now so we start our loop with 14 | # the index on the last input element, not after it 15 | mem = {value: index + 1 for index, value in enumerate(self.input)} 16 | last_num = self.input[-1] 17 | 18 | for turn in range(len(self.input), loops): 19 | to_speak = turn - mem[last_num] if last_num in mem else 0 20 | mem[last_num] = turn 21 | last_num = to_speak 22 | 23 | return last_num 24 | 25 | def part_1(self) -> int: 26 | return self.calculate_sequence(2020) 27 | 28 | @slow 29 | def part_2(self) -> int: 30 | return self.calculate_sequence(30_000_000) 31 | -------------------------------------------------------------------------------- /solutions/2020/day_16/README.md: -------------------------------------------------------------------------------- 1 | # Day 16 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/16 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_17/README.md: -------------------------------------------------------------------------------- 1 | # Day 17 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/17 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_18/README.md: -------------------------------------------------------------------------------- 1 | # Day 18 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/18 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_18/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/18 2 | 3 | import ast 4 | from operator import __add__, __mul__ 5 | from typing import Tuple 6 | 7 | from ...base import BaseSolution, InputTypes, answer 8 | 9 | # part 1 10 | 11 | OPERATORS = {"+": __add__, "*": __mul__} 12 | 13 | 14 | def solve(equation: str) -> Tuple[int, int]: 15 | number = None 16 | operator = None 17 | pointer = 0 18 | 19 | while pointer < len(equation): 20 | head = equation[pointer] 21 | if head in OPERATORS: 22 | operator = OPERATORS[head] 23 | else: 24 | if head == ")": 25 | return number, pointer 26 | 27 | if head == "(": 28 | head, offset = solve(equation[pointer + 1 :]) 29 | # extra 1 because the sub-function offset started at 0 30 | pointer += offset + 1 31 | 32 | if operator: 33 | number = operator(int(head), number) 34 | operator = None 35 | else: 36 | number = int(head) 37 | 38 | pointer += 1 39 | 40 | # pointer doesn't matter for final return 41 | return number, 0 42 | 43 | 44 | # part 2 45 | 46 | 47 | class SwapPrecedence(ast.NodeTransformer): 48 | def visit_Sub(self, _): 49 | return ast.Mult() 50 | 51 | def visit_Div(self, _): 52 | return ast.Add() 53 | 54 | 55 | class Solution(BaseSolution): 56 | _year = 2020 57 | _day = 18 58 | input_type = InputTypes.STRSPLIT 59 | 60 | @answer(75592527415659) 61 | def part_1(self) -> int: 62 | return sum([solve(line.replace(" ", ""))[0] for line in self.input]) 63 | 64 | @answer(360029542265462) 65 | def part_2(self) -> int: 66 | total = 0 67 | for line in self.input: 68 | tree = ast.parse(line.replace("+", "/").replace("*", "-"), mode="eval") 69 | new_tree = SwapPrecedence().visit(tree) 70 | total += eval(compile(new_tree, filename="", mode="eval")) 71 | 72 | return total 73 | -------------------------------------------------------------------------------- /solutions/2020/day_19/README.md: -------------------------------------------------------------------------------- 1 | # Day 19 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/19 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_19/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/19 2 | 3 | import re 4 | from typing import Tuple 5 | 6 | from ...base import BaseSolution 7 | 8 | 9 | class Solution(BaseSolution): 10 | _year = 2020 11 | _day = 19 12 | rules = {} 13 | num_loops = {"8": 0, "11": 0} # detects loops 14 | 15 | def resolve_rules(self, key): 16 | if key in self.num_loops: 17 | # after 6 levels, the answers stop changing 18 | if self.num_loops[key] == 6: 19 | return "" 20 | self.num_loops[key] += 1 21 | 22 | if self.rules[key] in ("a", "b"): 23 | return self.rules[key] 24 | 25 | result = "" 26 | parts = self.rules[key].split(" ") 27 | for p in parts: 28 | result += "|" if p == "|" else self.resolve_rules(p) 29 | 30 | if "|" in result: 31 | result = f"({result})" 32 | 33 | return result 34 | 35 | def solve(self) -> Tuple[int, int]: 36 | for line in self.input.split("\n\n")[0].split("\n"): 37 | key, rule = line.split(": ") 38 | if '"' in rule: 39 | # single character 40 | rule = rule.strip('"') 41 | 42 | self.rules[key] = rule 43 | 44 | messages = self.input.split("\n\n")[1].split("\n") 45 | 46 | rule_0 = self.resolve_rules("0") 47 | part_1 = len( 48 | [message for message in messages if re.match(f"^{rule_0}$", message)] 49 | ) 50 | 51 | self.rules["8"] = "42 | 42 8" 52 | self.rules["11"] = "42 31 | 42 11 31" 53 | 54 | rule_0 = self.resolve_rules("0") 55 | part_2 = len( 56 | [message for message in messages if re.match(f"^{rule_0}$", message)] 57 | ) 58 | 59 | return part_1, part_2 60 | -------------------------------------------------------------------------------- /solutions/2020/day_20/README.md: -------------------------------------------------------------------------------- 1 | # Day 20 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/20 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_21/README.md: -------------------------------------------------------------------------------- 1 | # Day 21 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/21 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_22/README.md: -------------------------------------------------------------------------------- 1 | # Day 22 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/22 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_23/README.md: -------------------------------------------------------------------------------- 1 | # Day 23 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/23 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_24/README.md: -------------------------------------------------------------------------------- 1 | # Day 24 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/24 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_25/README.md: -------------------------------------------------------------------------------- 1 | # Day 25 (2020) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2020/day/25 4 | 5 | -------------------------------------------------------------------------------- /solutions/2020/day_25/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2020/day/25 2 | 3 | from itertools import count 4 | 5 | from ...base import BaseSolution, InputTypes 6 | 7 | DIVISOR = 20201227 8 | 9 | 10 | class Solution(BaseSolution): 11 | _year = 2020 12 | _day = 25 13 | input_type = InputTypes.INTSPLIT 14 | 15 | def part_1(self) -> int: 16 | card_public_key, door_public_key = self.input 17 | 18 | val = 1 19 | card_loop_size = None # to help pylance 20 | for card_loop_size in count(start=1): 21 | val = (val * 7) % DIVISOR 22 | if val == card_public_key: 23 | break 24 | 25 | answer = pow(door_public_key, card_loop_size, DIVISOR) 26 | 27 | assert answer == 12285001 28 | return answer 29 | 30 | def part_2(self) -> int: 31 | pass 32 | -------------------------------------------------------------------------------- /solutions/2021/day_01/README.md: -------------------------------------------------------------------------------- 1 | # Day 1 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/1 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/1 2 | 3 | from typing import List 4 | 5 | from ...base import IntSplitSolution, answer 6 | 7 | 8 | def num_increases(nums: List[int]) -> int: 9 | total = 0 10 | 11 | for index, i in enumerate(nums): 12 | if index > 0 and i > nums[index - 1]: 13 | total += 1 14 | 15 | return total 16 | 17 | 18 | class Solution(IntSplitSolution): 19 | _year = 2021 20 | _day = 1 21 | 22 | @answer(1711) 23 | def part_1(self) -> int: 24 | return num_increases(self.input) 25 | 26 | @answer(1743) 27 | def part_2(self) -> int: 28 | return num_increases( 29 | [sum(x) for x in zip(self.input, self.input[1:], self.input[2:])] 30 | ) 31 | -------------------------------------------------------------------------------- /solutions/2021/day_02/README.md: -------------------------------------------------------------------------------- 1 | # Day 2 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/2 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/2 2 | 3 | from ...base import StrSplitSolution, answer 4 | 5 | 6 | class Solution(StrSplitSolution): 7 | _year = 2021 8 | _day = 2 9 | 10 | @answer(1714950) 11 | def part_1(self) -> int: 12 | depth = 0 13 | horiz = 0 14 | 15 | for ins in self.input: 16 | direction, value_str = ins.split(" ") 17 | value = int(value_str) 18 | if direction == "forward": 19 | horiz += value 20 | elif direction == "up": 21 | depth -= value 22 | elif direction == "down": 23 | depth += value 24 | else: 25 | raise ValueError(f'Unrecognized direction: "{direction}"') 26 | 27 | return depth * horiz 28 | 29 | @answer(1281977850) 30 | def part_2(self) -> int: 31 | depth = 0 32 | horiz = 0 33 | aim = 0 34 | 35 | for ins in self.input: 36 | direction, value_str = ins.split(" ") 37 | value = int(value_str) 38 | if direction == "forward": 39 | horiz += value 40 | depth += aim * value 41 | elif direction == "up": 42 | aim -= value 43 | elif direction == "down": 44 | aim += value 45 | else: 46 | raise ValueError(f'Unrecognized direction: "{direction}"') 47 | 48 | return depth * horiz 49 | -------------------------------------------------------------------------------- /solutions/2021/day_03/README.md: -------------------------------------------------------------------------------- 1 | # Day 3 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/3 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/3 2 | 3 | from collections import Counter 4 | from typing import Literal, Union 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | one_or_zero = Union[Literal["0"], Literal["1"]] 9 | 10 | 11 | class Solution(StrSplitSolution): 12 | _year = 2021 13 | _day = 3 14 | 15 | @answer(3958484) 16 | def part_1(self) -> int: 17 | gamma = "".join( 18 | [ 19 | Counter(place_values).most_common(1)[0][0] 20 | for place_values in zip(*self.input) 21 | ] 22 | ) 23 | epsilon = "".join(["0" if c == "1" else "1" for c in gamma]) 24 | return int(gamma, 2) * int(epsilon, 2) 25 | 26 | @answer(1613181) 27 | def part_2(self) -> int: 28 | o2 = self.filter_list("1", "0", "1") 29 | co2 = self.filter_list("0", "1", "0") 30 | return int(o2, 2) * int(co2, 2) 31 | 32 | def filter_list( 33 | self, 34 | val_if_1_higher: one_or_zero, 35 | val_if_0_higher: one_or_zero, 36 | val_if_tied: one_or_zero, 37 | ) -> str: 38 | values = self.input 39 | result = "" 40 | 41 | while len(values) > 1: 42 | place_to_check = len(result) 43 | counts = Counter([i[place_to_check] for i in values]) 44 | if counts["1"] > counts["0"]: 45 | result += val_if_1_higher 46 | elif counts["0"] > counts["1"]: 47 | result += val_if_0_higher 48 | else: 49 | result += val_if_tied 50 | 51 | values = [n for n in values if n.startswith(result)] 52 | 53 | return values[0] 54 | -------------------------------------------------------------------------------- /solutions/2021/day_04/README.md: -------------------------------------------------------------------------------- 1 | # Day 4 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/4 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_05/README.md: -------------------------------------------------------------------------------- 1 | # Day 5 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/5 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_06/README.md: -------------------------------------------------------------------------------- 1 | # Day 6 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/6 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/6 2 | 3 | from collections import defaultdict 4 | from typing import Tuple 5 | 6 | from ...base import IntSplitSolution, answer 7 | 8 | 9 | class Solution(IntSplitSolution): 10 | _year = 2021 11 | _day = 6 12 | separator = "," 13 | 14 | @answer((350605, 1592778185024)) 15 | def solve(self) -> Tuple[int, int]: 16 | part_1 = 0 17 | 18 | result = defaultdict(int) 19 | 20 | for i in self.input: 21 | result[i] += 1 22 | 23 | for day in range(256): 24 | if day == 80: 25 | part_1 = sum(result.values()) 26 | 27 | new_result = defaultdict(int) 28 | for fish, num in result.items(): 29 | if fish == 0: 30 | new_result[6] += num 31 | new_result[8] += num 32 | else: 33 | new_result[fish - 1] += num 34 | result = new_result 35 | 36 | return part_1, sum(result.values()) 37 | -------------------------------------------------------------------------------- /solutions/2021/day_07/README.md: -------------------------------------------------------------------------------- 1 | # Day 7 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/7 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_07/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/7 2 | 3 | from functools import cache 4 | 5 | from ...base import IntSplitSolution, answer 6 | 7 | 8 | @cache 9 | def range_sum(i: int) -> int: 10 | return i * (i + 1) // 2 11 | 12 | 13 | class Solution(IntSplitSolution): 14 | _year = 2021 15 | _day = 7 16 | separator = "," 17 | 18 | @answer(340056) 19 | def part_1(self) -> int: 20 | min_result = sum(self.input) # distance to 0 21 | for i in range(1, max(self.input)): 22 | distance_to_i = sum([abs(i - x) for x in self.input]) 23 | min_result = min(min_result, distance_to_i) 24 | 25 | return min_result 26 | 27 | @answer(96592275) 28 | def part_2(self) -> int: 29 | min_result = 100_000_000 30 | for i in range(1, max(self.input)): 31 | distance_to_i = sum([range_sum(abs(i - x)) for x in self.input]) 32 | min_result = min(min_result, distance_to_i) 33 | 34 | return min_result 35 | -------------------------------------------------------------------------------- /solutions/2021/day_08/README.md: -------------------------------------------------------------------------------- 1 | # Day 8 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/8 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_09/README.md: -------------------------------------------------------------------------------- 1 | # Day 9 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/9 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_09/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/9 2 | 3 | from typing import List, Set, Tuple 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | Point = Tuple[int, int] 8 | 9 | 10 | class Solution(StrSplitSolution): 11 | _year = 2021 12 | _day = 9 13 | 14 | def neighbors(self, row: int, col: int) -> List[Point]: 15 | neighbors: List[Point] = [] 16 | if col > 0: 17 | neighbors.append((row, col - 1)) 18 | if col < len(self.input[row]) - 1: 19 | neighbors.append((row, col + 1)) 20 | 21 | if row > 0: 22 | neighbors.append((row - 1, col)) 23 | if row < len(self.input) - 1: 24 | neighbors.append((row + 1, col)) 25 | 26 | return [n for n in neighbors if self.value_at(*n) != 9] 27 | 28 | def value_at(self, row: int, col: int) -> int: 29 | return int(self.input[row][col]) 30 | 31 | @answer((548, 786048)) 32 | def solve(self) -> Tuple[int, int]: 33 | total = 0 34 | basins: List[Set[Point]] = [] 35 | all_points: Set[Point] = set() 36 | 37 | for row in range(len(self.input)): 38 | for col in range(len(self.input[0])): 39 | loc = (row, col) 40 | if loc in all_points or self.value_at(*loc) == 9: 41 | continue 42 | 43 | # explore this basin 44 | to_explore = [loc] 45 | basin: Set[Point] = set() 46 | while to_explore: 47 | p = to_explore.pop() 48 | if p not in basin: 49 | basin.add(p) 50 | to_explore += self.neighbors(*p) 51 | 52 | total += min(self.value_at(*p) for p in basin) + 1 53 | all_points.update(basin) 54 | basins.append(basin) 55 | 56 | big_basins = sorted(basins, key=len, reverse=True)[:3] 57 | basin_product = len(big_basins[0]) * len(big_basins[1]) * len(big_basins[2]) 58 | return total, basin_product 59 | -------------------------------------------------------------------------------- /solutions/2021/day_10/README.md: -------------------------------------------------------------------------------- 1 | # Day 10 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/10 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/10 2 | 3 | from typing import Iterable, List, Tuple 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | OPENERS = { 8 | "(": ")", 9 | "[": "]", 10 | "{": "}", 11 | "<": ">", 12 | } 13 | CLOSERS_INVALID = { 14 | ")": 3, 15 | "]": 57, 16 | "}": 1197, 17 | ">": 25137, 18 | } 19 | CLOSERS_MATCHING = { 20 | ")": 1, 21 | "]": 2, 22 | "}": 3, 23 | ">": 4, 24 | } 25 | 26 | 27 | def closing_score(nums: Iterable[int]) -> int: 28 | score = 0 29 | for i in nums: 30 | score *= 5 31 | score += i 32 | return score 33 | 34 | 35 | class Solution(StrSplitSolution): 36 | _year = 2021 37 | _day = 10 38 | 39 | @answer((464991, 3662008566)) 40 | def solve(self) -> Tuple[int, int]: 41 | invalid_score = 0 42 | valid_line_scores: List[int] = [] 43 | for line in self.input: 44 | was_valid_line = True 45 | incomplete_pairs: List[str] = [] 46 | for c in line: 47 | if c in OPENERS: 48 | incomplete_pairs.append(c) 49 | else: 50 | if OPENERS[incomplete_pairs[-1]] != c: 51 | # invalid line! 52 | invalid_score += CLOSERS_INVALID[c] 53 | was_valid_line = False 54 | break 55 | 56 | incomplete_pairs.pop() 57 | 58 | if was_valid_line: 59 | closers_needed = reversed( 60 | [CLOSERS_MATCHING[OPENERS[p]] for p in incomplete_pairs] 61 | ) 62 | valid_line_scores.append(closing_score(closers_needed)) 63 | 64 | middle_score = sorted(valid_line_scores)[len(valid_line_scores) // 2] 65 | 66 | return invalid_score, middle_score 67 | -------------------------------------------------------------------------------- /solutions/2021/day_11/README.md: -------------------------------------------------------------------------------- 1 | # Day 11 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/11 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_12/README.md: -------------------------------------------------------------------------------- 1 | # Day 12 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/12 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_13/README.md: -------------------------------------------------------------------------------- 1 | # Day 13 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/13 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_14/README.md: -------------------------------------------------------------------------------- 1 | # Day 14 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/14 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_14/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/14 2 | 3 | from collections import Counter 4 | from typing import Dict, Tuple 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | 9 | class Solution(StrSplitSolution): 10 | _year = 2021 11 | _day = 14 12 | 13 | def _solve(self, steps: int) -> int: 14 | formula: Dict[Tuple, str] = {} 15 | for line in self.input[2:]: 16 | inp, out = line.split(" -> ") 17 | formula[tuple(inp)] = out 18 | 19 | chain = Counter(zip(self.input[0], self.input[0][1:])) 20 | counts = Counter(self.input[0]) 21 | 22 | for _ in range(steps): 23 | # list() so we can modify chain as we go 24 | for (l, r), num in list(chain.items()): 25 | if not num: 26 | continue 27 | 28 | chain[(l, r)] -= num 29 | out = formula[(l, r)] 30 | counts[out] += num 31 | 32 | chain[(l, out)] += num 33 | chain[(out, r)] += num 34 | 35 | return max(counts.values()) - min(counts.values()) 36 | 37 | @answer((2657, 2911561572630)) 38 | def solve(self) -> Tuple[int, int]: 39 | return self._solve(10), self._solve(40) 40 | -------------------------------------------------------------------------------- /solutions/2021/day_15/README.md: -------------------------------------------------------------------------------- 1 | # Day 15 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/15 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_16/README.md: -------------------------------------------------------------------------------- 1 | # Day 16 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/16 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_17/README.md: -------------------------------------------------------------------------------- 1 | # Day 17 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/17 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_17/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/17 2 | 3 | from dataclasses import dataclass 4 | from re import findall 5 | from typing import Tuple, cast 6 | 7 | from ...base import TextSolution, answer 8 | 9 | Range = Tuple[int, int] 10 | 11 | 12 | @dataclass 13 | class Probe: 14 | vel_x: int 15 | vel_y: int 16 | targ_x: Range 17 | targ_y: Range 18 | 19 | pos_x = 0 20 | pos_y = 0 21 | 22 | def step(self) -> None: 23 | self.pos_x += self.vel_x 24 | self.pos_y += self.vel_y 25 | 26 | if self.vel_x < 0: 27 | self.vel_x += 1 28 | if self.vel_x > 0: 29 | self.vel_x -= 1 30 | 31 | self.vel_y -= 1 32 | 33 | def is_in_target(self) -> bool: 34 | return (self.targ_x[0] <= self.pos_x <= self.targ_x[1]) and ( 35 | self.targ_y[0] <= self.pos_y <= self.targ_y[1] 36 | ) 37 | 38 | def fly(self) -> bool: 39 | """ 40 | returns true if probe hit the target, false otherwise 41 | """ 42 | 43 | while self.pos_x <= self.targ_x[1] and self.pos_y >= self.targ_y[0]: 44 | self.step() 45 | if self.is_in_target(): 46 | return True 47 | 48 | return False 49 | 50 | 51 | class Solution(TextSolution): 52 | _year = 2021 53 | _day = 17 54 | 55 | @answer((7381, 3019)) 56 | def solve(self) -> Tuple[int, int]: 57 | ranges = [ 58 | cast(Range, tuple(map(int, x))) 59 | for x in findall(r"(-?\d+)\.\.(-?\d+)", self.input) 60 | ] 61 | assert len(ranges) == 2, "Found too many ranges from input" 62 | 63 | y1 = ranges[1][0] 64 | # math magic based on physics 65 | max_y = y1 * (y1 + 1) // 2 66 | 67 | total = 0 68 | for x in range(int((ranges[0][0] * 2) ** 0.5), ranges[0][1] + 1): 69 | for y in range(y1, -y1): 70 | p = Probe(x, y, *ranges) 71 | if p.fly(): 72 | total += 1 73 | 74 | return max_y, total 75 | -------------------------------------------------------------------------------- /solutions/2021/day_18/README.md: -------------------------------------------------------------------------------- 1 | # Day 18 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/18 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_19/README.md: -------------------------------------------------------------------------------- 1 | # Day 19 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/19 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_20/README.md: -------------------------------------------------------------------------------- 1 | # Day 20 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/20 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_20/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2021/day/20 2 | 3 | 4 | from typing import Dict, Literal, Tuple, Union, cast 5 | 6 | from ...base import StrSplitSolution, answer, slow 7 | from ...utils.graphs import GridPoint, neighbors 8 | 9 | Marker = Union[Literal["#"], Literal["."]] 10 | Image = Dict[GridPoint, bool] 11 | 12 | 13 | class Solution(StrSplitSolution): 14 | _year = 2021 15 | _day = 20 16 | 17 | @slow 18 | @answer((5268, 16875)) 19 | def solve(self) -> Tuple[int, int]: 20 | enhancer = {x for x in range(len(self.input[0])) if self.input[0][x] == "#"} 21 | # the bounds are initialized as `.`, regardless of if we swap 22 | if_missing = False 23 | # we only swap if a field of `.` should become a `#` 24 | should_swap = self.input[0][0] == "#" 25 | results = [] 26 | 27 | # read input 28 | image: Image = {} 29 | for y, line in enumerate(self.input[2:]): 30 | for x, c in enumerate(line): 31 | image[(x, y)] = c == "#" 32 | 33 | # run 34 | for i in range(50): 35 | new_image: Image = {} 36 | 37 | for pixel in image: 38 | for neighbor in neighbors(pixel, 9): 39 | # this line brings a 5x speed increase 40 | if neighbor in new_image: 41 | continue 42 | result = [ 43 | "1" if image.get(n, if_missing) else "0" 44 | for n in neighbors(neighbor, 9) 45 | ] 46 | new_image[neighbor] = int("".join(result), 2) in enhancer 47 | 48 | image = new_image 49 | 50 | if i in [1, 49]: 51 | results.append(len([x for x in image.values() if x])) 52 | if should_swap: 53 | if_missing = not if_missing 54 | 55 | return cast(Tuple[int, int], tuple(results)) 56 | -------------------------------------------------------------------------------- /solutions/2021/day_21/README.md: -------------------------------------------------------------------------------- 1 | # Day 21 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/21 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_22/README.md: -------------------------------------------------------------------------------- 1 | # Day 22 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/22 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_23/README.md: -------------------------------------------------------------------------------- 1 | # Day 23 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/23 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_24/README.md: -------------------------------------------------------------------------------- 1 | # Day 24 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/24 4 | 5 | -------------------------------------------------------------------------------- /solutions/2021/day_25/README.md: -------------------------------------------------------------------------------- 1 | # Day 25 (2021) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2021/day/25 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_01/README.md: -------------------------------------------------------------------------------- 1 | # Day 1 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/1 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_01/input.test.txt: -------------------------------------------------------------------------------- 1 | 1000 2 | 2000 3 | 3000 4 | 5 | 4000 6 | 7 | 5000 8 | 6000 9 | 10 | 7000 11 | 8000 12 | 9000 13 | 14 | 10000 15 | -------------------------------------------------------------------------------- /solutions/2022/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/1 2 | 3 | from typing import Tuple 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | class Solution(StrSplitSolution): 9 | _year = 2022 10 | _day = 1 11 | 12 | separator = "\n\n" 13 | 14 | @answer((68292, 203203)) 15 | def solve(self) -> Tuple[int, int]: 16 | elves = sorted(sum(map(int, elf.split("\n"))) for elf in self.input) 17 | return elves[-1], sum(elves[-3:]) 18 | -------------------------------------------------------------------------------- /solutions/2022/day_02/README.md: -------------------------------------------------------------------------------- 1 | # Day 2 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/2 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_02/input.test.txt: -------------------------------------------------------------------------------- 1 | A Y 2 | B X 3 | C Z 4 | -------------------------------------------------------------------------------- /solutions/2022/day_03/README.md: -------------------------------------------------------------------------------- 1 | # Day 3 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/3 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_03/input.test.txt: -------------------------------------------------------------------------------- 1 | vJrwpWtwJgWrhcsFMMfFFhFp 2 | jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL 3 | PmmdzqPrVvPwwTWBwg 4 | wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn 5 | ttgJtRGJQctTZtZT 6 | CrZsJsPPZsGzwwsLwLmpwMDw 7 | -------------------------------------------------------------------------------- /solutions/2022/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/3 2 | 3 | from string import ascii_letters 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | def priority(letter: str) -> int: 9 | return ascii_letters.index(letter) + 1 10 | 11 | 12 | def overlap_value(rucksack: str) -> int: 13 | mid_point = len(rucksack) // 2 14 | shared = set(rucksack[:mid_point]) & set(rucksack[mid_point:]) 15 | return priority(shared.pop()) 16 | 17 | 18 | class Solution(StrSplitSolution): 19 | _year = 2022 20 | _day = 3 21 | 22 | @answer(8153) 23 | def part_1(self) -> int: 24 | return sum(overlap_value(rucksack) for rucksack in self.input) 25 | 26 | def groups(self): 27 | """Yield successive groups of 3 from input""" 28 | n = 3 29 | for i in range(0, len(self.input), n): 30 | yield self.input[i : i + n] 31 | 32 | @answer(2342) 33 | def part_2(self) -> int: 34 | return sum( 35 | priority((set(a) & set(b) & set(c)).pop()) for a, b, c in self.groups() 36 | ) 37 | -------------------------------------------------------------------------------- /solutions/2022/day_04/README.md: -------------------------------------------------------------------------------- 1 | # Day 4 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/4 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_04/input.test.txt: -------------------------------------------------------------------------------- 1 | 2-4,6-8 2 | 2-3,4-5 3 | 5-7,7-9 4 | 2-8,3-7 5 | 6-6,4-6 6 | 2-6,4-8 7 | -------------------------------------------------------------------------------- /solutions/2022/day_04/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/4 2 | 3 | from typing import Callable, Set 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | def pair_to_range(pair: str) -> Set[int]: 9 | start, stop = pair.split("-") 10 | return set(range(int(start), int(stop) + 1)) 11 | 12 | 13 | class Solution(StrSplitSolution): 14 | _year = 2022 15 | _day = 4 16 | 17 | def _solve(self, f: Callable[[Set[int], Set[int]], bool]) -> int: 18 | return sum(f(*map(pair_to_range, line.split(","))) for line in self.input) 19 | 20 | @answer(576) 21 | def part_1(self) -> int: 22 | return self._solve(lambda a, b: a <= b or b <= a) 23 | 24 | @answer(905) 25 | def part_2(self) -> int: 26 | return self._solve(lambda a, b: bool(a & b)) 27 | -------------------------------------------------------------------------------- /solutions/2022/day_05/README.md: -------------------------------------------------------------------------------- 1 | # Day 5 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/5 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_05/input.test.txt: -------------------------------------------------------------------------------- 1 | [D] 2 | [N] [C] 3 | [Z] [M] [P] 4 | 1 2 3 5 | 6 | move 1 from 2 to 1 7 | move 3 from 1 to 3 8 | move 2 from 2 to 1 9 | move 1 from 1 to 2 10 | -------------------------------------------------------------------------------- /solutions/2022/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/5 2 | 3 | 4 | from itertools import zip_longest 5 | from typing import Callable, Generator, Tuple 6 | 7 | from ...base import StrSplitSolution, answer 8 | 9 | 10 | class Solution(StrSplitSolution): 11 | _year = 2022 12 | _day = 5 13 | 14 | separator = "\n\n" 15 | 16 | def parse_stacks(self): 17 | lines = self.input[0].split("\n") 18 | misc_groups = zip_longest(*reversed(lines)) 19 | result = [[]] 20 | for group in misc_groups: 21 | head = group[0] or "" 22 | if not head.strip().isdigit(): 23 | continue 24 | 25 | result.append([c for c in group[1:] if c and c.strip()]) 26 | 27 | return result 28 | 29 | def parse_instructions(self) -> Generator[Tuple[int, int, int], None, None]: 30 | for l in self.input[1].split("\n"): 31 | _, num, _, src, _, dst = l.split(" ") 32 | yield int(num), int(src), int(dst) 33 | 34 | def _solve(self, slicer: Callable[[int], slice]) -> str: 35 | stacks = self.parse_stacks() 36 | 37 | for num, src, dst in self.parse_instructions(): 38 | stacks[dst] += stacks[src][slicer(num)] 39 | del stacks[src][-num:] 40 | 41 | return "".join(s[-1] for s in stacks[1:]) 42 | 43 | @answer("VGBBJCRMN") 44 | def part_1(self) -> str: 45 | return self._solve(lambda num: slice(None, -num - 1, -1)) 46 | 47 | @answer("LBBVJBRMH") 48 | def part_2(self) -> str: 49 | return self._solve(lambda num: slice(-num, None)) 50 | -------------------------------------------------------------------------------- /solutions/2022/day_06/README.md: -------------------------------------------------------------------------------- 1 | # Day 6 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/6 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_06/input.test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xavdid/advent-of-code/66541bfdb3e9934d4d131d52fd65d2cc0fad1763/solutions/2022/day_06/input.test.txt -------------------------------------------------------------------------------- /solutions/2022/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/6 2 | 3 | 4 | from ...base import TextSolution, answer 5 | 6 | 7 | def find_packet_start(s: str, packet_size: int) -> int: 8 | for i in range(packet_size, len(s)): 9 | if len(set(s[i - packet_size : i])) == packet_size: 10 | return i 11 | raise ValueError("not found") 12 | 13 | 14 | class Solution(TextSolution): 15 | _year = 2022 16 | _day = 6 17 | 18 | @answer(1480) 19 | def part_1(self) -> int: 20 | return find_packet_start(self.input, 4) 21 | 22 | @answer(2746) 23 | def part_2(self) -> int: 24 | return find_packet_start(self.input, 14) 25 | -------------------------------------------------------------------------------- /solutions/2022/day_07/README.md: -------------------------------------------------------------------------------- 1 | # Day 7 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/7 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_07/input.test.txt: -------------------------------------------------------------------------------- 1 | $ cd / 2 | $ ls 3 | dir a 4 | 14848514 b.txt 5 | 8504156 c.dat 6 | dir d 7 | $ cd a 8 | $ ls 9 | dir e 10 | 29116 f 11 | 2557 g 12 | 62596 h.lst 13 | $ cd e 14 | $ ls 15 | 584 i 16 | $ cd .. 17 | $ cd .. 18 | $ cd d 19 | $ ls 20 | 4060174 j 21 | 8033020 d.log 22 | 5626152 d.ext 23 | 7214296 k 24 | -------------------------------------------------------------------------------- /solutions/2022/day_08/README.md: -------------------------------------------------------------------------------- 1 | # Day 8 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/8 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_08/input.test.txt: -------------------------------------------------------------------------------- 1 | 30373 2 | 25512 3 | 65332 4 | 33549 5 | 35390 6 | 7 | -------------------------------------------------------------------------------- /solutions/2022/day_09/README.md: -------------------------------------------------------------------------------- 1 | # Day 9 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/9 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_09/input.test.txt: -------------------------------------------------------------------------------- 1 | R 5 2 | U 8 3 | L 8 4 | D 3 5 | R 17 6 | D 10 7 | L 25 8 | U 20 9 | -------------------------------------------------------------------------------- /solutions/2022/day_10/README.md: -------------------------------------------------------------------------------- 1 | # Day 10 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/10 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_10/input.test.txt: -------------------------------------------------------------------------------- 1 | addx 15 2 | addx -11 3 | addx 6 4 | addx -3 5 | addx 5 6 | addx -1 7 | addx -8 8 | addx 13 9 | addx 4 10 | noop 11 | addx -1 12 | addx 5 13 | addx -1 14 | addx 5 15 | addx -1 16 | addx 5 17 | addx -1 18 | addx 5 19 | addx -1 20 | addx -35 21 | addx 1 22 | addx 24 23 | addx -19 24 | addx 1 25 | addx 16 26 | addx -11 27 | noop 28 | noop 29 | addx 21 30 | addx -15 31 | noop 32 | noop 33 | addx -3 34 | addx 9 35 | addx 1 36 | addx -3 37 | addx 8 38 | addx 1 39 | addx 5 40 | noop 41 | noop 42 | noop 43 | noop 44 | noop 45 | addx -36 46 | noop 47 | addx 1 48 | addx 7 49 | noop 50 | noop 51 | noop 52 | addx 2 53 | addx 6 54 | noop 55 | noop 56 | noop 57 | noop 58 | noop 59 | addx 1 60 | noop 61 | noop 62 | addx 7 63 | addx 1 64 | noop 65 | addx -13 66 | addx 13 67 | addx 7 68 | noop 69 | addx 1 70 | addx -33 71 | noop 72 | noop 73 | noop 74 | addx 2 75 | noop 76 | noop 77 | noop 78 | addx 8 79 | noop 80 | addx -1 81 | addx 2 82 | addx 1 83 | noop 84 | addx 17 85 | addx -9 86 | addx 1 87 | addx 1 88 | addx -3 89 | addx 11 90 | noop 91 | noop 92 | addx 1 93 | noop 94 | addx 1 95 | noop 96 | noop 97 | addx -13 98 | addx -19 99 | addx 1 100 | addx 3 101 | addx 26 102 | addx -30 103 | addx 12 104 | addx -1 105 | addx 3 106 | addx 1 107 | noop 108 | noop 109 | noop 110 | addx -9 111 | addx 18 112 | addx 1 113 | addx 2 114 | noop 115 | noop 116 | addx 9 117 | noop 118 | noop 119 | noop 120 | addx -1 121 | addx 2 122 | addx -37 123 | addx 1 124 | addx 3 125 | noop 126 | addx 15 127 | addx -21 128 | addx 22 129 | addx -6 130 | addx 1 131 | noop 132 | addx 2 133 | addx 1 134 | noop 135 | addx -10 136 | noop 137 | noop 138 | addx 20 139 | addx 1 140 | addx 2 141 | addx 2 142 | addx -6 143 | addx -11 144 | noop 145 | noop 146 | noop 147 | -------------------------------------------------------------------------------- /solutions/2022/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/10 2 | 3 | 4 | from typing import Tuple 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | REPORTING_CYCLES = {20, 60, 100, 140, 180, 220} 9 | 10 | 11 | class Solution(StrSplitSolution): 12 | _year = 2022 13 | _day = 10 14 | 15 | @answer((14420, "RGLRBZAU")) 16 | def solve(self) -> Tuple[int, str]: 17 | x = 1 18 | signal_strength = 0 19 | instruction_pointer = 0 20 | in_addx = False 21 | pixels = [] 22 | for cycle in range(1, 241): 23 | # start cycle! 24 | 25 | instruction = self.input[instruction_pointer].split() 26 | addx = 0 27 | 28 | if instruction == ["noop"]: 29 | pass 30 | elif instruction[0] == "addx": 31 | if in_addx: 32 | in_addx = False 33 | addx = int(instruction[1]) 34 | else: 35 | in_addx = True 36 | 37 | if cycle in REPORTING_CYCLES: 38 | signal_strength += cycle * x 39 | 40 | # draw pixel 41 | if x - 1 <= (cycle - 1) % 40 <= x + 1: 42 | pixels.append("#") 43 | else: 44 | pixels.append(".") 45 | 46 | # end cycle! 47 | 48 | if not in_addx: 49 | x += addx 50 | instruction_pointer += 1 51 | 52 | print() 53 | # print each chunk of 40 pixels on a line 54 | print( 55 | "\n".join( 56 | "".join(l) for l in [pixels[i : i + 40] for i in range(0, 241, 40)] 57 | ) 58 | ) 59 | 60 | # Capital letters should print out in the terminal 61 | return signal_strength, "RGLRBZAU" 62 | -------------------------------------------------------------------------------- /solutions/2022/day_11/README.md: -------------------------------------------------------------------------------- 1 | # Day 11 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/11 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_11/input.test.txt: -------------------------------------------------------------------------------- 1 | Monkey 0: 2 | Starting items: 79, 98 3 | Operation: new = old * 19 4 | Test: divisible by 23 5 | If true: throw to monkey 2 6 | If false: throw to monkey 3 7 | 8 | Monkey 1: 9 | Starting items: 54, 65, 75, 74 10 | Operation: new = old + 6 11 | Test: divisible by 19 12 | If true: throw to monkey 2 13 | If false: throw to monkey 0 14 | 15 | Monkey 2: 16 | Starting items: 79, 60, 97 17 | Operation: new = old * old 18 | Test: divisible by 13 19 | If true: throw to monkey 1 20 | If false: throw to monkey 3 21 | 22 | Monkey 3: 23 | Starting items: 74 24 | Operation: new = old + 3 25 | Test: divisible by 17 26 | If true: throw to monkey 0 27 | If false: throw to monkey 1 28 | -------------------------------------------------------------------------------- /solutions/2022/day_12/README.md: -------------------------------------------------------------------------------- 1 | # Day 12 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/12 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_12/input.test.txt: -------------------------------------------------------------------------------- 1 | Sabqponm 2 | abcryxxl 3 | accszExk 4 | acctuvwj 5 | abdefghi 6 | -------------------------------------------------------------------------------- /solutions/2022/day_13/README.md: -------------------------------------------------------------------------------- 1 | # Day 13 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/13 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_13/input.test.txt: -------------------------------------------------------------------------------- 1 | [1,1,3,1,1] 2 | [1,1,5,1,1] 3 | 4 | [[1],[2,3,4]] 5 | [[1],4] 6 | 7 | [9] 8 | [[8,7,6]] 9 | 10 | [[4,4],4,4] 11 | [[4,4],4,4,4] 12 | 13 | [7,7,7,7] 14 | [7,7,7] 15 | 16 | [] 17 | [3] 18 | 19 | [[[]]] 20 | [[]] 21 | 22 | [1,[2,[3,[4,[5,6,7]]]],8,9] 23 | [1,[2,[3,[4,[5,6,0]]]],8,9] 24 | -------------------------------------------------------------------------------- /solutions/2022/day_14/README.md: -------------------------------------------------------------------------------- 1 | # Day 14 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/14 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_14/input.test.txt: -------------------------------------------------------------------------------- 1 | 498,4 -> 498,6 -> 496,6 2 | 503,4 -> 502,4 -> 502,9 -> 494,9 3 | -------------------------------------------------------------------------------- /solutions/2022/day_15/README.md: -------------------------------------------------------------------------------- 1 | # Day 15 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/15 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_15/input.test.txt: -------------------------------------------------------------------------------- 1 | Sensor at x=2, y=18: closest beacon is at x=-2, y=15 2 | Sensor at x=9, y=16: closest beacon is at x=10, y=16 3 | Sensor at x=13, y=2: closest beacon is at x=15, y=3 4 | Sensor at x=12, y=14: closest beacon is at x=10, y=16 5 | Sensor at x=10, y=20: closest beacon is at x=10, y=16 6 | Sensor at x=14, y=17: closest beacon is at x=10, y=16 7 | Sensor at x=8, y=7: closest beacon is at x=2, y=10 8 | Sensor at x=2, y=0: closest beacon is at x=2, y=10 9 | Sensor at x=0, y=11: closest beacon is at x=2, y=10 10 | Sensor at x=20, y=14: closest beacon is at x=25, y=17 11 | Sensor at x=17, y=20: closest beacon is at x=21, y=22 12 | Sensor at x=16, y=7: closest beacon is at x=15, y=3 13 | Sensor at x=14, y=3: closest beacon is at x=15, y=3 14 | Sensor at x=20, y=1: closest beacon is at x=15, y=3 15 | -------------------------------------------------------------------------------- /solutions/2022/day_16/README.md: -------------------------------------------------------------------------------- 1 | # Day 16 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/16 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_16/input.test.txt: -------------------------------------------------------------------------------- 1 | Valve AA has flow rate=0; tunnels lead to valves DD, II, BB 2 | Valve BB has flow rate=13; tunnels lead to valves CC, AA 3 | Valve CC has flow rate=2; tunnels lead to valves DD, BB 4 | Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE 5 | Valve EE has flow rate=3; tunnels lead to valves FF, DD 6 | Valve FF has flow rate=0; tunnels lead to valves EE, GG 7 | Valve GG has flow rate=0; tunnels lead to valves FF, HH 8 | Valve HH has flow rate=22; tunnel leads to valve GG 9 | Valve II has flow rate=0; tunnels lead to valves AA, JJ 10 | Valve JJ has flow rate=21; tunnel leads to valve II 11 | -------------------------------------------------------------------------------- /solutions/2022/day_17/README.md: -------------------------------------------------------------------------------- 1 | # Day 17 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/17 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_17/input.test.txt: -------------------------------------------------------------------------------- 1 | >>><<><>><<<>><>>><<<>>><<<><<<>><>><<>> 2 | -------------------------------------------------------------------------------- /solutions/2022/day_18/README.md: -------------------------------------------------------------------------------- 1 | # Day 18 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/18 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_18/input.test.txt: -------------------------------------------------------------------------------- 1 | 2,2,2 2 | 1,2,2 3 | 3,2,2 4 | 2,1,2 5 | 2,3,2 6 | 2,2,1 7 | 2,2,3 8 | 2,2,4 9 | 2,2,6 10 | 1,2,5 11 | 3,2,5 12 | 2,1,5 13 | 2,3,5 14 | -------------------------------------------------------------------------------- /solutions/2022/day_19/README.md: -------------------------------------------------------------------------------- 1 | # Day 19 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/19 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_19/input.test.txt: -------------------------------------------------------------------------------- 1 | Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian. 2 | Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian. 3 | -------------------------------------------------------------------------------- /solutions/2022/day_20/README.md: -------------------------------------------------------------------------------- 1 | # Day 20 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/20 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_20/input.test.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | -3 4 | 3 5 | -2 6 | 0 7 | 4 8 | -------------------------------------------------------------------------------- /solutions/2022/day_20/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/20 2 | 3 | from collections import deque 4 | from typing import NamedTuple, Optional 5 | 6 | from ...base import IntSplitSolution, answer 7 | 8 | 9 | class Item(NamedTuple): 10 | id: int 11 | value: int 12 | 13 | 14 | class Solution(IntSplitSolution): 15 | _year = 2022 16 | _day = 20 17 | 18 | def _solve(self, multiplier: int, num_mixes: int): 19 | items = [Item(idx, value * multiplier) for idx, value in enumerate(self.input)] 20 | num_items = len(items) 21 | 22 | d = deque(items) 23 | zero: Optional[Item] = None 24 | 25 | for _ in range(num_mixes): 26 | for item in items: 27 | if zero is None and item.value == 0: 28 | zero = item 29 | 30 | d.rotate(-d.index(item)) 31 | assert d[0] == item 32 | 33 | d.popleft() 34 | 35 | # -1 because we're down one item 36 | rotation = item.value % (num_items - 1) 37 | 38 | d.rotate(-rotation) 39 | d.appendleft(item) 40 | 41 | assert zero 42 | d.rotate(-d.index(zero)) 43 | 44 | return sum(d[c * 1000 % num_items].value for c in [1, 2, 3]) 45 | 46 | @answer(7228) 47 | def part_1(self) -> int: 48 | return self._solve(1, 1) 49 | 50 | @answer(4526232706281) 51 | def part_2(self) -> int: 52 | return self._solve(811589153, 10) 53 | -------------------------------------------------------------------------------- /solutions/2022/day_21/README.md: -------------------------------------------------------------------------------- 1 | # Day 21 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/21 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_21/input.test.txt: -------------------------------------------------------------------------------- 1 | root: pppw + sjmn 2 | dbpl: 5 3 | cczh: sllz + lgvd 4 | zczc: 2 5 | ptdq: humn - dvpt 6 | dvpt: 3 7 | lfqf: 4 8 | humn: 5 9 | ljgn: 2 10 | sjmn: drzm * dbpl 11 | sllz: 4 12 | pppw: cczh / lfqf 13 | lgvd: ljgn * ptdq 14 | drzm: hmdt - zczc 15 | hmdt: 32 16 | -------------------------------------------------------------------------------- /solutions/2022/day_21/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/21 2 | 3 | from operator import add, mul, sub, truediv 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | OPERATIONS = {"+": add, "-": sub, "*": mul, "/": truediv} 8 | 9 | 10 | class Solution(StrSplitSolution): 11 | _year = 2022 12 | _day = 21 13 | 14 | def parse_input(self): 15 | # untyped because exactly one item is `complex` and that's a big pain everywhere else 16 | settled = {} 17 | tbd: dict[str, str] = {} 18 | 19 | for line in self.input: 20 | name, value = line.split(": ") 21 | if value.isdigit(): 22 | settled[name] = int(value) 23 | else: 24 | tbd[name] = value 25 | 26 | return settled, tbd 27 | 28 | def _solve(self, settled: dict, tbd: dict): 29 | while "root" not in settled: 30 | for name, eq in list(tbd.items()): 31 | l, op, r = eq.split() 32 | if l in settled and r in settled: 33 | settled[name] = OPERATIONS[op](settled[l], settled[r]) 34 | tbd.pop(name) 35 | 36 | @answer(78342931359552) 37 | def part_1(self) -> int: 38 | settled, tbd = self.parse_input() 39 | 40 | self._solve(settled, tbd) 41 | 42 | return int(settled["root"]) 43 | 44 | @answer(3296135418820) 45 | def part_2(self) -> int: 46 | settled, tbd = self.parse_input() 47 | 48 | settled["humn"] = 1j 49 | l, _, r = tbd["root"].split() 50 | 51 | self._solve(settled, tbd) 52 | eq, goal = settled[l], settled[r] 53 | 54 | return round((goal - eq.real) / eq.imag) 55 | -------------------------------------------------------------------------------- /solutions/2022/day_22/README.md: -------------------------------------------------------------------------------- 1 | # Day 22 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/22 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_22/input.test.txt: -------------------------------------------------------------------------------- 1 | ...# 2 | .#.. 3 | #... 4 | .... 5 | ...#.......# 6 | ........#... 7 | ..#....#.... 8 | ..........#. 9 | ...#.... 10 | .....#.. 11 | .#...... 12 | ......#. 13 | 14 | 10R5L5R10L4R5L5 15 | -------------------------------------------------------------------------------- /solutions/2022/day_23/README.md: -------------------------------------------------------------------------------- 1 | # Day 23 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/23 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_23/input.test.txt: -------------------------------------------------------------------------------- 1 | .............. 2 | .............. 3 | .......#...... 4 | .....###.#.... 5 | ...#...#.#.... 6 | ....#...##.... 7 | ...#.###...... 8 | ...##.#.##.... 9 | ....#..#...... 10 | .............. 11 | .............. 12 | .............. 13 | -------------------------------------------------------------------------------- /solutions/2022/day_24/README.md: -------------------------------------------------------------------------------- 1 | # Day 24 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/24 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_24/input.test.txt: -------------------------------------------------------------------------------- 1 | #.###### 2 | #>>.<^<# 3 | #.<..<<# 4 | #>v.><># 5 | #<^v^^># 6 | ######.# 7 | -------------------------------------------------------------------------------- /solutions/2022/day_25/README.md: -------------------------------------------------------------------------------- 1 | # Day 25 (2022) 2 | 3 | This writeup has been moved my standalone AoC site: https://advent-of-code.xavd.id/writeups/2022/day/25 4 | 5 | -------------------------------------------------------------------------------- /solutions/2022/day_25/input.test.txt: -------------------------------------------------------------------------------- 1 | 1=-0-2 2 | 12111 3 | 2=0= 4 | 21 5 | 2=01 6 | 111 7 | 20012 8 | 112 9 | 1=-1= 10 | 1-12 11 | 12 12 | 1= 13 | 122 14 | -------------------------------------------------------------------------------- /solutions/2022/day_25/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2022/day/25 2 | 3 | from ...base import StrSplitSolution, answer 4 | 5 | MULTIPLIERS = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2} 6 | 7 | 8 | def from_snafu(s: str) -> int: 9 | return sum(5**idx * MULTIPLIERS[c] for idx, c in enumerate(reversed(s))) 10 | 11 | 12 | def to_snafu(i: int) -> str: 13 | result = "" 14 | 15 | while i > 0: 16 | remainder = i % 5 17 | 18 | if remainder >= 3: 19 | result += {3: "=", 4: "-"}[remainder] 20 | i += 5 - remainder 21 | else: 22 | result += str(remainder) 23 | 24 | i //= 5 25 | 26 | return result[::-1] 27 | 28 | 29 | class Solution(StrSplitSolution): 30 | _year = 2022 31 | _day = 25 32 | 33 | @answer("2=222-2---22=1=--1-2") 34 | def part_1(self) -> str: 35 | target = sum(from_snafu(line) for line in self.input) 36 | return to_snafu(target) 37 | -------------------------------------------------------------------------------- /solutions/2023/day_01/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 1 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/1 4 | -------------------------------------------------------------------------------- /solutions/2023/day_01/input.test.txt: -------------------------------------------------------------------------------- 1 | two1nine 2 | eightwothree 3 | abcone2threexyz 4 | xtwone3four 5 | 4nineeightseven2 6 | zoneight234 7 | 7pqrstsixteen 8 | -------------------------------------------------------------------------------- /solutions/2023/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/1 2 | 3 | 4 | from ...base import StrSplitSolution, answer 5 | 6 | NUMBERS = { 7 | "one": "1", 8 | "two": "2", 9 | "three": "3", 10 | "four": "4", 11 | "five": "5", 12 | "six": "6", 13 | "seven": "7", 14 | "eight": "8", 15 | "nine": "9", 16 | } 17 | 18 | 19 | def calculate_calibration(s: str, replace_nums=False) -> int: 20 | if replace_nums: 21 | for num, digit in NUMBERS.items(): 22 | # don't remove the word, since some words are used twice 23 | s = s.replace(num, f"{num[0]}{digit}{num[-1]}") 24 | 25 | digits = [c for c in s if c.isdigit()] 26 | assert digits, "empty array!" 27 | 28 | return int(digits[0] + digits[-1]) 29 | 30 | 31 | class Solution(StrSplitSolution): 32 | _year = 2023 33 | _day = 1 34 | 35 | @answer(54953) 36 | def part_1(self) -> int: 37 | return sum(calculate_calibration(line) for line in self.input) 38 | 39 | @answer(53868) 40 | def part_2(self) -> int: 41 | return sum( 42 | calculate_calibration(line, replace_nums=True) for line in self.input 43 | ) 44 | -------------------------------------------------------------------------------- /solutions/2023/day_02/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 2 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/2 -------------------------------------------------------------------------------- /solutions/2023/day_02/input.test.txt: -------------------------------------------------------------------------------- 1 | Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green 2 | Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue 3 | Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red 4 | Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red 5 | Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green 6 | -------------------------------------------------------------------------------- /solutions/2023/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/2 2 | 3 | import re 4 | from collections import defaultdict 5 | from functools import reduce 6 | from operator import mul 7 | 8 | from ...base import StrSplitSolution, answer 9 | 10 | 11 | class Solution(StrSplitSolution): 12 | _year = 2023 13 | _day = 2 14 | 15 | @answer(2716) 16 | def part_1(self) -> int: 17 | total = 0 18 | 19 | for idx, game_info in enumerate(self.input): 20 | _, marbles = game_info.split(": ") 21 | if all( 22 | int(count) <= {"red": 12, "green": 13, "blue": 14}[color] 23 | for count, color in re.findall(r"(\d+) (\w+)", marbles) 24 | ): 25 | total += idx + 1 26 | 27 | return total 28 | 29 | @answer(72227) 30 | def part_2(self) -> int: 31 | total = 0 32 | 33 | for game_info in self.input: 34 | _, marbles = game_info.split(": ") 35 | mins: dict[str, int] = defaultdict(int) 36 | 37 | for count, color in re.findall(r"(\d+) (\w+)", marbles): 38 | mins[color] = max(mins[color], int(count)) 39 | 40 | total += reduce(mul, mins.values()) 41 | 42 | return total 43 | -------------------------------------------------------------------------------- /solutions/2023/day_03/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 3 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/3 -------------------------------------------------------------------------------- /solutions/2023/day_03/input.test.txt: -------------------------------------------------------------------------------- 1 | 467..114.. 2 | ...*...... 3 | ..35..633. 4 | ......#... 5 | 617*...... 6 | .....+.58. 7 | ..592..... 8 | ......755. 9 | ...$.*.... 10 | .664.598.. 11 | -------------------------------------------------------------------------------- /solutions/2023/day_04/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 4 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/4 -------------------------------------------------------------------------------- /solutions/2023/day_04/input.test.txt: -------------------------------------------------------------------------------- 1 | Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 2 | Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 3 | Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 4 | Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 5 | Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 6 | Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 7 | -------------------------------------------------------------------------------- /solutions/2023/day_04/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/4 2 | 3 | from collections import defaultdict 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | def count_winning_numbers(line: str) -> int: 9 | winners, nums = line[line.index(":") + 1 :].split(" | ") 10 | 11 | return len(set(winners.split()) & set(nums.split())) 12 | 13 | 14 | class Solution(StrSplitSolution): 15 | _year = 2023 16 | _day = 4 17 | 18 | @answer(28750) 19 | def part_1(self) -> int: 20 | return sum( 21 | 2 ** (num_winners - 1) 22 | for line in self.input 23 | if (num_winners := count_winning_numbers(line)) 24 | ) 25 | 26 | @answer(10212704) 27 | def part_2(self) -> int: 28 | num_copies: defaultdict[int, int] = defaultdict(int) 29 | 30 | for idx, line in enumerate(self.input): 31 | card_id = idx + 1 # 1-index our card numbers! 32 | 33 | num_copies[card_id] += 1 34 | num_winners = count_winning_numbers(line) 35 | 36 | for c in range(card_id + 1, card_id + 1 + num_winners): 37 | num_copies[c] += num_copies[card_id] 38 | 39 | return sum(num_copies.values()) 40 | -------------------------------------------------------------------------------- /solutions/2023/day_05/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 5 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/5 -------------------------------------------------------------------------------- /solutions/2023/day_05/input.test.txt: -------------------------------------------------------------------------------- 1 | seeds: 79 14 55 13 2 | 3 | seed-to-soil map: 4 | 50 98 2 5 | 52 50 48 6 | 7 | soil-to-fertilizer map: 8 | 0 15 37 9 | 37 52 2 10 | 39 0 15 11 | 12 | fertilizer-to-water map: 13 | 49 53 8 14 | 0 11 42 15 | 42 0 7 16 | 57 7 4 17 | 18 | water-to-light map: 19 | 88 18 7 20 | 18 25 70 21 | 22 | light-to-temperature map: 23 | 45 77 23 24 | 81 45 19 25 | 68 64 13 26 | 27 | temperature-to-humidity map: 28 | 0 69 1 29 | 1 0 69 30 | 31 | humidity-to-location map: 32 | 60 56 37 33 | 56 93 4 34 | -------------------------------------------------------------------------------- /solutions/2023/day_06/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 6 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/6 -------------------------------------------------------------------------------- /solutions/2023/day_06/input.test.txt: -------------------------------------------------------------------------------- 1 | Time: 7 15 30 2 | Distance: 9 40 200 3 | -------------------------------------------------------------------------------- /solutions/2023/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/6 2 | 3 | from functools import reduce 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | def num_race_wins(time: int, distance: int) -> int: 9 | return len([1 for t in range(1, time) if t * (time - t) > distance]) 10 | 11 | 12 | class Solution(StrSplitSolution): 13 | _year = 2023 14 | _day = 6 15 | 16 | @answer(2269432) 17 | def part_1(self) -> int: 18 | # parse the input into zipped columns 19 | races = zip(*(map(int, line.split()[1:]) for line in self.input)) 20 | 21 | # multiply all the results together 22 | return reduce(lambda res, race: res * num_race_wins(*race), races, 1) 23 | 24 | @answer(35865985) 25 | def part_2(self) -> int: 26 | time, distance = [int("".join(line.split()[1:])) for line in self.input] 27 | return num_race_wins(time, distance) 28 | -------------------------------------------------------------------------------- /solutions/2023/day_07/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 7 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/7 -------------------------------------------------------------------------------- /solutions/2023/day_07/input.test.txt: -------------------------------------------------------------------------------- 1 | 32T3K 765 2 | T55J5 684 3 | KK677 28 4 | KTJJT 220 5 | QQQJA 483 6 | -------------------------------------------------------------------------------- /solutions/2023/day_08/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 8 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/8 -------------------------------------------------------------------------------- /solutions/2023/day_08/input.test.txt: -------------------------------------------------------------------------------- 1 | LLR 2 | 3 | AAA = (BBB, BBB) 4 | BBB = (AAA, ZZZ) 5 | ZZZ = (ZZZ, ZZZ) 6 | -------------------------------------------------------------------------------- /solutions/2023/day_08/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/8 2 | 3 | import math 4 | import re 5 | from itertools import cycle 6 | from typing import Iterable 7 | 8 | from ...base import TextSolution, answer 9 | 10 | 11 | class Solution(TextSolution): 12 | _year = 2023 13 | _day = 8 14 | 15 | def _parse_input(self) -> tuple[Iterable[str], dict[str, dict[str, str]]]: 16 | raw_directions, raw_nodes = self.input.split("\n\n") 17 | 18 | instructions = cycle(raw_directions) 19 | 20 | nodes: dict[str, dict[str, str]] = {} 21 | for line in raw_nodes.split("\n"): 22 | root, l, r = re.findall(r"[A-Z]{3}", line) 23 | nodes[root] = {"L": l, "R": r} 24 | 25 | return instructions, nodes 26 | 27 | def _solve(self, current: str) -> int: 28 | instructions, nodes = self._parse_input() 29 | for idx, ins in enumerate(instructions): 30 | if current[-1] == "Z": 31 | return idx 32 | current = nodes[current][ins] 33 | 34 | return -1 # unreachable, but make type checker happy 35 | 36 | @answer(14681) 37 | def part_1(self) -> int: 38 | return self._solve("AAA") 39 | 40 | @answer(14321394058031) 41 | def part_2(self) -> int: 42 | _, nodes = self._parse_input() 43 | starts = [k for k in nodes if k[-1] == "A"] 44 | 45 | return math.lcm(*[self._solve(s) for s in starts]) 46 | -------------------------------------------------------------------------------- /solutions/2023/day_09/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 9 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/9 -------------------------------------------------------------------------------- /solutions/2023/day_09/input.test.txt: -------------------------------------------------------------------------------- 1 | 0 3 6 9 12 15 2 | 1 3 6 10 15 21 3 | 10 13 16 21 30 45 4 | -------------------------------------------------------------------------------- /solutions/2023/day_09/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/9 2 | 3 | 4 | from ...base import StrSplitSolution, answer 5 | 6 | NestedList = list[list[int]] 7 | 8 | 9 | def simplify(nums: list[int]) -> NestedList: 10 | result = [nums] 11 | 12 | while True: 13 | last = result[-1] 14 | if all(e == 0 for e in last): 15 | return result 16 | 17 | result.append([r - l for l, r in zip(last, last[1:])]) 18 | last = result[-1] 19 | 20 | 21 | def extrapolate(layers: NestedList) -> int: 22 | for l, r in zip(layers[::-1], layers[::-1][1:]): 23 | r.append(l[-1] + r[-1]) 24 | 25 | return layers[0][-1] 26 | 27 | 28 | class Solution(StrSplitSolution): 29 | _year = 2023 30 | _day = 9 31 | 32 | def _parse_input(self) -> NestedList: 33 | return [list(map(int, line.split())) for line in self.input] 34 | 35 | def _solve(self, reverse: bool) -> int: 36 | histories = self._parse_input() 37 | return sum(extrapolate(simplify(h[::-1] if reverse else h)) for h in histories) 38 | 39 | @answer(1819125966) 40 | def part_1(self) -> int: 41 | return self._solve(reverse=False) 42 | 43 | @answer(1140) 44 | def part_2(self) -> int: 45 | return self._solve(reverse=True) 46 | -------------------------------------------------------------------------------- /solutions/2023/day_10/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 10 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/10 -------------------------------------------------------------------------------- /solutions/2023/day_10/input.test.txt: -------------------------------------------------------------------------------- 1 | FF7FSF7F7F7F7F7F---7 2 | L|LJ||||||||||||F--J 3 | FL-7LJLJ||||||LJL-77 4 | F--JF--7||LJLJ7F7FJ- 5 | L---JF-JLJ.||-FJLJJ7 6 | |F|F-JF---7F7-L7L|7| 7 | |FFJF7L7F-JF7|JL---7 8 | 7-L-JL7||F7|L7F-7F7| 9 | L.L7LFJ|||||FJL7||LJ 10 | L7JLJL-JLJLJL--JLJ.L 11 | -------------------------------------------------------------------------------- /solutions/2023/day_11/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 11 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/11 -------------------------------------------------------------------------------- /solutions/2023/day_11/input.test.txt: -------------------------------------------------------------------------------- 1 | ...#...... 2 | .......#.. 3 | #......... 4 | .......... 5 | ......#... 6 | .#........ 7 | .........# 8 | .......... 9 | .......#.. 10 | #...#..... 11 | -------------------------------------------------------------------------------- /solutions/2023/day_11/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/11 2 | 3 | from itertools import combinations 4 | 5 | from ...base import StrSplitSolution, answer 6 | from ...utils.graphs import GridPoint, parse_grid, taxicab_distance 7 | 8 | 9 | def empty_lines(grid: list[GridPoint], grid_size: int, dim: int) -> set[int]: 10 | return set(range(grid_size)) - {p[dim] for p in grid} 11 | 12 | 13 | def expand_points(val: int, empty_lines: set[int], multiplier: int) -> int: 14 | return len(tuple(filter(lambda i: i < val, empty_lines))) * (multiplier - 1) 15 | 16 | 17 | class Solution(StrSplitSolution): 18 | _year = 2023 19 | _day = 11 20 | 21 | def _solve(self, multiplier: int) -> int: 22 | assert len(self.input) == len(self.input[0]), "not a square grid!" 23 | grid_size = len(self.input) 24 | grid = list(parse_grid(self.input, ignore_chars=".").keys()) 25 | 26 | rows_to_expand = empty_lines(grid, grid_size, dim=0) 27 | cols_to_expand = empty_lines(grid, grid_size, dim=1) 28 | 29 | expanded_points = { 30 | ( 31 | row + expand_points(row, rows_to_expand, multiplier), 32 | col + expand_points(col, cols_to_expand, multiplier), 33 | ) 34 | for row, col in grid 35 | } 36 | 37 | return sum(taxicab_distance(a, b) for a, b in combinations(expanded_points, 2)) 38 | 39 | @answer(10289334) 40 | def part_1(self) -> int: 41 | return self._solve(2) 42 | 43 | @answer(649862989626) 44 | def part_2(self) -> int: 45 | return self._solve(1_000_000) 46 | -------------------------------------------------------------------------------- /solutions/2023/day_12/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 12 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/12 -------------------------------------------------------------------------------- /solutions/2023/day_12/input.test.txt: -------------------------------------------------------------------------------- 1 | ???.### 1,1,3 2 | .??..??...?##. 1,1,3 3 | ?#?#?#?#?#?#?#? 1,3,1,6 4 | ????.#...#... 4,1,1 5 | ????.######..#####. 1,6,5 6 | ?###???????? 3,2,1 7 | -------------------------------------------------------------------------------- /solutions/2023/day_13/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 13 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/13 -------------------------------------------------------------------------------- /solutions/2023/day_13/input.test.txt: -------------------------------------------------------------------------------- 1 | #.##..##. 2 | ..#.##.#. 3 | ##......# 4 | ##......# 5 | ..#.##.#. 6 | ..##..##. 7 | #.#.##.#. 8 | 9 | #...##..# 10 | #....#..# 11 | ..##..### 12 | #####.##. 13 | #####.##. 14 | ..##..### 15 | #....#..# 16 | -------------------------------------------------------------------------------- /solutions/2023/day_13/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/13 2 | 3 | from ...base import TextSolution, answer 4 | 5 | 6 | def distance(l: str, r: str) -> int: 7 | return sum(a != b for a, b in zip(l, r)) 8 | 9 | 10 | def reflection_row(block: list[str], distance_to_match: int) -> int: 11 | for idx in range(1, len(block)): 12 | if ( 13 | sum(distance(l, r) for l, r in zip(reversed(block[:idx]), block[idx:])) 14 | == distance_to_match 15 | ): 16 | return idx 17 | 18 | return 0 19 | 20 | 21 | def score_block(block: str, distance_to_match: int) -> int: 22 | rows = block.split("\n") 23 | if row := reflection_row(rows, distance_to_match): 24 | return 100 * row 25 | 26 | if col := reflection_row(list(zip(*rows)), distance_to_match): 27 | return col 28 | 29 | raise ValueError("no reflection found!") 30 | 31 | 32 | class Solution(TextSolution): 33 | _year = 2023 34 | _day = 13 35 | 36 | @answer(37381) 37 | def part_1(self) -> int: 38 | return sum(score_block(block, 0) for block in self.input.split("\n\n")) 39 | 40 | @answer(28210) 41 | def part_2(self) -> int: 42 | return sum(score_block(block, 1) for block in self.input.split("\n\n")) 43 | -------------------------------------------------------------------------------- /solutions/2023/day_14/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 14 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/14 -------------------------------------------------------------------------------- /solutions/2023/day_14/input.test.txt: -------------------------------------------------------------------------------- 1 | O....#.... 2 | O.OO#....# 3 | .....##... 4 | OO.#O....O 5 | .O.....O#. 6 | O.#..O.#.# 7 | ..O..#O..O 8 | .......O.. 9 | #....###.. 10 | #OO..#.... 11 | -------------------------------------------------------------------------------- /solutions/2023/day_15/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 15 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/15 -------------------------------------------------------------------------------- /solutions/2023/day_15/input.test.txt: -------------------------------------------------------------------------------- 1 | rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7 2 | -------------------------------------------------------------------------------- /solutions/2023/day_15/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/15 2 | 3 | from collections import defaultdict 4 | 5 | from ...base import StrSplitSolution, answer 6 | 7 | 8 | def make_hash(ins: str) -> int: 9 | result = 0 10 | 11 | for c in ins: 12 | result += ord(c) 13 | result *= 17 14 | result %= 256 15 | 16 | return result 17 | 18 | 19 | class Solution(StrSplitSolution): 20 | _year = 2023 21 | _day = 15 22 | 23 | separator = "," 24 | 25 | @answer(519041) 26 | def part_1(self) -> int: 27 | return sum(make_hash(ins) for ins in self.input) 28 | 29 | @answer(260530) 30 | def part_2(self) -> int: 31 | lenses: dict[int, dict[str, int]] = defaultdict(dict) 32 | 33 | for ins in self.input: 34 | if "-" in ins: 35 | label = ins[:-1] 36 | lenses[make_hash(label)].pop(label, None) 37 | elif "=" in ins: 38 | label, val_str = ins.split("=") 39 | lenses[make_hash(label)][label] = int(val_str) 40 | else: 41 | raise ValueError(f"unrecognized pattern: {ins}") 42 | 43 | return sum( 44 | sum( 45 | (box_id + 1) * (lense_pos + 1) * (focal_length) 46 | for lense_pos, focal_length in enumerate(box.values()) 47 | ) 48 | for box_id, box in lenses.items() 49 | ) 50 | -------------------------------------------------------------------------------- /solutions/2023/day_16/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 16 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/16 -------------------------------------------------------------------------------- /solutions/2023/day_16/input.test.txt: -------------------------------------------------------------------------------- 1 | .|...\.... 2 | |.-.\..... 3 | .....|-... 4 | ........|. 5 | .......... 6 | .........\ 7 | ..../.\\.. 8 | .-.-/..|.. 9 | .|....-|.\ 10 | ..//.|.... 11 | -------------------------------------------------------------------------------- /solutions/2023/day_17/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 17 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/17 -------------------------------------------------------------------------------- /solutions/2023/day_17/input.test.txt: -------------------------------------------------------------------------------- 1 | 2413432311323 2 | 3215453535623 3 | 3255245654254 4 | 3446585845452 5 | 4546657867536 6 | 1438598798454 7 | 4457876987766 8 | 3637877979653 9 | 4654967986887 10 | 4564679986453 11 | 1224686865563 12 | 2546548887735 13 | 4322674655533 14 | -------------------------------------------------------------------------------- /solutions/2023/day_18/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 18 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/18 -------------------------------------------------------------------------------- /solutions/2023/day_18/input.test.txt: -------------------------------------------------------------------------------- 1 | R 6 (#70c710) 2 | D 5 (#0dc571) 3 | L 2 (#5713f0) 4 | D 2 (#d2c081) 5 | R 2 (#59c680) 6 | D 2 (#411b91) 7 | L 5 (#8ceee2) 8 | U 2 (#caa173) 9 | L 1 (#1b58a2) 10 | U 2 (#caa171) 11 | R 2 (#7807d2) 12 | U 3 (#a77fa3) 13 | L 2 (#015232) 14 | U 2 (#7a21e3) 15 | -------------------------------------------------------------------------------- /solutions/2023/day_19/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 19 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/19 -------------------------------------------------------------------------------- /solutions/2023/day_19/input.test.txt: -------------------------------------------------------------------------------- 1 | px{a<2006:qkq,m>2090:A,rfg} 2 | pv{a>1716:R,A} 3 | lnx{m>1548:A,A} 4 | rfg{s<537:gd,x>2440:R,A} 5 | qs{s>3448:A,lnx} 6 | qkq{x<1416:A,crn} 7 | crn{x>2662:A,R} 8 | in{s<1351:px,qqz} 9 | qqz{s>2770:qs,m<1801:hdj,R} 10 | gd{a>3333:R,R} 11 | hdj{m>838:A,pv} 12 | 13 | {x=787,m=2655,a=1222,s=2876} 14 | {x=1679,m=44,a=2067,s=496} 15 | {x=2036,m=264,a=79,s=2244} 16 | {x=2461,m=1339,a=466,s=291} 17 | {x=2127,m=1623,a=2188,s=1013} 18 | -------------------------------------------------------------------------------- /solutions/2023/day_20/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 20 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/20 -------------------------------------------------------------------------------- /solutions/2023/day_20/input.test.txt: -------------------------------------------------------------------------------- 1 | broadcaster -> a 2 | %a -> inv, con 3 | &inv -> b 4 | %b -> con 5 | &con -> output 6 | -------------------------------------------------------------------------------- /solutions/2023/day_21/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 21 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/21 -------------------------------------------------------------------------------- /solutions/2023/day_21/input.test.txt: -------------------------------------------------------------------------------- 1 | ......... 2 | #..#..#.. 3 | #.....#.. 4 | ..#...##. 5 | ....S.... 6 | .......#. 7 | #.##.#.#. 8 | ..#..#... 9 | .......#. 10 | -------------------------------------------------------------------------------- /solutions/2023/day_22/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 22 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/22 -------------------------------------------------------------------------------- /solutions/2023/day_22/input.test.txt: -------------------------------------------------------------------------------- 1 | 1,0,1~1,2,1 2 | 0,0,2~2,0,2 3 | 0,2,3~2,2,3 4 | 0,0,4~0,2,4 5 | 2,0,5~2,2,5 6 | 0,1,6~2,1,6 7 | 1,1,8~1,1,9 8 | -------------------------------------------------------------------------------- /solutions/2023/day_23/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 23 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/23 -------------------------------------------------------------------------------- /solutions/2023/day_23/input.test.txt: -------------------------------------------------------------------------------- 1 | #.##################### 2 | #.......#########...### 3 | #######.#########.#.### 4 | ###.....#.>.>.###.#.### 5 | ###v#####.#v#.###.#.### 6 | ###.>...#.#.#.....#...# 7 | ###v###.#.#.#########.# 8 | ###...#.#.#.......#...# 9 | #####.#.#.#######.#.### 10 | #.....#.#.#.......#...# 11 | #.#####.#.#.#########v# 12 | #.#...#...#...###...>.# 13 | #.#.#v#######v###.###v# 14 | #...#.>.#...>.>.#.###.# 15 | #####v#.#.###v#.#.###.# 16 | #.....#...#...#.#.#...# 17 | #.#########.###.#.#.### 18 | #...###...#...#...#.### 19 | ###.###.#.###v#####v### 20 | #...#...#.#.>.>.#.>.### 21 | #.###.###.#.###.#.#v### 22 | #.....###...###...#...# 23 | #####################.# 24 | -------------------------------------------------------------------------------- /solutions/2023/day_24/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 24 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/24 -------------------------------------------------------------------------------- /solutions/2023/day_24/input.test.txt: -------------------------------------------------------------------------------- 1 | 19, 13, 30 @ -2, 1, -2 2 | 18, 19, 22 @ -1, -1, -2 3 | 20, 25, 34 @ -2, -2, -4 4 | 12, 31, 28 @ -1, -2, -1 5 | 20, 19, 15 @ 1, -5, -3 6 | -------------------------------------------------------------------------------- /solutions/2023/day_25/README.md: -------------------------------------------------------------------------------- 1 | # 2023 Day 25 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2023/day/25 -------------------------------------------------------------------------------- /solutions/2023/day_25/input.test.txt: -------------------------------------------------------------------------------- 1 | jqt: rhn xhk nvd 2 | rsh: frs pzl lsr 3 | xhk: hfx 4 | cmg: qnr nvd lhk bvb 5 | rhn: xhk bvb hfx 6 | bvb: xhk hfx 7 | pzl: lsr hfx nvd 8 | qnr: nvd 9 | ntq: jqt hfx bvb xhk 10 | nvd: lhk 11 | lsr: lhk 12 | rzs: qnr cmg lsr rsh 13 | frs: qnr lhk lsr 14 | -------------------------------------------------------------------------------- /solutions/2023/day_25/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2023/day/25 2 | 3 | from collections import defaultdict 4 | from random import choice as random_choice 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | 9 | class Solution(StrSplitSolution): 10 | _year = 2023 11 | _day = 25 12 | 13 | def sort_groups(self, graph: dict[str, set[str]]) -> int: 14 | ejected = random_choice(list(graph.keys())) 15 | 16 | right: set[str] = {ejected} 17 | left = set(graph) - right 18 | 19 | def num_right_neighbors(n: str): 20 | """ 21 | for node `n`, counts how many neighbors it has in the right group 22 | """ 23 | return len(graph[n] & right) 24 | 25 | while sum(map(num_right_neighbors, left)) != 3: 26 | if not left: 27 | # sometimes we get unlucky by ejecting a node too close to the cut point 28 | # in that case, just run again! 29 | self.debug( 30 | f"got unlucky by ejecting `{ejected}` to start and didn't find a result; running again!" 31 | ) 32 | return self.sort_groups(graph) 33 | 34 | node = max(left, key=num_right_neighbors) 35 | left.remove(node) 36 | right.add(node) 37 | 38 | return len(left) * len(right) 39 | 40 | @answer(520380) 41 | def part_1(self) -> int: 42 | """ 43 | NOTE: adapted from https://www.reddit.com/r/adventofcode/comments/18qbsxs/2023_day_25_solutions/ketzp94/ 44 | """ 45 | graph: dict[str, set[str]] = defaultdict(set) 46 | for line in self.input: 47 | root, nodes = line.split(": ") 48 | for node in nodes.split(): 49 | graph[root].add(node) 50 | graph[node].add(root) 51 | 52 | return self.sort_groups(graph) 53 | -------------------------------------------------------------------------------- /solutions/2024/day_01/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 1 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/1 -------------------------------------------------------------------------------- /solutions/2024/day_01/input.test.txt: -------------------------------------------------------------------------------- 1 | 3 4 2 | 4 3 3 | 2 5 4 | 1 3 5 | 3 9 6 | 3 3 7 | -------------------------------------------------------------------------------- /solutions/2024/day_01/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/1 2 | 3 | 4 | from collections import Counter 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | 9 | class Solution(StrSplitSolution): 10 | _year = 2024 11 | _day = 1 12 | 13 | @answer((1223326, 21070419)) 14 | def solve(self) -> tuple[int, int]: 15 | pairs = [map(int, l.split()) for l in self.input] 16 | l, r = [sorted(col) for col in zip(*pairs)] 17 | 18 | total_distance = sum(abs(a - b) for a, b in zip(l, r)) 19 | 20 | c = Counter(r) 21 | similarity_score = sum(i * c.get(i, 0) for i in l) 22 | 23 | return total_distance, similarity_score 24 | -------------------------------------------------------------------------------- /solutions/2024/day_02/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 2 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/2 -------------------------------------------------------------------------------- /solutions/2024/day_02/input.test.txt: -------------------------------------------------------------------------------- 1 | 7 6 4 2 1 2 | 1 2 7 8 9 3 | 9 7 6 2 1 4 | 1 3 2 4 5 5 | 8 6 4 4 1 6 | 1 3 6 7 9 7 | -------------------------------------------------------------------------------- /solutions/2024/day_02/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/2 2 | 3 | from itertools import pairwise 4 | from typing import Iterable 5 | 6 | from ...base import StrSplitSolution, answer 7 | from ...utils.transformations import parse_ints 8 | 9 | 10 | def is_strictly_increasing(vals: list[int]) -> bool: 11 | return all(l < r and 1 <= r - l <= 3 for l, r in pairwise(vals)) 12 | 13 | 14 | def is_safe(vals: list[int]) -> bool: 15 | return is_strictly_increasing(vals) or is_strictly_increasing(vals[::-1]) 16 | 17 | 18 | def omit_one(vals: list[int]) -> Iterable[list[int]]: 19 | for idx in range(len(vals)): 20 | yield vals[:idx] + vals[idx + 1 :] 21 | 22 | 23 | class Solution(StrSplitSolution): 24 | _year = 2024 25 | _day = 2 26 | 27 | @answer(432) 28 | def part_1(self) -> int: 29 | return sum(is_safe(parse_ints(line.split())) for line in self.input) 30 | 31 | @answer(488) 32 | def part_2(self) -> int: 33 | lists = [parse_ints(line.split()) for line in self.input] 34 | 35 | return sum(is_safe(l) or any(is_safe(o) for o in omit_one(l)) for l in lists) 36 | -------------------------------------------------------------------------------- /solutions/2024/day_03/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 3 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/3 -------------------------------------------------------------------------------- /solutions/2024/day_03/input.test.txt: -------------------------------------------------------------------------------- 1 | xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5)) 2 | -------------------------------------------------------------------------------- /solutions/2024/day_03/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/3 2 | 3 | import re 4 | 5 | from ...base import TextSolution, answer 6 | 7 | 8 | class Solution(TextSolution): 9 | _year = 2024 10 | _day = 3 11 | 12 | @answer(173785482) 13 | def part_1(self) -> int: 14 | return sum( 15 | int(l) * int(r) 16 | for l, r in re.findall(r"mul\((\d{1,3}),(\d{1,3})\)", self.input) 17 | ) 18 | 19 | @answer(83158140) 20 | def part_2(self) -> int: 21 | instructions = re.finditer( 22 | r"mul\((\d{1,3}),(\d{1,3})\)|do\(\)|don't\(\)", self.input 23 | ) 24 | 25 | total = 0 26 | active = True 27 | for i in instructions: 28 | command, l, r = i.group(0, 1, 2) 29 | if command == "do()": 30 | active = True 31 | elif command == "don't()": 32 | active = False 33 | elif active: 34 | total += int(l) * int(r) 35 | else: 36 | # we hit mul but are inactive; skip! 37 | pass 38 | 39 | return total 40 | -------------------------------------------------------------------------------- /solutions/2024/day_04/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 4 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/4 -------------------------------------------------------------------------------- /solutions/2024/day_04/input.test.txt: -------------------------------------------------------------------------------- 1 | MMMSXXMASM 2 | MSAMXMSMSA 3 | AMXSXMAAMM 4 | MSAMASMSMX 5 | XMASAMXAMM 6 | XXAMMXXAMA 7 | SMSMSASXSS 8 | SAXAMASAAA 9 | MAMMMXMMMM 10 | MXMXAXMASX 11 | -------------------------------------------------------------------------------- /solutions/2024/day_05/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 5 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/5 -------------------------------------------------------------------------------- /solutions/2024/day_05/input.test.txt: -------------------------------------------------------------------------------- 1 | 47|53 2 | 97|13 3 | 97|61 4 | 97|47 5 | 75|29 6 | 61|13 7 | 75|53 8 | 29|13 9 | 97|29 10 | 53|29 11 | 61|53 12 | 97|53 13 | 61|29 14 | 47|13 15 | 75|47 16 | 97|75 17 | 47|61 18 | 75|61 19 | 47|29 20 | 75|13 21 | 53|13 22 | 23 | 75,47,61,53,29 24 | 97,61,53,29,13 25 | 75,29,13 26 | 75,97,47,61,53 27 | 61,13,29 28 | 97,13,75,29,47 29 | -------------------------------------------------------------------------------- /solutions/2024/day_05/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/5 2 | 3 | from graphlib import TopologicalSorter 4 | 5 | from ...base import TextSolution, answer 6 | from ...utils.transformations import parse_ints 7 | 8 | 9 | def adheres_to_rule(rule: list[int], update: list[int]) -> bool: 10 | assert len(rule) == 2 11 | l, r = rule 12 | if l in update and r in update: 13 | return update.index(l) < update.index(r) 14 | 15 | # if we don't have both rule items, we ignore it 16 | return True 17 | 18 | 19 | def middle_element(l: list[int]) -> int: 20 | return l[len(l) // 2] 21 | 22 | 23 | class Solution(TextSolution): 24 | _year = 2024 25 | _day = 5 26 | 27 | @answer((4996, 6311)) 28 | def solve(self) -> tuple[int, int]: 29 | raw_rules, raw_updates = self.input.split("\n\n") 30 | 31 | rules: list[list[int]] = [ 32 | parse_ints(raw_rule.split("|")) for raw_rule in raw_rules.splitlines() 33 | ] 34 | 35 | updates: list[list[int]] = [ 36 | parse_ints(raw_update.split(",")) for raw_update in raw_updates.splitlines() 37 | ] 38 | 39 | part_1, part_2 = 0, 0 40 | 41 | for update in updates: 42 | if all(adheres_to_rule(r, update) for r in rules): 43 | part_1 += middle_element(update) 44 | else: 45 | sorter = TopologicalSorter() 46 | for l, r in rules: 47 | if l in update and r in update: 48 | sorter.add(l, r) 49 | 50 | fixed_update = list(sorter.static_order()) 51 | 52 | part_2 += middle_element(fixed_update) 53 | 54 | return part_1, part_2 55 | -------------------------------------------------------------------------------- /solutions/2024/day_06/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 6 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/6 -------------------------------------------------------------------------------- /solutions/2024/day_06/input.test.txt: -------------------------------------------------------------------------------- 1 | ....#..... 2 | .........# 3 | .......... 4 | ..#....... 5 | .......#.. 6 | .......... 7 | .#..^..... 8 | ........#. 9 | #......... 10 | ......#... 11 | -------------------------------------------------------------------------------- /solutions/2024/day_06/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/6 2 | 3 | 4 | from itertools import cycle 5 | 6 | from ...base import StrSplitSolution, answer 7 | from ...utils.graphs import Grid, GridPoint, add_points, parse_grid 8 | 9 | 10 | def track_guard(grid: Grid) -> tuple[bool, set[GridPoint]]: 11 | OFFSETS = cycle([(-1, 0), (0, 1), (1, 0), (0, -1)]) 12 | offset = next(OFFSETS) 13 | 14 | loc = next(k for k, v in grid.items() if v == "^") 15 | visited: set[tuple[GridPoint, GridPoint]] = {(loc, offset)} 16 | 17 | while True: 18 | next_loc = add_points(loc, offset) 19 | if next_loc not in grid: 20 | break 21 | 22 | if grid[next_loc] == "#": 23 | offset = next(OFFSETS) 24 | visited.add((loc, offset)) 25 | else: 26 | to_add = next_loc, offset 27 | if to_add in visited: 28 | # loop! 29 | return False, set() 30 | 31 | visited.add(to_add) 32 | loc = next_loc 33 | 34 | return True, {l for l, _ in visited} 35 | 36 | 37 | class Solution(StrSplitSolution): 38 | _year = 2024 39 | _day = 6 40 | 41 | @answer((5239, 1753)) 42 | def solve(self) -> tuple[int, int]: 43 | grid = parse_grid(self.input) 44 | 45 | exited, path = track_guard(grid) 46 | assert exited 47 | initial_path_size = len(path) 48 | 49 | possible_obstacle_locations = 0 50 | for loc in path: 51 | # can't drop a barrel on his head 52 | if grid[loc] != ".": 53 | continue 54 | 55 | grid[loc] = "#" 56 | exited, _ = track_guard(grid) 57 | if not exited: 58 | possible_obstacle_locations += 1 59 | grid[loc] = "." 60 | 61 | return initial_path_size, possible_obstacle_locations 62 | -------------------------------------------------------------------------------- /solutions/2024/day_07/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 7 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/7 -------------------------------------------------------------------------------- /solutions/2024/day_07/input.test.txt: -------------------------------------------------------------------------------- 1 | 190: 10 19 2 | 3267: 81 40 27 3 | 83: 17 5 4 | 156: 15 6 5 | 7290: 6 8 6 15 6 | 161011: 16 10 13 7 | 192: 17 8 14 8 | 21037: 9 7 18 13 9 | 292: 11 6 16 20 10 | -------------------------------------------------------------------------------- /solutions/2024/day_07/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/7 2 | 3 | from functools import partial 4 | from itertools import product 5 | from multiprocessing import Pool 6 | from operator import add, mul 7 | from typing import Callable, Sequence 8 | 9 | from ...base import StrSplitSolution, answer 10 | from ...utils.transformations import parse_ints 11 | 12 | type Operation = Callable[[int, int], int] 13 | 14 | 15 | def process_ops(nums: list[int], ops: Sequence[Operation]) -> int: 16 | if len(nums) == 1: 17 | return nums[0] 18 | 19 | l, r, *rest = nums 20 | cur_op, *remaining_ops = ops 21 | 22 | return process_ops([cur_op(l, r), *rest], remaining_ops) 23 | 24 | 25 | def concat(a: int, b: int) -> int: 26 | return int(str(a) + str(b)) 27 | 28 | 29 | def process_line(line: str, include_concat=False) -> int: 30 | target, *inputs = parse_ints(line.replace(":", "").split()) 31 | 32 | ops = [add, mul] 33 | if include_concat: 34 | ops.append(concat) 35 | 36 | if any( 37 | process_ops(inputs, op_combo) == target 38 | for op_combo in product(ops, repeat=len(inputs) - 1) 39 | ): 40 | return target 41 | 42 | return 0 43 | 44 | 45 | class Solution(StrSplitSolution): 46 | _year = 2024 47 | _day = 7 48 | 49 | @answer(12553187650171) 50 | def part_1(self) -> int: 51 | with Pool() as pool: 52 | return sum(pool.map(process_line, self.input)) 53 | 54 | @answer(96779702119491) 55 | def part_2(self) -> int: 56 | # I paid for a whole CPU amd I'm gonna use all of it! 57 | with Pool() as pool: 58 | return sum( 59 | pool.map( 60 | partial(process_line, include_concat=True), 61 | self.input, 62 | ) 63 | ) 64 | -------------------------------------------------------------------------------- /solutions/2024/day_08/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 8 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/8 -------------------------------------------------------------------------------- /solutions/2024/day_08/input.test.txt: -------------------------------------------------------------------------------- 1 | ............ 2 | ........0... 3 | .....0...... 4 | .......0.... 5 | ....0....... 6 | ......A..... 7 | ............ 8 | ............ 9 | ........A... 10 | .........A.. 11 | ............ 12 | ............ 13 | -------------------------------------------------------------------------------- /solutions/2024/day_08/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/8 2 | 3 | from itertools import combinations 4 | 5 | from ...base import StrSplitSolution, answer 6 | from ...utils.graphs import add_points, parse_grid, subtract_points 7 | 8 | 9 | class Solution(StrSplitSolution): 10 | _year = 2024 11 | _day = 8 12 | 13 | @answer(371) 14 | def part_1(self) -> int: 15 | grid = parse_grid(self.input) 16 | # ignore the space, but keep the grid dimensions 17 | frequencies = set(grid.values()) - {"."} 18 | 19 | antinode_locations = set() 20 | 21 | for frequency in frequencies: 22 | locations = (k for k, v in grid.items() if v == frequency) 23 | 24 | for l, r in combinations(locations, 2): 25 | slope = subtract_points(l, r) 26 | 27 | for p in add_points(l, slope), subtract_points(r, slope): 28 | if p in grid: 29 | antinode_locations.add(p) 30 | 31 | return len(antinode_locations) 32 | 33 | @answer(1229) 34 | def part_2(self) -> int: 35 | grid = parse_grid(self.input) 36 | # ignore the space, but keep the grid dimensions 37 | frequencies = set(grid.values()) - {"."} 38 | 39 | antinode_locations = set() 40 | 41 | for frequency in frequencies: 42 | locations = (k for k, v in grid.items() if v == frequency) 43 | 44 | for l, r in combinations(locations, 2): 45 | slope = subtract_points(l, r) 46 | 47 | for p, fn in (l, add_points), (r, subtract_points): 48 | antinode_locations.add(p) 49 | 50 | while (next_p := fn(p, slope)) in grid: 51 | antinode_locations.add(next_p) 52 | p = next_p # noqa: PLW2901 53 | 54 | return len(antinode_locations) 55 | -------------------------------------------------------------------------------- /solutions/2024/day_09/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 9 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/9 -------------------------------------------------------------------------------- /solutions/2024/day_09/input.test.txt: -------------------------------------------------------------------------------- 1 | 2333133121414131402 2 | -------------------------------------------------------------------------------- /solutions/2024/day_10/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 10 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/10 -------------------------------------------------------------------------------- /solutions/2024/day_10/input.test.txt: -------------------------------------------------------------------------------- 1 | 89010123 2 | 78121874 3 | 87430965 4 | 96549874 5 | 45678903 6 | 32019012 7 | 01329801 8 | 10456732 9 | -------------------------------------------------------------------------------- /solutions/2024/day_10/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/10 2 | 3 | 4 | from ...base import StrSplitSolution, answer 5 | from ...utils.graphs import GridPoint, IntGrid, neighbors, parse_grid 6 | 7 | 8 | def score_trailhead(grid: IntGrid, trailhead: GridPoint, *, skip_visited: bool) -> int: 9 | score = 0 10 | visited: set[GridPoint] = set() 11 | 12 | queue: list[GridPoint] = [trailhead] 13 | 14 | while queue: 15 | cur = queue.pop() 16 | 17 | if skip_visited: 18 | if cur in visited: 19 | continue 20 | 21 | visited.add(cur) 22 | 23 | if (val := grid[cur]) == 9: 24 | score += 1 25 | continue 26 | 27 | queue.extend( 28 | n for n in neighbors(cur, num_directions=4) if grid.get(n) == val + 1 29 | ) 30 | 31 | return score 32 | 33 | 34 | class Solution(StrSplitSolution): 35 | _year = 2024 36 | _day = 10 37 | 38 | @answer((737, 1619)) 39 | def solve(self) -> tuple[int, int]: 40 | grid = parse_grid(self.input, int_vals=True) 41 | 42 | return tuple( # type: ignore - I promise this is a 2-tuple 43 | sum( 44 | score_trailhead(grid, trailhead, skip_visited=skip_visited) 45 | for trailhead, v in grid.items() 46 | if v == 0 47 | ) 48 | for skip_visited in (True, False) 49 | ) 50 | -------------------------------------------------------------------------------- /solutions/2024/day_11/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 11 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/11 -------------------------------------------------------------------------------- /solutions/2024/day_11/input.test.txt: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /solutions/2024/day_11/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/11 2 | 3 | from collections import defaultdict 4 | from itertools import chain 5 | 6 | from ...base import StrSplitSolution, answer 7 | 8 | 9 | def step_stone(s: str) -> list[str]: 10 | if s == "0": 11 | return ["1"] 12 | 13 | if (l := len(s)) % 2 == 0: 14 | cut_line = l // 2 15 | return [str(int(new_stone)) for new_stone in (s[:cut_line], s[cut_line:])] 16 | 17 | return [str(int(s) * 2024)] 18 | 19 | 20 | class Solution(StrSplitSolution): 21 | _year = 2024 22 | _day = 11 23 | separator = " " 24 | 25 | @answer(239714) 26 | def part_1(self) -> int: 27 | stones = self.input 28 | 29 | for _ in range(25): 30 | stones = chain.from_iterable(map(step_stone, stones)) 31 | 32 | return len(list(stones)) 33 | 34 | @answer(284973560658514) 35 | def part_2(self) -> int: 36 | stones: dict[str, int] = {k: 1 for k in self.input} 37 | assert len(stones) == len(self.input) 38 | 39 | for _ in range(75): 40 | new_stones = defaultdict(int) 41 | for stone, num in stones.items(): 42 | for new_stone in step_stone(stone): 43 | new_stones[new_stone] += num 44 | 45 | stones = new_stones 46 | 47 | return sum(stones.values()) 48 | -------------------------------------------------------------------------------- /solutions/2024/day_12/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 12 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/12 -------------------------------------------------------------------------------- /solutions/2024/day_12/input.test.txt: -------------------------------------------------------------------------------- 1 | RRRRIICCFF 2 | RRRRIICCCF 3 | VVRRRCCFFF 4 | VVRCCCJFFF 5 | VVVVCJJCFE 6 | VVIVCCJJEE 7 | VVIIICJJEE 8 | MIIIIIJJEE 9 | MIIISIJEEE 10 | MMMISSJEEE 11 | -------------------------------------------------------------------------------- /solutions/2024/day_13/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 13 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/13 -------------------------------------------------------------------------------- /solutions/2024/day_13/input.test.txt: -------------------------------------------------------------------------------- 1 | Button A: X+94, Y+34 2 | Button B: X+22, Y+67 3 | Prize: X=8400, Y=5400 4 | 5 | Button A: X+26, Y+66 6 | Button B: X+67, Y+21 7 | Prize: X=12748, Y=12176 8 | 9 | Button A: X+17, Y+86 10 | Button B: X+84, Y+37 11 | Prize: X=7870, Y=6450 12 | 13 | Button A: X+69, Y+23 14 | Button B: X+27, Y+71 15 | Prize: X=18641, Y=10279 16 | -------------------------------------------------------------------------------- /solutions/2024/day_13/solution.py: -------------------------------------------------------------------------------- 1 | # prompt: https://adventofcode.com/2024/day/13 2 | 3 | from re import findall 4 | 5 | from ...base import StrSplitSolution, answer 6 | from ...utils.transformations import parse_ints 7 | 8 | PART_2_INCREASE = 10_000_000_000_000 9 | 10 | 11 | def find_intersection( 12 | a_x: int, a_y: int, b_x: int, b_y: int, x_prize: int, y_prize: int 13 | ) -> tuple[float, float]: 14 | """ 15 | this solves: 16 | - a_x * a + b_x * b = x_prize 17 | - a_y * a + b_y * b = y_prize 18 | """ 19 | 20 | # multiply out b_y and b_x (the coefficients of B) 21 | a_x_with_b_y = a_x * b_y 22 | x_prize_with_b_y = x_prize * b_y 23 | 24 | a_y_with_b_x = a_y * b_x 25 | y_prize_with_b_x = y_prize * b_x 26 | 27 | a = (x_prize_with_b_y - y_prize_with_b_x) / (a_x_with_b_y - a_y_with_b_x) 28 | 29 | b = (y_prize - a_y * a) / b_y 30 | 31 | return a, b 32 | 33 | 34 | class Solution(StrSplitSolution): 35 | _year = 2024 36 | _day = 13 37 | separator = "\n\n" 38 | 39 | def _solve(self, increase_distance=False) -> int: 40 | total = 0 41 | for block in self.input: 42 | vals = parse_ints(findall(r"\d+", block)) 43 | if increase_distance: 44 | vals[4] += PART_2_INCREASE 45 | vals[5] += PART_2_INCREASE 46 | 47 | a, b = find_intersection(*vals) 48 | if a.is_integer() and b.is_integer(): 49 | total += a * 3 + b 50 | 51 | return int(total) 52 | 53 | @answer(37680) 54 | def part_1(self) -> int: 55 | return self._solve() 56 | 57 | @answer(87550094242995) 58 | def part_2(self) -> int: 59 | return self._solve(increase_distance=True) 60 | -------------------------------------------------------------------------------- /solutions/2024/day_14/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 14 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/14 -------------------------------------------------------------------------------- /solutions/2024/day_14/input.test.txt: -------------------------------------------------------------------------------- 1 | p=2,4 v=2,-3 2 | p=0,4 v=3,-3 3 | p=6,3 v=-1,-3 4 | p=10,3 v=-1,2 5 | p=2,0 v=2,-1 6 | p=0,0 v=1,3 7 | p=3,0 v=-2,-2 8 | p=7,6 v=-1,-3 9 | p=3,0 v=-1,-2 10 | p=9,3 v=2,3 11 | p=7,3 v=-1,2 12 | p=9,5 v=-3,-3 13 | -------------------------------------------------------------------------------- /solutions/2024/day_15/README.md: -------------------------------------------------------------------------------- 1 | # 2024 Day 15 2 | 3 | For a full explanation of this code, check out https://advent-of-code.xavd.id/writeups/2024/day/15 -------------------------------------------------------------------------------- /solutions/2024/day_15/input.test.txt: -------------------------------------------------------------------------------- 1 | ########## 2 | #..O..O.O# 3 | #......O.# 4 | #.OO..O.O# 5 | #..O@..O.# 6 | #O#..O...# 7 | #O..O..O.# 8 | #.OO.O.OO# 9 | #....O...# 10 | ########## 11 | 12 | ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ 13 | vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< 15 | <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ 16 | ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< 17 | ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ 19 | <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> 20 | ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< 21 | v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ 22 | -------------------------------------------------------------------------------- /solutions/utils/transformations.py: -------------------------------------------------------------------------------- 1 | from itertools import compress, repeat 2 | from typing import Iterable 3 | 4 | 5 | def parse_ints(l: Iterable[str]) -> list[int]: 6 | return [int(i) for i in l] 7 | 8 | 9 | def ilen(i: Iterable) -> int: 10 | """ 11 | Return the number of items in *i*. 12 | 13 | >>> ilen(x for x in range(1000000) if x % 3 == 0) 14 | 333334 15 | 16 | This consumes the i, so handle with care. 17 | 18 | Pulled directly from more_itertools: https://more-itertools.readthedocs.io/en/latest/api.html#more_itertools.ilen 19 | 20 | > note to self: uses zip so that every item in `i` is truthy. otherwise, elements that happen to be falsy (but should otherwise be included in the result) are skipped). For instance, `sum(compress(repeat(1), range(3)))` returns `2`, because the first `0` is excluded. However, the 1-tuple `(0,)` is correctly included. 21 | """ 22 | return sum(compress(repeat(1), zip(i))) 23 | --------------------------------------------------------------------------------