view the Advent of Code 2021 Day 4 challenge
# Setup - load any packages
from copy import deepcopy
# load the data
day4_raw = open('day4_data.txt').read().splitlines()
# extract the numbers called from the first item in the list
# pop extracts and removes them from the input
numbers_called = day4_raw.pop(0).split(',')
#extract the next row as it is blank
day4_raw.pop(0)
def create_bingo_cards(cards):
# card count
count = 1
# dictionary to hold all bingo cards
bingo_cards = {}
#temporary list to hold a single bingo card
bingo_card = []
#for each row in the data
for row in cards:
# check for empty line signalling the start/end of a card
if row =='':
# add the current card to the dictionary
bingo_cards['card_'+str(count)] = bingo_card
# reset the temporary bingo card
bingo_card = []
# increase card count
count = count + 1
else:
# append the row to the current card
bingo_card.append(row.split())
# return the dictionary of cards
return bingo_cards
# make the bingo cards
bingo_cards = create_bingo_cards(day4_raw)
def call_numbers(bingo_cards, numbers_called, part_one=True):
# create a temp copy of the bingo cards so we can update them
# Using deepcopy as the dictioanry contains other objects
bingo_cards_temp = deepcopy(bingo_cards)
# create a dictioanry to store the winning cards
winners = {}
# loop through the numbers called
for num in numbers_called:
#update each card to mark off the numbers called
for card, rows in bingo_cards_temp.items():
#only update cards that haven't yet won
if card not in winners:
# itterate through each row in the card
for idx, row in enumerate(rows):
# check if number called is in the row
if num in row:
#number is in the row so repalce with x
row[row.index(num)] = 'x'
# update the row in the card
bingo_cards_temp[card][idx] = row
# Check if the card is a winner
if check_winner(bingo_cards_temp[card]):
if part_one:
#The first card has won, calculate the answer and return it
return calculate(int(num), bingo_cards_temp[card])
# Add the winning card to the winners dictionary
winners[card] = calculate(int(num),bingo_cards_temp[card])
# Return the result of the calculation for the last card in the winners dictionary
return winners[list(winners.keys())[-1]]
def check_winner(card):
# copy the card using depcopy so we can alter it to make checking easier
card_temp=deepcopy(card)
# transpose the columns into rows
columns=list(map(list, zip(*card)))
# append the transposed columns as rows
for sublist in columns:
card_temp.append(sublist)
# check each row
for row in card_temp:
if gotaline(row):
# winning line found
return True
def gotaline(row):
# is the number of x's the same as the number of items
# Return a boolean list of number =='x'
# All function returns True if all items are True
return all([num=='x' for num in row])
def calculate(num, card):
# sum the numbers that are not X
unmarked_sum = sum([int(x) for y in card for x in y if x != 'x'])
# multiply the sum by the last number called and return it
return unmarked_sum*num
# calculate part 1 answer
p1_answer = call_numbers(bingo_cards, numbers_called)
print(f'answer = {p1_answer}')
answer = 51776
# calculate part 2 answer
p2_answer = call_numbers(bingo_cards, numbers_called, False)
print(f'answer = {p2_answer}')
answer = 16830