├── .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 |
11 |
~
13 | {
14 | // path && is extraneous here, but TS likes it
15 | path?.map((segment, index) => {
16 | return (
17 |
18 | / {segment}
19 |
20 | );
21 | })
22 | } / {page}
23 |
24 |
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 |
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 |
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 |
--------------------------------------------------------------------------------