summaryrefslogtreecommitdiffstats
path: root/day4/__init__.py
blob: 74c01f13ca18d9d12cea5fdbb4431b85b063508f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from collections import Counter
from copy import copy
from dataclasses import dataclass
from typing import Iterator, List, Optional, Tuple, Callable

from aoc import BaseAssignment

def bold(item: str, condition: Callable[[], bool]):
    return f'\033[36m{item}\033[0m' if condition() else item

@dataclass
class Number:
    nr: int
    marked: bool = False

    def __int__(self):
        return self.nr

    def __bool__(self):
        return self.marked

    def __radd__(self, other):
        return other + int(self)

    def mark(self):
        self.marked = True

@dataclass
class BingoCard:
    card: List[List[Number]]

    @property
    def bingo(self) -> bool:
        return any([
            *[all(bool(item) for item in row) for row in self.card],
            *[all(bool(row[col_index]) for row in self.card) for col_index in range(5)]
        ])

    def mark(self, nr: int):
        for row in self.card:
            for item in row:
                if int(item) == nr:
                    item.mark()

    @property
    def unmarked(self):
        return [
            item
            for row in self.card
            for item in row
            if not bool(item)
        ]

    def __str__(self):
            return '\n'.join([
                ''.join([
                    bold(str(int(item)).rjust(2, ' ').ljust(3, ' '), lambda: item.marked)
                    for item in row
                ])
                for row in self.card
            ])


class Assignment(BaseAssignment):
    def read_input(self, example = False) -> Tuple[List[int], List[BingoCard]]:
        cards = []
        card = None

        input = super().read_input(example)
        nrs = [ int(n) for n in next(input).split(',') ]

        try:
            row = next(input)

            while True:
                if row == '':
                    if card is not None:
                        cards.append(BingoCard(card=card))
                    card = []
                else:
                    card.append([ Number(nr=int(item)) for item in row.split(' ') if item != ''])

                row = next(input)

        except StopIteration:
            if card is not None:
                cards.append(BingoCard(card=card))

        return nrs, cards


class AssignmentOne(Assignment):
    example_result = 4512

    def run(self, input: Tuple[List[int], List[BingoCard]]) -> int:
        nrs, cards = input

        for nr in nrs:
            for card in cards:
                card.mark(nr)

                if card.bingo:
                    return nr * sum(card.unmarked)


class AssignmentTwo(Assignment):
    example_result = 1924

    def run(self, input: Tuple[List[int], List[BingoCard]]) -> int:
        nrs, cards = input

        in_game = copy(cards)
        for nr in nrs:
            for card in copy(in_game):
                card.mark(nr)

                if card.bingo:
                    in_game.remove(card)

                if len(in_game) == 0:
                    return nr * sum(card.unmarked)