144 lines
3.9 KiB
Python
144 lines
3.9 KiB
Python
import importlib
|
|
import __main__
|
|
import os
|
|
import re
|
|
import pathlib
|
|
import traceback
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
PATH = pathlib.Path(__file__).parent
|
|
INPUT_PATH = PATH / "input"
|
|
|
|
class Tests(list):
|
|
def add(self, test_input, partI=None, partII=None):
|
|
self.append((str(test_input), partI, partII))
|
|
|
|
def testf(tests, f, part):
|
|
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:
|
|
def __init__(self, mod):
|
|
def unpack(name, default):
|
|
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')
|
|
|
|
def format_result(f):
|
|
def aux(*args):
|
|
solver = args[0]
|
|
ptI, ptII = f(*args)
|
|
print("Part One : {}".format(ptI))
|
|
print("Part Two : {}".format(ptII))
|
|
return aux
|
|
|
|
@format_result
|
|
def solve(self, puzzle_input):
|
|
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
|
|
|
|
@format_result
|
|
def test(self):
|
|
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
|
|
|
|
|
|
|
|
def read_input(mod_name):
|
|
name = mod_name + ".txt"
|
|
with open(INPUT_PATH / name) as f:
|
|
r = f.read()
|
|
return r
|
|
|
|
|
|
parser = ArgumentParser()
|
|
|
|
parser.description = \
|
|
"""Solves Advent of Code 2019 puzzle of day {}.
|
|
The puzzle is available at https://adventofcode.com/2019/day/{}"""
|
|
|
|
parser.add_argument('-d', '--day',
|
|
action='store',
|
|
type=str,
|
|
metavar="DAY[-DAY]",
|
|
help="solve puzzle of day DAY or of range DAY-DAY")
|
|
|
|
parser.add_argument('-t', '--test',
|
|
action='store_true',
|
|
help="run tests")
|
|
|
|
def parse_day_arg(s):
|
|
split = s.split('-')
|
|
if len(split) == 1:
|
|
return [int(split[0])]
|
|
elif len(split) == 2:
|
|
d, D = map(int, split)
|
|
return list(range(d, D+1))
|
|
raise ValueError
|
|
|
|
|
|
|
|
if __name__ == '__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}
|
|
|
|
days = list(range(1, 25+1))
|
|
|
|
args = parser.parse_args()
|
|
if args.day != None:
|
|
try:
|
|
days = parse_day_arg(args.day)
|
|
except ValueError:
|
|
print("error : invalid format of argument -d/--day")
|
|
quit()
|
|
|
|
for d in days:
|
|
print("=" * 6 + " Day {} ".format(d) + "=" * 6)
|
|
if d not in solvers:
|
|
print("error : no solver for day {}".format(d))
|
|
continue
|
|
mod_name = solvers[d][:-3]
|
|
try:
|
|
mod = importlib.import_module(mod_name)
|
|
solver = Solver(mod)
|
|
except:
|
|
print("error : bad solver")
|
|
print(traceback.format_exc())
|
|
continue
|
|
try:
|
|
if args.test:
|
|
solver.test()
|
|
else:
|
|
puzzle_input = read_input(mod_name)
|
|
solver.solve(puzzle_input)
|
|
except:
|
|
print("error : error in solver")
|
|
print(traceback.format_exc())
|
|
|