This repository has been archived by the owner on Dec 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
__init__.py
110 lines (100 loc) · 4.45 KB
/
__init__.py
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
"""--- Day 4: Giant Squid ---"""
from dataclasses import dataclass
from pathlib import Path
from numpy import array
from numpy import byte
from numpy import sum as sum_
from aoc import log
from aoc import open_utf8
@dataclass
class Bingo:
"""Stores the complete data for running the bingo game."""
bingo_input: array # A list of the bingo numbers to be called in sequential order
bingo_boards: array # 3D array tracking the current state of the bingo boards
win_order: [] # Set to the index of the winning board when complete
result_products: [] # Captures the first and last result product values
def __init__(self, dataset_path: Path):
"""Constructor loads the data and executes the game to populate the
object."""
self.win_order = []
self.result_products = []
with open_utf8(dataset_path) as file:
self.bingo_input = array(
[byte(entry) for entry in file.readline().split(",")]
)
bingo_boards = [] # Easier to build bingo boards in a list.
for line in file:
if len(line.strip()) == 0: # New bingo board
bingo_boards.append([])
else: # add row to current board
bingo_boards[-1].append(
[
int(entry.strip())
for entry in line.strip().split(" ")
if len(entry.strip()) > 0 # single digits have extra space
]
)
self.bingo_boards = array(bingo_boards, dtype=byte)
log.info(
"Loaded day 4 %s dataset of bingo input %d with %d bingo boards",
dataset_path.name,
len(self.bingo_input),
len(self.bingo_boards),
)
for called_number in self.bingo_input: # run the game.
self.__check_number(called_number)
if len(self.win_order) == len(self.bingo_boards): # All boards finished
break
def __check_number(self, called_value):
"""Checks if number exists on boards, if so will set number to -1 to
show it's called.
:param called_value: The called integer value to mark in the game of bingo.
:return:
"""
for board_index, _ in enumerate(self.bingo_boards):
if (
board_index not in self.win_order
and called_value in self.bingo_boards[board_index]
):
log.debug("Found %d in bingo board index %d", called_value, board_index)
self.bingo_boards[board_index][
self.bingo_boards[board_index] == called_value
] = -1
# check for bingo
if self.__check_bingo(self.bingo_boards[board_index]):
if (
len(self.win_order) == 0
or len(self.win_order) == len(self.bingo_boards) - 1
):
# Zero marked elements
self.bingo_boards[board_index][
self.bingo_boards[board_index] == -1
] = 0
summed_value = sum_(self.bingo_boards[board_index])
self.result_products.append(summed_value * called_value)
log.info(
"%s board at index %d. The sum of unmarked elements %d and "
"last element %d (product=%d)",
"First" if len(self.win_order) == 0 else "Last",
board_index,
summed_value,
called_value,
self.result_products[-1],
)
self.win_order.append(board_index)
@staticmethod
def __check_bingo(board: array) -> bool:
"""Checks rows and columns for all -1's indicating all numbers have
been called.
:param board: The numpy array / bingo board to chec.
:return: Boolean, true indicates board has bingo!
"""
# Check the rows
for row_index in range(board.shape[0]):
if all(-1 == board[row_index]):
return True
# Check the columns
for column_index in range(board.shape[1]):
if all(-1 == board[:, column_index]):
return True
return False