aoc/2021/04/04.py

81 lines
2.8 KiB
Python
Raw Permalink Normal View History

2021-12-06 23:39:12 +00:00
# get input
with open("input.txt") as f:
lines = f.read().split('\n\n')
raw_numbers = lines[0]
raw_boards = lines[1:]
# define a structure for our bingo game
class Board:
def __init__(self, points):
self.unmarked = set()
self.marked = set()
for point in points:
self.unmarked.add(point) # all points are unmarked at start of game
def __repr__(self):
return f"\nmarked = {self.marked}\nunmarked = {sorted(self.unmarked)}\n"
def is_bingo(self):
# we need to count rows and columns (diagonals don't count)
i_vals = []
j_vals = []
for point in self.marked:
i, j, _ = point
i_vals.append(i)
j_vals.append(j)
from collections import Counter
i_count = Counter(i_vals)
j_count = Counter(j_vals)
# bingo is called if there are 5 in a row or column;
# luckily, diagonals don't count
return ( ( 5 in i_count.values() ) or ( 5 in j_count.values() ) )
def score(self, last_number):
score = 0
# "start by finding the sum of all unmarked numbers"
for point in self.unmarked:
_, _, value = point
score += value
# "then, multiply that sum by the number that was just called"
score *= last_number
return score
# parse input
numbers = [int(number) for number in raw_numbers.split(',')]
boards = set()
for board in raw_boards:
points = set(
(i,j,int(value))
for i, row in enumerate(board.split('\n'))
for j, value in enumerate(row.split())
)
boards.add(Board(points))
# game loop
def part1(numbers, boards):
for number in numbers: # call a number
for board in boards: # then check the boards
for point in board.unmarked: # point by point.
_, _, value = point
if value == number: # if there is a match,
board.unmarked.remove(point) # then it is no longer unmarked
board.marked.add(point) # and it should be tracked as marked.
break # we also don't need to continue checking, since numbers are unique.
if board.is_bingo(): # once bingo is called,
return board.score(number) # we can calculate our score with that last number.
print(part1(numbers, boards))
# game loop 2 -- this time we wanna lose
def part2(numbers, boards):
for number in numbers: # call a number
for board in boards.copy(): # then check the boards
for point in board.unmarked: # point by point.
_, _, value = point
if value == number: # if there is a match,
board.unmarked.remove(point) # then it is no longer unmarked
board.marked.add(point) # and it should be tracked as marked.
break # we also don't need to continue checking, since numbers are unique.
if len(boards) == 1 and board.is_bingo(): # if this is the last bingo'd board,
return board.score(number) # then we can calculate our score.
elif board.is_bingo(): # otherwise we still have more boards to check
boards.remove(board) # so we just throw it away.
print(part2(numbers, boards))