Fun with mixins and linear time solution for day 04

This commit is contained in:
Christoph Stahl 2023-12-04 18:39:43 +01:00
parent 6ef57e1e75
commit 19db86c4f2
5 changed files with 90 additions and 45 deletions

48
aoc.py
View file

@ -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 = [

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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<card_id>\d+):(\s*(?P<win>\d+)\s*)*\|(\s*(?P<have>\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()