chore: create new project structure and aoc.py runner script

This commit is contained in:
2025-08-04 16:23:06 +02:00
parent f76375d835
commit e2964c6c36
91 changed files with 177 additions and 113 deletions

View File

@@ -1,17 +0,0 @@
ran day1/day1.py in 0.016s
ran day2/day2.py in 0.021s
ran day3/day3.py in 0.017s
ran day4/day4.py in 0.039s
ran day5/day5.py in 0.191s
ran day6/day6.py in 9.655s
ran day7/day7.py in 2.227s
ran day8/day8.py in 0.017s
ran day9/day9.py in 11.159s
ran day10/day10.py in 0.052s
ran day11/day11.py in 0.116s
ran day12/day12.py in 0.170s
ran day13/day13.py in 0.014s
ran day14/day14.py in 3.729s
ran day16/day16.py in 10.082s
ran day18/day18.py in 0.872s

26
Makefile Normal file
View File

@@ -0,0 +1,26 @@
.PHONY: all venv install clean
# Define the virtual environment directory
VENV_DIR = ./venv
# Define the Python interpreter to use
PYTHON = $(VENV_DIR)/bin/python
# Default target
all: venv install
# Create virtual environment if it doesn't exist
venv:
@echo "Creating virtual environment..."
@if [ ! -d $(VENV_DIR) ]; then \
python3 -m venv $(VENV_DIR); \
fi
install: venv
@$(PYTHON) -m pip install --upgrade pip
@$(PYTHON) -m pip install -e .
clean:
@echo "Removing virtual environment..."
@rm -rf $(VENV_DIR)

View File

@@ -4,15 +4,25 @@ My solutions to the [advent of code](https://adventofcode.com/) challenges, writ
## How to run ## How to run
### Install project
Run `make install` or
Run from root directory (inside a virtualenv):
```shell
$ pip install -e .
```
### Run a single day ### Run a single day
```shell ```shell
$ python3 run.py <year> <day> $ aoc <year> <day>
``` ```
### Run a whole year ### Run a whole year
```shell ```shell
$ python3 run.py <year> $ aoc <year>
``` ```

View File

View File

View File

@@ -1,79 +1,79 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
def manhattan_distance(p): def manhattan_distance(p):
return abs(p[0]) + abs(p[1]) return abs(p[0]) + abs(p[1])
def points_for_wire(wire): def points_for_wire(wire):
x, y, count = 0, 0, 0 x, y, count = 0, 0, 0
points = {} points = {}
# (x, y) # (x, y)
directions = {"R": (1, 0), "L": (-1, 0), "U": (0, 1), "D": (0, -1)} directions = {"R": (1, 0), "L": (-1, 0), "U": (0, 1), "D": (0, -1)}
for p in wire: for p in wire:
# D42 -> for _ in range(42) # D42 -> for _ in range(42)
for _ in range(int(p[1:])): for _ in range(int(p[1:])):
offset = directions[p[0]] offset = directions[p[0]]
x += offset[0] x += offset[0]
y += offset[1] y += offset[1]
count += 1 count += 1
points[(x ,y)] = count points[(x ,y)] = count
return points return points
def find_min_distance(wire1, wire2): def find_min_distance(wire1, wire2):
points1 = points_for_wire(wire1) points1 = points_for_wire(wire1)
points2 = points_for_wire(wire2) points2 = points_for_wire(wire2)
intersections = points1.keys() & points2.keys() intersections = points1.keys() & points2.keys()
closest = min((intersection for intersection in intersections), key=manhattan_distance) closest = min((intersection for intersection in intersections), key=manhattan_distance)
return manhattan_distance(closest) return manhattan_distance(closest)
def find_least_steps(wire1, wire2): def find_least_steps(wire1, wire2):
points1 = points_for_wire(wire1) points1 = points_for_wire(wire1)
points2 = points_for_wire(wire2) points2 = points_for_wire(wire2)
intersections = points1.keys() & points2.keys() intersections = points1.keys() & points2.keys()
# Intersection with the least steps # Intersection with the least steps
least_steps = min(intersections, key=lambda x: points1[x] + points2[x]) least_steps = min(intersections, key=lambda x: points1[x] + points2[x])
return points1[least_steps] + points2[least_steps] return points1[least_steps] + points2[least_steps]
def tests(): def tests():
inputs = ( inputs = (
(("R8", "U5", "L5", "D3"), ("U7", "R6", "D4", "L4")), (("R8", "U5", "L5", "D3"), ("U7", "R6", "D4", "L4")),
(("R75","D30","R83", "U83", "L12", "D49", "R71", "U7", "L72"), ("U62", "R66", "U55", "R34", "D71", "R55", "D58", "R83")), (("R75","D30","R83", "U83", "L12", "D49", "R71", "U7", "L72"), ("U62", "R66", "U55", "R34", "D71", "R55", "D58", "R83")),
(("R98", "U47", "R26", "D63", "R33", "U87", "L62", "D20", "R33", "U53", "R51"), ("U98", "R91", "D20", "R16", "D67", "R40", "U7", "R15", "U6", "R7")) (("R98", "U47", "R26", "D63", "R33", "U87", "L62", "D20", "R33", "U53", "R51"), ("U98", "R91", "D20", "R16", "D67", "R40", "U7", "R15", "U6", "R7"))
) )
# Part 1 # Part 1
expected = (6, 159, 135) expected = (6, 159, 135)
for i, inp in enumerate(inputs): for i, inp in enumerate(inputs):
result = find_min_distance(inp[0], inp[1]) result = find_min_distance(inp[0], inp[1])
assert result == expected[i], "Result for {} should be {}, was {}".format( assert result == expected[i], "Result for {} should be {}, was {}".format(
inp, expected[i], result inp, expected[i], result
) )
# Part 2 # Part 2
# expected number of steps # expected number of steps
expected_part2 = (30, 610, 410) expected_part2 = (30, 610, 410)
print("All tests passed.") print("All tests passed.")
for i, inp in enumerate(inputs): for i, inp in enumerate(inputs):
result = find_least_steps(inp[0], inp[1]) result = find_least_steps(inp[0], inp[1])
assert result == expected_part2[i], "Result for {} should be {}, was {}".format( assert result == expected_part2[i], "Result for {} should be {}, was {}".format(
inp, expected_part2[i], result inp, expected_part2[i], result
) )
if __name__ == "__main__": if __name__ == "__main__":
tests() tests()
import sys import sys
infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt" infile = sys.argv[1] if len(sys.argv) > 1 else "example.txt"
with open(infile) as raw_input: with open(infile) as raw_input:
lines = raw_input.readlines() lines = raw_input.readlines()
wire1, wire2 = [line.strip("\n").split(",") for line in lines] wire1, wire2 = [line.strip("\n").split(",") for line in lines]
print("Part 1 -- distance = ", find_min_distance(wire1, wire2)) print("Part 1 -- distance = ", find_min_distance(wire1, wire2))
print("Part 2 -- steps = ", find_least_steps(wire1, wire2)) print("Part 2 -- steps = ", find_least_steps(wire1, wire2))

View File

@@ -1,14 +1,11 @@
import sys import sys
from pathlib import Path from pathlib import Path
# TODO replace PYTHONPATH hack with a proper solution, like making intcode an
# installed module https://stackoverflow.com/a/50194143
sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode")) sys.path.append(str(Path(__file__).absolute().parent.parent / "intcode"))
from itertools import cycle, permutations from itertools import cycle, permutations
from intcode import interpret_intcode, Interpreter from intcode import interpret_intcode, Interpreter
def main(inp): def main(inp):
mem = list(map(int, inp.readline().rstrip().split(","))) mem = list(map(int, inp.readline().rstrip().split(",")))
max_ret = 0 max_ret = 0

41
adventofcode/aoc.py Normal file
View File

@@ -0,0 +1,41 @@
import argparse
from adventofcode.helper import run, get_input_file
def main():
parser = argparse.ArgumentParser(description="Advent of Code CLI")
subparsers = parser.add_subparsers(dest='command')
# Sous-commande init
init_parser = subparsers.add_parser('init', help='Init an aoc day')
init_parser.add_argument('year', type=int)
init_parser.add_argument('day', type=int)
# Sous-commande run
run_parser = subparsers.add_parser('run', help='Run an aoc day')
run_parser.add_argument('year', type=int)
run_parser.add_argument('day', type=int)
args = parser.parse_args()
if args.command == 'init':
handle_init(args.year, args.day)
elif args.command == 'run':
handle_run(args.year, args.day)
else:
parser.print_help()
def handle_init(year, day):
# TODO initialize directory if needed, download input file and create
# dayX.py from a template
raise NotImplementedError("init")
def handle_run(year, day):
run(year, day)
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import urllib.request import urllib.request
import getpass import getpass
import sys import sys
@@ -6,6 +7,7 @@ import subprocess
import os import os
from pathlib import Path from pathlib import Path
ROOTPATH = Path(os.path.dirname(os.path.realpath(__file__)))
_auth = None _auth = None
@@ -27,9 +29,9 @@ def get_input_file(year, day):
return res return res
def main(year, day): def run(year, day):
if day is not None: if day is not None:
path = Path(f"{year}/day{day}") path = ROOTPATH / Path(f"{year}/day{day}")
script_path = path / Path(f"day{day}.py") script_path = path / Path(f"day{day}.py")
input_path = path / Path("input.txt") input_path = path / Path("input.txt")
if not script_path.exists(): if not script_path.exists():
@@ -45,7 +47,7 @@ def main(year, day):
run_day(script_path, input_path) run_day(script_path, input_path)
else: else:
for day in range(1, 26): for day in range(1, 26):
path = Path(f"{year}/day{day}") path = ROOTPATH / Path(f"{year}/day{day}")
script_path = path / Path(f"day{day}.py") script_path = path / Path(f"day{day}.py")
input_path = path / Path("input.txt") input_path = path / Path("input.txt")
if script_path.exists(): if script_path.exists():
@@ -69,12 +71,3 @@ def run_day(script_path, input_path):
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
print(f"> timeout {script_path} after 30s", file=sys.stderr) print(f"> timeout {script_path} after 30s", file=sys.stderr)
if __name__ == "__main__":
if len(sys.argv) <= 1:
print(f"Usage: {__file__} <year> [<day>]", file=sys.stderr)
exit(1)
year = sys.argv[1]
day = sys.argv[2] if len(sys.argv) > 2 else None
main(year, day)

14
pyproject.toml Normal file
View File

@@ -0,0 +1,14 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "adventofcode"
version = "1.0.0"
[project.scripts]
aoc = "adventofcode.aoc:main"
[tool.setuptools.packages.find]
where = ["."]