from __future__ import annotations from collections import defaultdict from dataclasses import dataclass from typing import TypeAlias from .aoc import Aoc2, AocParseLines, AocSameImplementation @dataclass(frozen=True) class Line: springs: str groups: tuple[int, ...] @classmethod def from_str(cls, inpt: str, multi: int) -> Line: springs, groups_str = inpt.split(" ") springs = "?".join([springs] * multi) groups = tuple(int(d) for d in groups_str.split(",")) * multi return Line("." + springs + ".", groups) P1: TypeAlias = list[Line] P2: TypeAlias = list[Line] class Day12(AocSameImplementation[P1], AocParseLines[Line, Line], Aoc2[P1, P2]): def parseline1(self, inpt: str) -> Line: return Line.from_str(inpt, 1) def parseline2(self, inpt: str) -> Line: return Line.from_str(inpt, 5) @staticmethod def fits(line: str, startpos: int, length: int) -> bool: starter = line[startpos - 1] segment = line[startpos : startpos + length] stopper = line[startpos + length] return "#" not in (starter, stopper) and "." not in segment def part(self, inpt: P1) -> int: accum = 0 for line in inpt: ends = {0: 1} for i, group in enumerate(line.groups): 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 ends = next_ends accum += sum(ends.values()) return accum def main() -> None: aoc = Day12(2023, 12, example_code_nr1=1, example_code_nr2=1) aoc.run() if __name__ == "__main__": main()