summaryrefslogtreecommitdiffstats
path: root/4.py
blob: 0c7cf4fba8a80b8ca30918483326e17eadd435bd (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
from collections import defaultdict
from utils import open_day

class Board:
    col_hits: list[int]
    row_hits: list[int]
    has_bingo: bool
    nums: dict[int, tuple[int, int]]
    def __init__(self, cells: list[list[int]]) -> None:
        width: int = len(cells[0])
        height: int = len(cells)
        self.col_hits = [height] * width
        self.row_hits = [width ] * height
        self.nums = dict()
        for y, row in enumerate(cells):
            for x, cell in enumerate(row):
                self.nums[cell] = (x, y)
        self.has_bingo = False
    def call(self, num: int) -> None:
        pos: tuple[int, int] | None = self.nums.pop(num, None)
        if pos is None: return
        x, y = pos
        self.col_hits[x] -= 1
        self.row_hits[y] -= 1
        self.has_bingo = (
            self.has_bingo or
            not self.col_hits[x] or
            not self.row_hits[y]
        )
    @property
    def unmarked_sum(self) -> int:
        return sum(self.nums.keys())
    @staticmethod
    def from_string(s: str) -> 'Board':
        return Board([[int(n) for n in l.split()] for l in s.split('\n')])

def solve(nums: list[int], boards: list[Board]) -> tuple[int, int]:
    indices: set[int] = { i for i in range(len(boards)) }
    nums_indices: defaultdict[set[int]] = defaultdict(set)
    i: int
    board: Board
    for i, board in enumerate(boards):
        for n in board.nums:
            nums_indices[n].add(i)
    wins: list[int] = []
    num: int
    for num in nums:
        i: int
        board: Board
        winners: set[int] = set()
        num_indices: set[int] = nums_indices.get(num)
        for i in indices & num_indices:
            boards[i].call(num)
            if boards[i].has_bingo:
                wins.append(num * boards[i].unmarked_sum)
                winners.add(i)
        indices.difference_update(winners)
    return wins[0], wins[-1]

nums: str | list[int]
boards: list[str] | list[Board]
nums, *boards = open_day(4).read().rstrip().split('\n\n')
nums = list(map(int, nums.split(',')))
boards = [Board.from_string(b) for b in boards]

part1, part2 = solve(nums, boards)
print(part1)
print(part2)