adventOfCode/challenges/2021/04-giantSquid/py/__init__.py

111 lines
2.8 KiB
Python

from typing import List, Tuple, Optional
from aocpy import BaseChallenge
from dataclasses import dataclass
BOARD_EDGE = 5 # assuming our boards are square, how long is one edge of the board?
WINNING_SEQUENCES = (
(0, 1, 2, 3, 4),
(5, 6, 7, 8, 9),
(10, 11, 12, 13, 14),
(15, 16, 17, 18, 19),
(20, 21, 22, 23, 24),
(0, 5, 10, 15, 20),
(1, 6, 11, 16, 21),
(2, 7, 12, 17, 22),
(3, 8, 13, 18, 23),
(4, 9, 14, 19, 24),
)
@dataclass
class Point:
value: int
marked: bool
Board = List[Point]
def parse(instr: str) -> Tuple[List[int], List[Board]]:
numbers, rawBoards = instr.strip().split("\n", 1)
numbers = list(map(int, numbers.split(",")))
rawBoards = rawBoards.strip().split("\n\n")
boards = []
for rb in rawBoards:
boards.append(
[Point(int(x), False) for x in rb.replace("\n", " ").split(" ") if x != ""]
)
return numbers, boards
def check_for_winning_boards(boards: List[Board]) -> List[int]:
# returns a list of indexes in `boards` that have won.
winning_boards = []
for i, board in enumerate(boards):
for sequence in WINNING_SEQUENCES:
has_sequence = True
for n in sequence:
has_sequence = has_sequence and board[n].marked
if has_sequence:
winning_boards.append(i)
break
return winning_boards
def calculate_board_score(board: Board, last_called_number: int) -> int:
sigma = sum([n.value for n in board if not n.marked])
return sigma * last_called_number
def play_round(boards: List[Board], number: int) -> List[Board]:
# plays a round of bingo, removes winning boards from `boards` and returns them.
for board in boards:
for board_number in board:
if number == board_number.value:
board_number.marked = True
break # assume each number only exists once in each board
winning_boards = check_for_winning_boards(boards)
o = []
for board_number in reversed(sorted(winning_boards)):
o.append(boards.pop(board_number))
return o
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
numbers, boards = parse(instr)
for number in numbers:
winning_boards = play_round(boards, number)
if len(winning_boards) != 0:
assert len(winning_boards) == 1
return calculate_board_score(winning_boards[0], number)
return -1
@staticmethod
def two(instr: str) -> int:
numbers, boards = parse(instr)
winners = []
for number in numbers:
winners += play_round(boards, number)
if len(boards) == 0:
return calculate_board_score(winners[-1], number)
return -1