Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/xavdid/advent-of-code-python-template
This is my tried-and-true Python helper for solving Advent of Code puzzles
https://github.com/xavdid/advent-of-code-python-template
advent-of-code advent-of-code-starter adventofcode python python3 template
Last synced: 16 days ago
JSON representation
This is my tried-and-true Python helper for solving Advent of Code puzzles
- Host: GitHub
- URL: https://github.com/xavdid/advent-of-code-python-template
- Owner: xavdid
- Created: 2023-09-30T21:06:06.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-08-17T19:50:01.000Z (3 months ago)
- Last Synced: 2024-10-03T12:22:49.511Z (about 1 month ago)
- Topics: advent-of-code, advent-of-code-starter, adventofcode, python, python3, template
- Language: Python
- Homepage: https://advent-of-code.xavd.id
- Size: 27.3 KB
- Stars: 8
- Watchers: 2
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
- awesome-advent-of-code - xavdid/advent-of-code-python-template
README
# @xavdid's Python Advent of Code Project Template
This is my tried-and-true Python utility package for the phenomenal [Advent of Code](https://adventofcode.com/) puzzles. It handles creating stub solutions, input parsing, and printing your answer, letting you focus on the actual solve. I've been [using it since 2017](https://github.com/xavdid/advent-of-code).
Additionally, Over in the main repo, I include a step-by-step [explanation of each puzzle](https://advent-of-code.xavd.id/) if you're in the learning mood!
## Quickstart
To use this base class for your own solutions:
1. Ensure you have Python `3.12` or higher. You can use [pyenv](https://github.com/pyenv/pyenv) or [asdf](https://asdf-vm.com/) to manage your Python versions. It may work on older versions, but `3.12`-specific features will be added without further breaking changes
2. Create a new repo using this template ([docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template#creating-a-repository-from-a-template)) and clone it locally
3. Start a new solution using `./start`
4. Edit the newly created file at `solutions/YEAR/day_01/solution.py`
5. Run your code answers using `./advent`
6. Repeat and enjoy!## Commands
This repo has two main commands: `start` and `advent`.
### `./start`
#### Usage
> `./start [-h] [--year YEAR] [day]`
Scaffold files to start a new Advent of Code solution
**positional arguments**:
- `day` (optional): Which puzzle day to start, between `[1,25]`. Defaults to the next day _without_ a folder (matching `day_NN`) in the specified year.
**optional arguments**:
- `-h, --help` (optional): show this help message and exit
- `--year YEAR` (optional): Puzzle year. Defaults to current year if December has begun, otherwise previous year.#### Examples
- `./start`
- `./start 2`
- `./start 3 --year 2019`### `./advent`
#### Usage
> `./advent [--year year] [--test-data] [--debug] [--profile] [--slow] [--time] [day]`
Run a specific day of Advent of Code
**informational flags**
- `-h, --help`: Show this help message and exit
- `--version`: Print version info and exit**positional arguments**:
- `day` (optional): Which puzzle day to start, between `[1,25]`. Defaults to the latest day _with_ a folder (matching `day_NN`) in the specified year.
**optional flags**:
- `--year YEAR`: puzzle year. Defaults to current year if December has begun, otherwise previous year
- `-t, --test-data`: read puzzle input from `input.test.txt` instead of `input.txt`. Ignores `@answer` decorators (see [saving answers](#saving-answers))
- `--debug`: prints normally-hidden debugging statements (written with `self.debug(...)`). See [debugging](#debugging)
- `--profile`: run solution through a performance profiler
- `--slow`: specify that long-running solutions (or those requiring manual input) should be run. They're skipped otherwise
- `--time`: print information about how long solutions took to run. More useful than timing at a shell level, since this only starts the timer once all imports have happened and any `advent`-related code is done.#### Examples
- `./advent`
- `./advent 2`
- `./advent 5 --year 2019`
- `./advent 7 --test-data`
- `./advent 9 -t --debug`## File Structure
```
solutions/
├── ...
└── 2020/
├── day_01/
│ ├── solution.py
│ ├── input.txt
│ ├── input.test.txt
│ └── README.md
├── day_02/
│ ├── solution.py
│ ├── ...
└── ...
```- each year has a folder (`YYYY`)
- each day in that year (will eventually) have a folder (`day_NN`)Each `day_NN` folder has the following files:
- `solution.py`, which has a `class Solution`. `./advent` expects both that filename and that class name exactly, so you shouldn't change them. See [Writing Solutions](#writing-solutions) for how the file is structured
- `input.txt` holds your individualized input from the AoC site. Make sure [not to share it publicly](https://old.reddit.com/r/adventofcode/wiki/troubleshooting/no_asking_for_inputs)!
- `input.test.txt` holds the example input from the prompt. It's read when the `--test-input` flag is used (see below). It also won't throw errors if the result doesn't match the [answer](#saving-answers). You can also do all your work in `input.txt`, but it's marginally less convenient
- `README.md` is a convenient place to take notes or explain your solution## Writing Solutions
### The `Solution` Class
A helpful base class on which to build your AoC solutions. It's got 2 required properties (which should be pre-filled if you use `./start`): `_year` and `_day`, corresponding to the puzzle you're solving.
Your puzzle input, the parsed contents of the day's `input.txt`, will be available at `self.input`. Learn more in [Reading Input](#reading-input).
There's also a convenience method for print-based debugging: `self.debug()`. You can pass it any number of items and they'll get pretty-printed. It only prints if you use the `--debug` flag with `./advent`.
### Reading Input
AoC input takes a number of forms, so there are a number of simple modes for input parsing. Your generated `Solution` class should inherit from one of the following classes, which will parse `self.input` for you:
| Inherited Class | description | sample for this mode |
| ------------------ | --------------------------------------------------------- | --------------------- |
| `TextSolution` | one solid block of text; the default | `abcde` |
| `IntSolution` | one number | `12345` |
| `StrSplitSolution` | `str[]`, split by a specified separator (default newline) | a
b
c
d
e |
| `IntSplitSolution` | `int[]`, split by a specified separator (default newline) | 1
2
3
4
5 |```py
# input file is "12345"from ...base import (
IntSolution,
IntSplitSolution,
StrSplitSolution,
TextSolution,
)for BaseClass in [TextSolution, IntSolution, StrSplitSolution, IntSplitSolution]:
class Solution(BaseClass):
def show_input(self):
print(f"\n{self.input} (type: {type(self.input)})\n")Solution().show_input()
# 12345 (type: )
# 12345 (type: )
# ['12345'] (type: )
# [12345] (type: )
```You can also change the separator to change how the `SplitSolution`s work:
```py
# input file is "1,2,3,4,5"from ...base import IntSplitSolution, StrSplitSolution
for BaseClass in [StrSplitSolution, IntSplitSolution]:
class Solution(BaseClass):
separator = ","def show_input(self):
print(f"\n{self.input} (type: {type(self.input)})\n")Solution().show_input()
# ['1', '2', '3', '4', '5'] (type: )
# [1, 2, 3, 4, 5] (type: )
```### Solution Functions
Each AoC puzzle has two parts, so there are two functions you need to write: `part_1` and `part_2`. Each should return an `int`, since that's typically the answer that AoC expects.
Sometimes, it's easier to calculate both parts in a single function (such as if the answer is asking about two parts of a single computation). In that case, there's also a `solve()` method, which should return a 2-tuple with your answers (like `(5, 7)`). `solve` **takes precedence if present**. Feel free to delete any unused functions when you're done.
```py
class Solution(TextSolution):
def part_1(self) -> int:
return some_computation()def part_2(self) -> int:
return some_other_computation()# or:
def solve(self) -> tuple[int, int]:
part_1 = 0
total = 0for i in range(10):
result = some_computation()
if i == 0:
part_1 = resulttotal += result
return result
```### Saving Answers
Once you've solved the puzzle, you can decorate your answer function (`solve` or `part_N`) with the `@answer` decorator. It asserts that the value returned from the function is whatever you pass to the decorator:
```py
class Solution(TextSolution):
_year = 2022
_day = 5@answer(123)
def part_1(self) -> int:
return 123@answer(234)
def part_2(self) -> int:
return 123 # err!
```This is helpful for ensuring your answer doesn't change when editing your code after you've solved the puzzle. It's included as a comment in the template. It's ignored when running against test input, so it's easy to verify as you go.
### Debugging
The base class includes a `self.debug` method which will pretty-print all manner of inputs. These only show up when the `--debug` flag is used, making it a convenient way to show debugging info selectively.
### Linting & Type Checking
I recommend the following tools:
- [Ruff](https://astral.sh/ruff), a lightning-fast linter (to help you catch bugs)
- [Pyright](https://github.com/microsoft/pyright), a type checker to identify logical errors (also available in VSCode using their Python plugin)If you have both available, then `just lint` will run them both. I've included a simple `ruff` configuration file to help get you started.
### Marking Slow Solutions
If you're running many solutions at once and want to exclude individual parts of solutions (or entire days), you can mark individual functions with the `@slow` decorator. They'll print a warning, but won't actually run the solution.