advent-of-code-2019/main.py

190 lines
5.5 KiB
Python
Raw Normal View History

2023-03-05 15:23:59 +01:00
#!/usr/bin/env python3
import importlib
import os
import re
import pathlib
import traceback
from argparse import ArgumentParser
PATH = pathlib.Path(__file__).parent
2023-03-05 15:23:59 +01:00
INPUT_PATH = PATH / 'input'
class Tests(list):
2023-03-04 15:20:58 +01:00
"""
Test cases class. A test case is a tuple of the form
(puzzle_input, partI_result, partII_result).
"""
def add(self, test_input, partI=None, partII=None):
2023-03-04 15:20:58 +01:00
"""Add a test case"""
self.append((str(test_input), partI, partII))
def testf(tests, f, part):
2023-03-04 15:20:58 +01:00
"""
Tests a function f on test cases tests and compares the result
to test_case[part]. The test is not run if test_case[part] == None.
"""
tests = [(t[0], t[part]) for t in tests if t[part] != None]
if tests == []:
return "No tests"
for i, (case, sol) in enumerate(tests):
if sol == None:
continue
r = f(case)
if r == sol:
continue
return "Failed on test {}. Expected {}. Got {}".format(i+1, sol, r)
return "OK"
class Solver:
2023-03-04 15:20:58 +01:00
"""
Solver class. A solver is a module with functions partI and partII,
an optional preprocessing function preproc, and an optional variable
tests of type Tests.
"""
def __init__(self, mod):
2023-03-04 15:20:58 +01:00
"""Initilizes the solver instance of module mod."""
def unpack(name, default):
2023-03-04 15:20:58 +01:00
"""Returns mod.name if it exists and default otherwise."""
return getattr(mod, name) if name in dir(mod) else default
dn_pattern = re.compile("\d\d?")
self.preproc = unpack('preproc', NotImplemented)
self.tests = unpack('tests', [])
self.partI = getattr(mod, 'partI')
self.partII = getattr(mod, 'partII')
2023-03-04 15:20:58 +01:00
def solve(self, puzzle_input):
2023-03-05 15:23:59 +01:00
"""Solves puzzle input and returns pair of solutions"""
puzzle_input = puzzle_input.rstrip()
if self.preproc != NotImplemented:
puzzle_input = self.preproc(puzzle_input)
ptI = self.partI(puzzle_input)
ptII = self.partII(puzzle_input)
return ptI, ptII
def test(self):
2023-03-05 15:23:59 +01:00
"""Tests the solver and returns pair of test results."""
tests = self.tests
if self.preproc != NotImplemented:
tests = [(self.preproc(str(t)), s1, s2)
for (t, s1, s2) in tests]
ptI = testf(tests, self.partI, 1)
ptII = testf(tests, self.partII, 2)
return ptI, ptII
2023-03-05 15:23:59 +01:00
return r
parser = ArgumentParser()
parser.description = \
2023-03-13 23:55:12 +01:00
"""Solves Advent of Code 2019 puzzles.
The puzzles are available at https://adventofcode.com/2019/"""
parser.add_argument('-d', '--day',
action='store',
type=str,
2023-03-05 15:23:59 +01:00
metavar='DAY[-DAY]',
help='solve puzzle of day DAY or of range DAY-DAY')
parser.add_argument('-i', '--input',
action='store',
type=str,
metavar='INPUT',
default=INPUT_PATH,
help='use INPUT as puzzle input')
parser.add_argument('-t', '--test',
action='store_true',
help="run tests")
2023-03-05 15:23:59 +01:00
def parse_day_arg(s, mind, maxd):
2023-03-04 15:20:58 +01:00
"""Parses day argument. Returns a list of days."""
split = s.split('-')
if len(split) == 1:
return [int(split[0])]
elif len(split) == 2:
d, D = map(int, split)
2023-03-05 15:23:59 +01:00
return list(range(max(1, d), min(D+1, 26)))
raise ValueError
2023-03-05 15:23:59 +01:00
def main():
solver_pattern = re.compile("day(\d\d?).py")
matches = map(solver_pattern.fullmatch, os.listdir(PATH))
solvers = {int(s.group(1)) : s.group(0) for s in matches if s != None}
2023-03-05 15:23:59 +01:00
mind, maxd = min(solvers), max(solvers)
days = sorted(solvers.keys())
args = parser.parse_args()
2023-03-05 15:23:59 +01:00
#Handle day argument
if args.day != None:
try:
2023-03-05 15:23:59 +01:00
days = parse_day_arg(args.day, mind, maxd)
except ValueError:
2023-03-05 15:23:59 +01:00
return 'invalid format of argument -d/--day'
#Handle input argument
inputs = {}
input_path = pathlib.Path('.')
num_pattern = re.compile(r'\d\d?')
try:
fs = os.listdir(args.input)
input_path /= args.input
for fn in fs:
ns = num_pattern.findall(fn)
if ns == []:
print('warning : ignoring input file {}'.format(fn))
continue
n = int(ns[0])
inputs[n] = fn
except FileNotFoundError:
return "input directory or file doesn't exist"
except NotADirectoryError:
if len(days) > 1:
return "input is file"
day = days[0]
inputs[day] = args.input
#Solve days
for d in days:
print("=" * 6 + " Day {} ".format(d) + "=" * 6)
if d not in solvers:
2023-03-05 15:23:59 +01:00
return "no solver for day {}".format(d)
mod_name = solvers[d][:-3]
try:
mod = importlib.import_module(mod_name)
solver = Solver(mod)
except:
2023-03-05 15:23:59 +01:00
return "bad solver\n\n{}".format(traceback.format_exc())
try:
if args.test:
2023-03-05 15:23:59 +01:00
ptI, ptII = solver.test()
else:
2023-03-05 15:23:59 +01:00
if d not in inputs:
return 'no input file'
with open(input_path / inputs[d]) as f:
puzzle_input = f.read()
ptI, ptII = solver.solve(puzzle_input)
print("Part One : {}".format(ptI))
print("Part Two : {}".format(ptII))
except:
2023-03-05 15:23:59 +01:00
return "error in solver\n\n{}".format(traceback.format_exc())
2023-03-05 15:23:59 +01:00
if __name__ == '__main__':
error = main()
if error != None:
print('error : {}'.format(error))