# 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)) # refactor out common code def check_board(board, number): 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. # game loop def part1(numbers, boards): for number in numbers: # call a number for board in boards: # then check the boards check_board(board, number) 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 check_board(board, number) 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))