advent.py
from abc import ABC, abstractmethod
from pathlib import Path
from pprint import pprint
from typing import final
from aocd import submit
class AoCException(Exception):
pass
# Abstract Solution
class BaseSolution(ABC):
_year: int
_day: int
_is_debugging: bool = False
def __init__(
cls,
lines=False,
csv=False,
two_dimensional=False,
int_csvline=False,
block=False,
separator=",",
to_int=False,
):
if lines:
if to_int:
lines = cls.read_input().splitlines()
cls.input = [int(d) for d in lines]
else:
cls.input = cls.read_input().splitlines()
else:
if csv:
lines = cls.read_input().splitlines()
if to_int:
cls.input = [
[int(d) for d in line.split(separator)] for line in lines
]
else:
cls.input = [line.split(separator) for line in lines]
else:
if two_dimensional:
lines = cls.read_input().splitlines()
cls.input = [list(line) for line in lines]
else:
if int_csvline: # single line
line = cls.read_input().strip()
cls.input = [int(d) for d in line.split(separator)]
else:
if block: # blocks separated by newline
lines = cls.read_input()
blocks = lines.split("\n\n")
arr = []
for i in range(len(blocks)):
blocks[i] = blocks[i].strip().split("\n")
arr.append(blocks[i])
cls.input = arr
else: # if string:
if to_int:
cls.input = int(cls.read_input())
else:
cls.input = cls.read_input()
@property
def year(self):
if not hasattr(self, "_year"):
raise NotImplementedError("explicitly define Solution._year")
return self._year
@property
def day(self):
if not hasattr(self, "_day"):
raise NotImplementedError("explicitly define Solution._day")
return self._day
@abstractmethod
def dummy(self): # prevent usage of BaseSolution
pass
@final
def read_input(self) -> str:
"""
handles locating, reading, and parsing input files
"""
input_file = Path(
Path(__file__).parent.parent.parent,
"input.txt",
)
if not input_file.exists():
raise AoCException(
f'Failed to find an input file at path "./{input_file.relative_to(Path.cwd())}". You can run `./start --year {self.year} {self.day}` to create it.'
)
data = input_file.read_text().strip("\n")
if not data:
raise AoCException(
f'Found a file at path "./{input_file.relative_to(Path.cwd())}", but it was empty. Make sure to paste some input!'
)
return data
@final
def save(self, part, res, tm):
answer_path = Path(
Path(__file__).parent.parent.parent,
f"ans{part}.txt",
)
if Path.exists(answer_path):
Path.open(answer_path, "w").close() # always overwrite
with answer_path.open("a") as f:
f.write(res + "\n")
f.write(int(tm * 1000).__str__() + " msecs")
@final
def submit_puzzle(self, part, res):
submit(res, part=part, day=self.day, year=self.year)
@final
def debug(self, *objects, trailing_newline=False):
if not self._is_debugging:
return
for o in objects:
pprint(o)
if trailing_newline:
print()
def solve(self, part, res, tm, submit_to_aocd=True):
print(f"Part {part} :: {res}")
if self._is_debugging:
print("********** Debug mode, skipping submission **********")
else:
self.save(part, str(res), tm)
if submit_to_aocd:
self.submit_puzzle(part="a" if part == "1" else "b", res=res)
# Concrete Solutions
class InputAsStringSolution(BaseSolution):
def __init__(self):
super().__init__(
lines=False,
csv=False,
two_dimensional=False,
int_csvline=False,
block=False,
separator=",",
to_int=False,
)
def dummy(self):
pass
class InputAsLinesSolution(BaseSolution):
def __init__(self, to_int=False):
super().__init__(
lines=True,
csv=False,
two_dimensional=False,
int_csvline=False,
block=False,
separator=",",
to_int=to_int,
)
def dummy(self):
pass
class InputAsCSVSolution(BaseSolution):
def __init__(self, separator=",", to_int=False):
super().__init__(
lines=False,
csv=True,
two_dimensional=False,
int_csvline=False,
block=False,
separator=separator,
to_int=to_int,
)
def dummy(self):
pass
class InputAsIntCSVLineSolution(BaseSolution):
def __init__(self, separator=","):
super().__init__(
lines=False,
csv=False,
two_dimensional=False,
int_csvline=True,
block=False,
separator=separator,
to_int=False,
)
def dummy(self):
pass
class InputAs2DSolution(BaseSolution):
def __init__(self):
super().__init__(
lines=False,
csv=False,
two_dimensional=True,
int_csvline=False,
block=False,
separator=",",
to_int=False,
)
def dummy(self):
pass
class InputAsBlockSolution(BaseSolution):
def __init__(self):
super().__init__(
lines=False,
csv=False,
two_dimensional=False,
int_csvline=False,
block=True,
separator=",",
to_int=False,
)
def dummy(self):
pass