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 abc import ABC, abstractmethod
from pathlib import Path from pathlib import Path
from typing import Any, Generic, TypeVar from typing import Any, Generic, Protocol, TypeVar
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from html.parser import HTMLParser from html.parser import HTMLParser
from importlib import import_module from importlib import import_module
@ -124,6 +124,52 @@ class Aoc(Generic[T1], Aoc2[T1, T1]):
def part2(self, inpt: T1) -> Any: def part2(self, inpt: T1) -> Any:
return self.part1(inpt) 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: def main() -> None:
days = [ 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: def __init__(self) -> None:
super().__init__(2023, 1) Aoc2.__init__(self, 2023, 1)
def replace_digits(self, line: str) -> str: def replace_digits(self, line: str) -> str:
spelled_digits = [ 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))) rdigits = list(filter(lambda x: x in map(str, range(10)), reversed(line)))
return int(digits[0] + rdigits[0]) return int(digits[0] + rdigits[0])
def parseinput1(self, inpt: str) -> list[str]: def parseline1(self, inpt: str) -> str:
return inpt.splitlines() 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)) 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: def main() -> None:
day1 = Day01() day1 = Day01()

View file

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import aoc from aoc import Aoc2, AocParseLinesSameParser
@dataclass(frozen=True) @dataclass(frozen=True)
@ -57,12 +57,12 @@ class Game:
return Game(int(gameid), [GameSet.from_line(s) for s in set_str_list]) 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: def __init__(self) -> None:
super().__init__(2023, 2) Aoc2.__init__(self, 2023, 2)
def parseinput(self, inpt: str) -> list[Game]: def parseline(self, inpt: str) -> Game:
return [Game.from_line(line) for line in inpt.splitlines()] return Game.from_line(inpt)
def part1(self, games: list[Game]) -> int: def part1(self, games: list[Game]) -> int:
return sum(game.game_id for game in games if game.valid) 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 dataclasses import dataclass
from functools import reduce from functools import reduce
from typing import Any, Optional from typing import Any, Optional
from aoc import Aoc from aoc import Aoc2, AocSameParser
@dataclass @dataclass
@ -14,7 +14,7 @@ class Number:
linenr: int linenr: int
class Day03(Aoc[list[Number]]): class Day03(AocSameParser[list[Number]], Aoc2[list[Number], list[Number]]):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(2023, 3) super().__init__(2023, 3)

View file

@ -1,57 +1,56 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from collections import deque import regex
from aoc import Aoc2, AocParseLinesSameParser
from aoc import Aoc
@dataclass @dataclass
class Card: class Card:
card_id: int card_id: int
wins: int wins: int
winning: list[int] win: list[int]
have: list[int] have: list[int]
@staticmethod @staticmethod
def from_line(line: str) -> Card: def from_line(line: str) -> Card:
id_section, number_section = line.split(": ") matches = regex.match(
_, card_id = [s for s in id_section.split(" ") if s.strip()] r"Card\s*(?P<card_id>\d+):(\s*(?P<win>\d+)\s*)*\|(\s*(?P<have>\d+)\s*)*",
winning_str, have_str = number_section.split(" | ") 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()] win = [int(d) for d in matches.captures("win")]
have = [int(nr.strip()) for nr in have_str.split(" ") if nr.strip()] have = [int(d) for d in matches.captures("have")]
wins = len(set(winning).intersection(have)) wins = len(set(win).intersection(have))
return Card(int(card_id),wins, winning, 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: def __init__(self) -> None:
super().__init__(2023, 4) Aoc2.__init__(self, 2023, 4)
def parseinput(self, inpt: str) -> list[Card]:
lines = inpt.splitlines()
return [Card.from_line(line) for line in lines]
def parseline(self, inpt: str) -> Card:
return Card.from_line(inpt)
def part1(self, cards: list[Card]) -> int: 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: def part2(self, cards: list[Card]) -> int:
stack = deque(range(len(cards))) d = [0] * len(cards)
complete = len(cards) for card in reversed(cards):
while stack: d[card.card_id - 1] = 1 + sum(
card_id = stack.popleft() d[card.card_id + i] for i in range(card.wins)
for win in range(cards[card_id].wins): )
stack.appendleft(win + card_id + 1) return sum(d)
complete += 1
return complete
def main() -> None: def main() -> None:
day4 = Day04() day4 = Day04()
day4.run() day4.run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()