265 lines
7 KiB
Python
265 lines
7 KiB
Python
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Any, Generic, Protocol, TypeVar
|
|
from urllib.request import Request, urlopen
|
|
from html.parser import HTMLParser
|
|
from importlib import import_module
|
|
from timeit import default_timer
|
|
from argparse import ArgumentParser
|
|
|
|
T1 = TypeVar("T1")
|
|
T2 = TypeVar("T2")
|
|
|
|
|
|
class CodeExtractor(HTMLParser):
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.inpre = False
|
|
self.incode = False
|
|
self.code: list[str] = []
|
|
|
|
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
if tag == "code":
|
|
self.incode = True
|
|
elif tag == "pre":
|
|
self.inpre = True
|
|
|
|
def handle_endtag(self, tag: str) -> None:
|
|
if tag == "code":
|
|
self.incode = False
|
|
elif tag == "pre":
|
|
self.inpre = False
|
|
|
|
def handle_data(self, data: str) -> None:
|
|
if self.incode and self.inpre:
|
|
self.code.append(data)
|
|
|
|
|
|
class Aoc2(ABC, Generic[T1, T2]):
|
|
def __init__(
|
|
self, year: int, day: int, example_code_nr1: int = 0, example_code_nr2: int = -1
|
|
):
|
|
self._year = year
|
|
self._day = day
|
|
self._example_code_nr1 = example_code_nr1
|
|
self._example_code_nr2 = example_code_nr2
|
|
|
|
with open(".session", encoding="utf8") as f:
|
|
self._session = f.read().strip()
|
|
|
|
@abstractmethod
|
|
def part1(self, inpt: T1) -> Any:
|
|
...
|
|
|
|
@abstractmethod
|
|
def part2(self, inpt: T2) -> Any:
|
|
...
|
|
|
|
@abstractmethod
|
|
def parseinput1(self, inpt: str) -> T1:
|
|
...
|
|
|
|
@abstractmethod
|
|
def parseinput2(self, inpt: str) -> T2:
|
|
...
|
|
|
|
def dl_with_cache(self, url: str, cachefilepath: Path) -> str:
|
|
cachefilepath.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
if cachefilepath.exists():
|
|
return cachefilepath.read_text()
|
|
|
|
request = Request(url, headers={"Cookie": f"session={self._session}"})
|
|
with urlopen(request) as response:
|
|
inpt: str = response.read().decode()
|
|
|
|
cachefilepath.write_text(inpt)
|
|
return inpt
|
|
|
|
def get_data_for_day(self) -> str:
|
|
return self.dl_with_cache(
|
|
f"https://adventofcode.com/{self._year}/day/{self._day}/input",
|
|
Path("aoc_cache") / str(self._year) / f"day{self._day}_input",
|
|
)
|
|
|
|
def get_examples_for_day(self) -> list[str]:
|
|
site = self.dl_with_cache(
|
|
f"https://adventofcode.com/{self._year}/day/{self._day}",
|
|
Path("aoc_cache") / str(self._year) / f"day{self._day}_site",
|
|
)
|
|
code_extractor = CodeExtractor()
|
|
code_extractor.feed(site)
|
|
return code_extractor.code
|
|
|
|
def run_example_1(self) -> str:
|
|
inpt = self.get_examples_for_day()[self._example_code_nr1]
|
|
return str(self.part1(self.parseinput1(inpt)))
|
|
|
|
def run_example_2(self) -> str:
|
|
inpt = self.get_examples_for_day()[self._example_code_nr2]
|
|
return str(self.part2(self.parseinput2(inpt)))
|
|
|
|
def run_1(self) -> str:
|
|
inpt = self.get_data_for_day()
|
|
return str(self.part1(self.parseinput1(inpt)))
|
|
|
|
def run_2(self) -> str:
|
|
inpt = self.get_data_for_day()
|
|
return str(self.part2(self.parseinput2(inpt)))
|
|
|
|
def run(self) -> None:
|
|
print("Example:\t", end="")
|
|
print(f"{self.run_example_1()}\t", end="")
|
|
print(f"{self.run_example_2()}")
|
|
|
|
print("Real input:\t", end="")
|
|
print(f"{self.run_1()}\t", end="")
|
|
print(f"{self.run_2()}")
|
|
|
|
|
|
class Aoc(Generic[T1], Aoc2[T1, T1]):
|
|
def parseinput(self, inpt: str) -> T1:
|
|
raise NotImplementedError
|
|
|
|
def parseinput1(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
def parseinput2(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
def part2(self, inpt: T1) -> Any:
|
|
return self.part1(inpt)
|
|
|
|
|
|
class AocSameParser(Generic[T1]):
|
|
@abstractmethod
|
|
def parseinput(self, inpt: str) -> T1:
|
|
...
|
|
|
|
def parseinput1(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
def parseinput2(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
|
|
class AocSameImplementation(Generic[T1]):
|
|
@abstractmethod
|
|
def part(self, inpt: T1) -> Any:
|
|
...
|
|
|
|
def part1(self, inpt: T1) -> Any:
|
|
return self.part(inpt)
|
|
|
|
def part2(self, inpt: T1) -> Any:
|
|
return self.part(inpt)
|
|
|
|
|
|
class AocParseLines(Generic[T1, T2], Protocol):
|
|
def parseline1(self, inpt: str) -> T1:
|
|
raise NotImplementedError
|
|
|
|
def parseinput1(self, inpt: str) -> list[T1]:
|
|
return [self.parseline1(line) for line in inpt.splitlines()]
|
|
|
|
def parseline2(self, inpt: str) -> T2:
|
|
raise NotImplementedError
|
|
|
|
def parseinput2(self, inpt: str) -> list[T2]:
|
|
return [self.parseline2(line) for line in inpt.splitlines()]
|
|
|
|
|
|
class AocParseLinesSameParser(Generic[T1], AocParseLines[T1, T1]):
|
|
def parseline1(self, inpt: str) -> T1:
|
|
return self.parseline(inpt)
|
|
|
|
def parseline2(self, inpt: str) -> T1:
|
|
return self.parseline(inpt)
|
|
|
|
@abstractmethod
|
|
def parseline(self, inpt: str) -> T1:
|
|
...
|
|
|
|
|
|
def run_latest() -> None:
|
|
days = [
|
|
import_module(f".{file.stem}", package="aoc")
|
|
for file in sorted(Path("aoc").iterdir())
|
|
if file.name.endswith(".py") and file.name.startswith("day")
|
|
]
|
|
day = days[-1]
|
|
print(f"Running {day.__name__}")
|
|
s_time = default_timer()
|
|
day.main()
|
|
e_time = default_timer()
|
|
print(f"Time: {e_time-s_time}")
|
|
|
|
|
|
def new() -> None:
|
|
parser = ArgumentParser()
|
|
parser.add_argument("day", type=int)
|
|
parser.add_argument("--type1", "-1", default="list[int]")
|
|
parser.add_argument("--type2", "-2", default="list[int]")
|
|
|
|
args = parser.parse_args()
|
|
|
|
day = args.day
|
|
t1 = args.type1
|
|
t2 = args.type2
|
|
|
|
filepath = Path("aoc") / f"day{day:02d}.py"
|
|
if filepath.exists():
|
|
raise RuntimeError(f"{filepath.name} already exists")
|
|
|
|
template = f"""from __future__ import annotations
|
|
from typing import TypeAlias
|
|
|
|
from .aoc import Aoc2
|
|
|
|
P1: TypeAlias = {t1}
|
|
P2: TypeAlias = {t2}
|
|
|
|
class Day{day:02d}(Aoc2[P1, P2]):
|
|
def parseinput1(self, inpt: str) -> P1:
|
|
raise NotImplementedError()
|
|
|
|
def parseinput2(self, inpt: str) -> P2:
|
|
raise NotImplementedError()
|
|
|
|
def part1(self, inpt: P1) -> int:
|
|
return 0
|
|
|
|
def part2(self, inpt: P2) -> int:
|
|
return 0
|
|
|
|
def main() -> None:
|
|
aoc = Day{day:2d}(2023,{day})
|
|
aoc.run()
|
|
|
|
if __name__ == "__main__":
|
|
main()"""
|
|
|
|
with filepath.open("w") as f:
|
|
f.write(template)
|
|
|
|
|
|
def main() -> None:
|
|
days = [
|
|
import_module(f".{file.stem}", package="aoc")
|
|
for file in sorted(Path("aoc").iterdir())
|
|
if file.name.endswith(".py") and file.name.startswith("day")
|
|
]
|
|
b_time = default_timer()
|
|
for day in days:
|
|
print(f"Running {day.__name__}")
|
|
s_time = default_timer()
|
|
day.main()
|
|
e_time = default_timer()
|
|
print(f"Time: {e_time-s_time}")
|
|
print()
|
|
f_time = default_timer()
|
|
print(f"Overall time: {f_time - b_time}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|