diff --git a/aoc/aoc.py b/aoc/aoc.py index f4d5522..9392764 100644 --- a/aoc/aoc.py +++ b/aoc/aoc.py @@ -108,6 +108,8 @@ class Aoc2(ABC, Generic[T1, T2]): return str(self.part2(self.parseinput2(inpt))) def run(self) -> None: + print(f"Running Day {self._day:02d}") + s_time = default_timer() print("Example:\t", end="") print(f"{self.run_example_1()}\t", end="") print(f"{self.run_example_2()}") @@ -115,6 +117,8 @@ class Aoc2(ABC, Generic[T1, T2]): print("Real input:\t", end="") print(f"{self.run_1()}\t", end="") print(f"{self.run_2()}") + e_time = default_timer() + print(f"Time: {e_time-s_time}") class Aoc(Generic[T1], Aoc2[T1, T1]): @@ -188,11 +192,7 @@ def run_latest() -> None: 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: @@ -251,11 +251,7 @@ def main() -> None: ] 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}") diff --git a/aoc/day12.py b/aoc/day12.py index d7565c2..04545a2 100644 --- a/aoc/day12.py +++ b/aoc/day12.py @@ -1,151 +1,71 @@ from __future__ import annotations -from typing import TypeAlias + +from collections import defaultdict from dataclasses import dataclass -from itertools import groupby -from collections import deque +from typing import TypeAlias -from .aoc import Aoc2, AocParseLines +from .aoc import Aoc2, AocParseLines, AocSameImplementation -@dataclass +@dataclass(frozen=True) class Line: springs: str - spring_groups: deque[tuple[int, str]] - groups: list[int] + groups: tuple[int, ...] @classmethod - def from_str(cls, inpt: str) -> Line: + def from_str(cls, inpt: str, multi: int) -> Line: springs, groups_str = inpt.split(" ") - groups = [int(d) for d in groups_str.split(",")] + springs = "?".join([springs] * multi) + groups = tuple(int(d) for d in groups_str.split(",")) * multi - springs_group = deque((len(list(d)), i) for i, d in groupby(springs)) - - return Line("." + springs + ".", springs_group, groups) - - @classmethod - def from_str2(cls, inpt: str) -> Line: - springs, groups_str = inpt.split(" ") - springs = "?".join([springs] * 5) - groups = [int(d) for d in groups_str.split(",")] - groups = groups + groups + groups + groups + groups - - springs_group = deque((len(list(d)), i) for i, d in groupby(springs)) - - return Line("." + springs + ".", springs_group, groups) + return Line("." + springs + ".", groups) P1: TypeAlias = list[Line] P2: TypeAlias = list[Line] -class Day12(AocParseLines[Line, Line], Aoc2[P1, P2]): +class Day12(AocSameImplementation[P1], AocParseLines[Line, Line], Aoc2[P1, P2]): def parseline1(self, inpt: str) -> Line: - return Line.from_str(inpt) + return Line.from_str(inpt, 1) def parseline2(self, inpt: str) -> Line: - return Line.from_str2(inpt) + return Line.from_str(inpt, 5) @staticmethod def fits(line: str, startpos: int, length: int) -> bool: - if startpos + length >= len(line): - return False starter = line[startpos - 1] segment = line[startpos : startpos + length] stopper = line[startpos + length] - return starter != "#" and stopper != "#" and ("." not in segment) + return "#" not in (starter, stopper) and "." not in segment - def part1(self, inpt: P1) -> int: + def part(self, inpt: P1) -> int: accum = 0 - # inpt = inpt[:1] - # print(inpt) for line in inpt: - belegungen = [[0]] + ends = {0: 1} for i, group in enumerate(line.groups): - # print(f"{belegungen=}") - next_belegungen = [] - for belegung in belegungen: - last_end = belegung[-1] - # print(f"{last_start=}, {last_end=}, {group=}, {len(line.springs)=}") - # try: - # first_hash = line.springs[last_end:].index("#") + 1 - # # print("First hash", first_hash) - # except ValueError: - # first_hash = len(line.springs) - 1 - for startvalue in range(last_end + 1, len(line.springs)): - # print(startvalue) - if "#" in line.springs[last_end:startvalue]: - continue - fits = self.fits(line.springs, startvalue, group) + next_ends: dict[int, int] = defaultdict(lambda: 0) + for end, amount in ends.items(): + for startvalue in range(end + 1, len(line.springs) - group): + if not ( + "#" in line.springs[end:startvalue] + or ( + i == len(line.groups) - 1 + and "#" in line.springs[startvalue + group :] + ) + or not Day12.fits(line.springs, startvalue, group) + ): + next_ends[startvalue + group] += amount - if i == len(line.groups) - 1: - if "#" in line.springs[startvalue + group :]: - continue - - if fits: - # print(f"Y {i=}, {startvalue=}, {group=}") - next_belegungen.append(belegung + [startvalue + group]) - # else: - # print(f"N {i=}, {startvalue=}, {group=}") - - belegungen = next_belegungen - # print(belegungen) - # print(line.springs[1:-1], line.groups, len(belegungen)) - # if len(belegungen) == 1: - # print( - # f"{line.springs=}, {line.groups=}, {[line.springs[x] for x in belegungen[0]]}" - # ) - for belegung in belegungen: - last_belegung = belegung[-1] - if "#" in line.springs[last_belegung:]: - print(line.springs, line.groups) - if len(set((tuple(x) for x in belegungen))) != len(belegungen): - print("X") - # print(line.springs, line.groups) - for belegung in belegungen: - self.check_belegung(line, belegung) - accum += len(belegungen) - # print(len(belegungen)) - # print(belegungen) - # print(belegungen) - - print(accum) + ends = next_ends + accum += sum(ends.values()) return accum - def check_belegung(self, line, belegung): - if "#" in [line.springs[x] for x in belegung]: - print(f"Wrong # in {line=}") - if len(belegung) != 1 + len(line.groups): - print(f"Groups Wrong in {line=}") - - for ends, lengths in zip(belegung[1:], line.groups): - b = line.springs[ends - lengths : ends] - if "." in b: - print("{line=}") - - last_end = 0 - bstr = "." - for ends, lengths in zip(belegung[1:], line.groups): - beginning = ends - lengths - bstr += "." * (beginning - len(bstr)) - bstr += "#" * lengths - if (line.springs[beginning - 1] == "#") or line.springs[ends] == "#": - print("X") - - if beginning - last_end < 1: - print("XXX") - last_end = ends - # print(bstr) - - def part2(self, inpt: P2) -> int: - for line in inpt: - print(line) - return self.part1(inpt) - def main() -> None: aoc = Day12(2023, 12, example_code_nr1=1, example_code_nr2=1) - aoc.run_example_2() + aoc.run() if __name__ == "__main__":