From 19db86c4f29e9808f3e25f1d74bbe1fe4f72e790 Mon Sep 17 00:00:00 2001 From: Christoph Stahl Date: Mon, 4 Dec 2023 18:39:43 +0100 Subject: [PATCH] Fun with mixins and linear time solution for day 04 --- aoc.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- day01.py | 18 +++++++++--------- day02.py | 10 +++++----- day03.py | 4 ++-- day04.py | 55 +++++++++++++++++++++++++++---------------------------- 5 files changed, 90 insertions(+), 45 deletions(-) diff --git a/aoc.py b/aoc.py index 20738e8..d9d58b6 100644 --- a/aoc.py +++ b/aoc.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Generic, TypeVar +from typing import Any, Generic, Protocol, TypeVar from urllib.request import Request, urlopen from html.parser import HTMLParser from importlib import import_module @@ -124,6 +124,52 @@ class Aoc(Generic[T1], Aoc2[T1, T1]): 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 main() -> None: days = [ diff --git a/day01.py b/day01.py index 22549e5..0d1b632 100644 --- a/day01.py +++ b/day01.py @@ -1,9 +1,9 @@ -import aoc +from aoc import Aoc2, AocSameImplementation, AocParseLines -class Day01(aoc.Aoc[list[str]]): +class Day01(AocParseLines[str, str], AocSameImplementation[list[str]], Aoc2[list[str], list[str]]): def __init__(self) -> None: - super().__init__(2023, 1) + Aoc2.__init__(self, 2023, 1) def replace_digits(self, line: str) -> str: spelled_digits = [ @@ -28,15 +28,15 @@ class Day01(aoc.Aoc[list[str]]): rdigits = list(filter(lambda x: x in map(str, range(10)), reversed(line))) return int(digits[0] + rdigits[0]) - def parseinput1(self, inpt: str) -> list[str]: - return inpt.splitlines() + def parseline1(self, inpt: str) -> str: + return inpt - def part1(self, inpt: list[str]) -> int: + def parseline2(self, inpt: str) -> str: + return self.replace_digits(inpt) + + def part(self, inpt: list[str]) -> int: return sum(map(self.filter_digits, inpt)) - def parseinput2(self, inpt: str) -> list[str]: - return [self.replace_digits(line) for line in inpt.splitlines()] - def main() -> None: day1 = Day01() diff --git a/day02.py b/day02.py index f5b9cac..3be804a 100644 --- a/day02.py +++ b/day02.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -import aoc +from aoc import Aoc2, AocParseLinesSameParser @dataclass(frozen=True) @@ -57,12 +57,12 @@ class Game: return Game(int(gameid), [GameSet.from_line(s) for s in set_str_list]) -class Day02(aoc.Aoc[list[Game]]): +class Day02(AocParseLinesSameParser[Game], Aoc2[list[Game], list[Game]]): def __init__(self) -> None: - super().__init__(2023, 2) + Aoc2.__init__(self, 2023, 2) - def parseinput(self, inpt: str) -> list[Game]: - return [Game.from_line(line) for line in inpt.splitlines()] + def parseline(self, inpt: str) -> Game: + return Game.from_line(inpt) def part1(self, games: list[Game]) -> int: return sum(game.game_id for game in games if game.valid) diff --git a/day03.py b/day03.py index 39a2d6e..640aad3 100644 --- a/day03.py +++ b/day03.py @@ -2,7 +2,7 @@ from collections import defaultdict from dataclasses import dataclass from functools import reduce from typing import Any, Optional -from aoc import Aoc +from aoc import Aoc2, AocSameParser @dataclass @@ -14,7 +14,7 @@ class Number: linenr: int -class Day03(Aoc[list[Number]]): +class Day03(AocSameParser[list[Number]], Aoc2[list[Number], list[Number]]): def __init__(self) -> None: super().__init__(2023, 3) diff --git a/day04.py b/day04.py index d030f30..e19b139 100644 --- a/day04.py +++ b/day04.py @@ -1,57 +1,56 @@ from __future__ import annotations from dataclasses import dataclass -from collections import deque +import regex + +from aoc import Aoc2, AocParseLinesSameParser -from aoc import Aoc @dataclass class Card: card_id: int wins: int - winning: list[int] + win: list[int] have: list[int] @staticmethod def from_line(line: str) -> Card: - id_section, number_section = line.split(": ") - _, card_id = [s for s in id_section.split(" ") if s.strip()] - winning_str, have_str = number_section.split(" | ") + matches = regex.match( + r"Card\s*(?P\d+):(\s*(?P\d+)\s*)*\|(\s*(?P\d+)\s*)*", + line, + ) + if matches is None: + raise RuntimeError(f'Could not parse "{line}"') - winning = [int(nr.strip()) for nr in winning_str.split(" ") if nr.strip()] - have = [int(nr.strip()) for nr in have_str.split(" ") if nr.strip()] + win = [int(d) for d in matches.captures("win")] + have = [int(d) for d in matches.captures("have")] - wins = len(set(winning).intersection(have)) - return Card(int(card_id),wins, winning, have) + wins = len(set(win).intersection(have)) + return Card(int(matches["card_id"]), wins, win, have) -class Day04(Aoc[list[Card]]): + +class Day04(AocParseLinesSameParser[Card], Aoc2[list[Card], list[Card]]): def __init__(self) -> None: - super().__init__(2023, 4) - - def parseinput(self, inpt: str) -> list[Card]: - lines = inpt.splitlines() - return [Card.from_line(line) for line in lines] + Aoc2.__init__(self, 2023, 4) + def parseline(self, inpt: str) -> Card: + return Card.from_line(inpt) def part1(self, cards: list[Card]) -> int: - return sum(2**(card.wins - 1) for card in cards if card.wins) + return sum(2 ** (card.wins - 1) for card in cards if card.wins) def part2(self, cards: list[Card]) -> int: - stack = deque(range(len(cards))) - complete = len(cards) - while stack: - card_id = stack.popleft() - for win in range(cards[card_id].wins): - stack.appendleft(win + card_id + 1) - complete += 1 - return complete - - - + d = [0] * len(cards) + for card in reversed(cards): + d[card.card_id - 1] = 1 + sum( + d[card.card_id + i] for i in range(card.wins) + ) + return sum(d) def main() -> None: day4 = Day04() day4.run() + if __name__ == "__main__": main()