Welcome! This repository contains my solutions for Advent of Code puzzles, written in Python.
No installation required!
Simply open this project in a Dev Container (like GitHub Codespaces or VS Code Remote Containers).
You get a 100% safe, isolated environment with all dependencies pre-installed:
- Python 3 (with pip)
- Git (latest)
- All tools on the
PATH
Your local system is never changed. Everything runs inside a disposable, secure container.
advent_of_code_python/
├── src/
│ ├── 2023/
│ │ └── day3/
│ │ ├── index.py
│ │ ├── input.txt
│ │ └── sample.txt
│ └── constants/
│ └── constants.py
├── README.md
└── ...
-
Open this repo in your Dev Container.
-
Open a terminal in VS Code.
-
Run the solution as a module:
Recommended: from the src directory:
cd src python -m 2023.day1.indexOr from the repository root:
python -m src.2023.day1.index
Note: Running as a module ensures all imports work correctly! Use dots (.) to separate path components, not slashes (/), and omit the .py extension. Some modules may require running from the src directory due to import paths.
Run all tests:
pytest -qRun tests for a specific day:
pytest -q test/2023/day1/test_day1.pyRun tests with verbose output:
pytest -vRun tests and show coverage:
pytest --cov=src --cov-report=term-missingNote: Empty
__init__.pyfiles are automatically excluded from coverage reports. Files with actual code (likesrc/global_utils/__init__.py) are included as they contain testable logic.
- Runs in a closed, disposable container
- No changes to your local system
- All tools are pre-installed and ready to use
- Modular, year/day-based structure
- Reusable constants for grid puzzles
- Ready for extension and testing
Happy coding and puzzle solving! 🎅✨
Below are short, practical explanations for the coding practices. Apply these consistently to make solutions easier to read, debug, test and maintain. use type hinting following these practices will be easier for u return in a few months and understand what did u do on ur last journey!.
- Use the
loggerfunction imported from utils instead of nakedprint()calls so you can control verbosity. - Keep functions small and single-purpose — easier to step through and unit test.
- Add meaningful variable names and short docstrings; include examples in docstrings for tricky behaviour.
- Add reproducible examples / sample inputs in the repository so you can run the failing case locally.
Example:
# ...existing code...
from utils.logger import logger
def solve(lines: list[str]) -> int:
"""Solve one puzzle. Returns result as integer."""
logger.debug("starting solve with %d lines", len(lines))
# ...
# ...existing code...- Group domain logic into well-named functions and small classes; separate parsing, business logic and I/O.
- Keep file I/O and parsing in utilities (
utils.read_file) and pure functions for the algorithm itself (take and return plain data structures). - Prefer composable building blocks over very long monolithic functions.
- Encapsulate mutability: return new data instead of mutating global state where possible.
Example structure:
- parse_input(...) -> returns structured data
- compute_part1(data) -> returns result
- compute_part2(data) -> returns result
- main() -> reads files, calls functions, prints results
- Use pytest and keep tests small and deterministic. Put tests under
test/mirroring thesrc/layout. - Test edge cases, typical cases and the provided samples from the puzzle.
- Use
test_utils.load_moduleto dynamically load and test modules fromsrc/. - Run tests automatically in CI (or locally via
pytest).
Test example:
# test/2023/day1/test_day1.py
from test_utils import load_module
from global_utils.utils import read_file
module = load_module("src/2023/day1")
def test_find_number_examples():
assert module.find_number("1abc2") == 12
assert module.find_number("pqr3stu8vwx") == 38
assert module.find_number("a1b2c3d4e5f") == 15
assert module.find_number("treb7uchet") == 77
def test_sum_calibration_examples():
lines = read_file("test/2023/day1/sample1.txt")
assert module.sum_calibration(lines) == 142
def test_part1_input_answer():
lines = read_file("src/2023/day1/input.txt")
assert module.sum_calibration(lines) == 54573- Validate inputs early and raise clear, specific exceptions (custom exceptions for domain errors if helpful).
- Catch only expected exceptions and re-raise with context when needed; avoid broad
except:. - Use context managers for resource handling (
with open(...) as f:). - Provide helpful error messages showing tried paths or inputs (useful in
utils.read_file).
Example:
if not lines:
raise ValueError("input file is empty; expected at least one line")Apply these patterns while solving each puzzle: keep the I/O thin, the algorithm pure and small, add tests for the algorithm, and prefer logged, configurable debug output. This makes it far easier to reproduce, debug and maintain solutions.