4
\$\begingroup\$

I've learned Python basics about two years ago and did a few personal projects, one big one that turned out to be useful in my line of work, a few small programs for my hobbies and I've also began working on this huge personal project that is currently in a state of crisis as it needs a complete reworking to fully support all the functionality I want it to be capable of. So I've written quite a lot of code, but because I am completely self-taught and rarely showed my code to professionals, I am still very uncomfortable with the end results. Currently I am doing adventofcode.com and so far so good (currently at puzzle 12). Some tasks were very easy, and others, while completed, have resulted in some weird solutions by me. I would be very grateful if someone commented on some of the weirder and/or messier ones. Here's one example with my personal input and solution.

Basically, the input (listed both below and in the link to GitHub) lists draws in its first line and then lists bingo boards (of shape 5 x 5) separated by blank lines. Each line within the board contains numbers delimited with one or two whitespaces (for alignment). As the numbers are being drawn in succession from the first line, the bingo boards cross them out. The board that has at least one column or one row with crossed numbers only wins. The task is to find the first and the last winning bingo board in the given input, calculate the sum of uncrossed numbers in each board and multiply the sum by the respective draw that made that board the winner. The result are two scores, one for the first winning board and one for the last one.

Here's the verbose description of the problem

Bingo is played on a set of boards each consisting of a 5x5 grid of numbers. Numbers are chosen at random, and the chosen number is marked on all boards on which it appears. (Numbers may not appear on all boards.) If all numbers in any row or any column of a board are marked, that board wins. (Diagonals don't count.)

The submarine has a bingo subsystem to help passengers (currently, you and the giant squid) pass the time. It automatically generates a random order in which to draw numbers and a random set of boards (your puzzle input). For example:

7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 22 13 17 11 0 8 2 23 4 24 21 9 14 16 7 6 10 3 18 5 1 12 20 15 19 3 15 0 2 22 9 18 13 17 5 19 8 7 25 23 20 11 10 24 4 14 21 16 12 6 14 21 17 24 4 10 16 15 9 19 18 8 23 26 20 22 11 13 6 5 2 0 12 3 7 

After the first five numbers are drawn (7, 4, 9, 5, and 11), there are no winners, but the boards are marked as follows (shown here adjacent to each other to save space):

22 13 17 11 0 3 15 0 2 22 14 21 17 24 4
8 2 23 4 24 9 18 13 17 5 10 16 15 9 19
21 9 14 16 7 19 8 7 25 23 18 8 23 26 20
6 10 3 18 5 20 11 10 24 4 22 11 13 6 5
1 12 20 15 19 14 21 16 12 6 2 0 12 3 7

After the next six numbers are drawn (17, 23, 2, 0, 14, and 21), thereare still no winners:

22 13 17 11 0 3 15 0 2 22 14 21 17 24 4
8 2 23 4 24 9 18 13 17 5 10 16 15 9 19
21 9 14 16 7 19 8 7 25 23 18 8 23 26 20
6 10 3 18 5 20 11 10 24 4 22 11 13 6 5
1 12 20 15 19 14 21 16 12 6 2 0 12 3 7

Finally, 24 is drawn:

22 13 17 11 0 3 15 0 2 22 14 21 17 24 4
8 2 23 4 24 9 18 13 17 5 10 16 15 9 19
21 9 14 16 7 19 8 7 25 23 18 8 23 26 20
6 10 3 18 5 20 11 10 24 4 22 11 13 6 5
1 12 20 15 19 14 21 16 12 6 2 0 12 3 7

At this point, the third board wins because it has at least one complete row or column of marked numbers (in this case, the entire top row is marked: 14 21 17 24 4).

The score of the winning board can now be calculated. Start by finding the sum of all unmarked numbers on that board; in this case, the sum is 188. Then, multiply that sum by the number that was just called when the board won, 24, to get the final score, 188 * 24 = 4512.

To guarantee victory against the giant squid, figure out which board will win first. What will your final score be if you choose that board?

Your puzzle answer was 31424.

--- Part Two ---

On the other hand, it might be wise to try a different strategy: let the giant squid win.

You aren't sure how many bingo boards a giant squid could play at once, so rather than waste time counting its arms, the safe thing to do is to figure out which board will win last and choose that one. That way, no matter which boards it picks, it will win for sure.

In the above example, the second board is the last to win, which happens after 13 is eventually called and its middle column is completely marked. If you were to keep playing until this point, the second board would have a sum of unmarked numbers equal to 148 for a final score of 148 * 13 = 1924.

Figure out which board will win last. Once it wins, what would its final score be?

Your puzzle answer was 23042.

Here's the copy of the working solution from GitHub (Sorry for lines being longer than 79 characters).

import numpy as np class Bingo: """ A class to store bingo cards and tracking whether they win """ def __init__(self, bingo): """ Create a bingo card out of a 2D-list of shape (5, 5) :param bingo: a `list` of five `list`s of length 5 """ self.lines = np.array(bingo, dtype='object') # specifying the element type in `np.ndarray` # allows storing `str` of variable length self.columns = self.lines.T # The two attributes store the same object at a right angle, allowing for easy check of both # streaking lines or columns def check(self, x): """ Check whether the current number is in the bingo card and whether that concludes the game for the card :param x: `str` number :return: `bool`, `True` if the card wins, `False` otherwise """ if x in flatten(self.lines): # if the number is in the card for i, line in enumerate(self.lines): if x in line: self.lines[i][np.where(line == x)[0][0]] += '*' # find the number and mark it by appending an asterix to the `str` value if self.check_win(): return True # if it wins, it wins return False # otherwise it doesn't def check_win(self): """ Check whether a column wins by separately collapsing lines and columns into sets of final characters in that line/column. If that set is `{'*'}`, this can only mean that every number in that line/column has been marked and the card as a whole has won :return: `bool`, `True` if the card wins, `False` otherwise """ line_lasts = [{x[-1] for x in line} for line in self.lines] column_lasts = [{x[-1] for x in column} for column in self.columns] if {'*'} in line_lasts or {'*'} in column_lasts: return True return False def count_unmarked(self): """ To determine a score of the winning card, the sum of unmarked numbers has to be calculated. :return: `int` """ unmarked = [] for x in flatten(self.lines): if x[-1] != '*': unmarked.append(int(x)) return sum(unmarked) def flatten(x): """ Flatten an iterable (`list` or `np.ndarray`, to be precise) :param x: `list` or `np.ndarray` :return: one-dimensional `list` """ if isinstance(x, list) or isinstance(x, np.ndarray): return [flatten(item) for sublist in x for item in sublist] return x # read the input and strip the cosing blank line with open('input4.txt') as f: puzzle = f.read().rstrip() # break input into lines by `'\n'` escape character and filter out empty lines lines = [x for x in puzzle.split('\n') if x] # the first line contains the draws separated by commas. Build a list of their string values draws = lines[0].split(',') # The rest of the lines are bingo cards bingos_raw = lines[1:] # Just to test a personal hypothesis of mine that every card will for sure # First, every line is split using blank spaces. The resulting 2-dimensional list is flattened and # collapsed into a set, that is then checked against the draw set. If the draw set contains the bingo set, # every bingo card will win bingo_set = set(flatten([x.split() for x in bingos_raw])) draw_set = set(draws) print(f'Every board will win: {draw_set >= bingo_set}.') # Split the raw bingo lines into sets of 5: build a new element in the list once every five `j`s and # add 5 lines from that `j` into a single 2D element of shape (5, 5) by additionally splitting each line # using whitespaces sets = [[bingos_raw[i + j].split() for i in range(5)] for j in range(len(bingos_raw)) if j % 5 == 0] # use every element in `sets` to make a bingo card bingos = [Bingo(x) for x in sets] # initialize variables to store the first and the last winner first_winner = None last_winner = None # check numbers draw by draw for draw in draws: # Check the same number against every card. If the card has one this draw, it is deleted from the list # since there's no need to mark any mor numbers there, hence the usage of a `while` loop instead of a # `for` loop. If the card is not a winner, move to the next one. If this is the last card left, this is # the last winner (since my earlier hypothesis turned out to be true, this also breaks the outer loop). # If even the first winner has not been determined, remember him. Otherwise just discard the winner and check # the next draw i = 0 while i < len(bingos): winner = bingos[i].check(draw) if not winner: i += 1 continue if len(bingos) == 1: last_winner, last_draw = bingos[0], draw break if not first_winner: first_winner, first_draw = bingos[i], draw del bingos[i] if last_winner: break # Now that the first winner and the last winner have been determined, calculate their score. # The final check is simply my personal curiosity. print(f"First winner's score is: {first_winner.count_unmarked() * int(first_draw)}.") print(f"Last winner's score is: {last_winner.count_unmarked() * int(last_draw)}, " f"last winning draw was the last overall: {last_draw == draws[-1]}.") 

The input file is in the link to my GitHub (link given above). Here's the direct link to the file on GitHub. It is also presented below (it contains >600 lines):

23,30,70,61,79,49,19,37,64,48,72,34,69,53,15,74,89,38,46,36,28,32,45,2,39,58,11,62,97,40,14,87,96,94,91,92,80,99,6,31,57,98,65,10,33,63,42,17,47,66,26,22,73,27,7,0,55,8,56,29,86,25,4,12,51,60,35,50,5,75,95,44,16,93,21,3,24,52,77,76,43,41,9,84,67,71,83,88,59,68,85,82,1,18,13,78,20,90,81,54 50 98 65 14 47 0 22 3 83 46 87 93 81 84 58 40 35 28 74 48 45 99 59 37 64 85 66 90 32 88 95 6 4 74 27 1 10 70 41 92 54 36 42 9 39 60 99 31 67 16 4 44 66 10 58 33 64 93 42 46 19 63 6 83 54 60 51 76 8 30 71 49 73 7 55 17 67 52 61 98 46 5 4 51 76 73 59 74 8 33 48 96 20 26 15 55 19 86 29 43 20 75 12 67 41 89 36 65 66 92 40 19 1 0 28 99 61 85 58 50 44 72 57 35 86 69 87 27 59 33 47 34 60 93 9 71 84 46 24 96 15 91 5 61 19 57 78 55 31 8 19 10 1 81 96 27 71 2 52 56 15 22 48 82 34 64 47 42 49 51 26 72 61 12 57 71 94 40 34 26 12 80 57 38 55 4 56 11 73 49 75 60 61 9 50 91 70 23 1 90 39 86 30 73 38 6 53 58 14 36 85 12 75 88 5 0 29 41 21 15 47 66 59 54 1 99 97 50 17 60 36 13 29 80 32 49 85 75 71 15 10 79 41 61 66 68 57 55 74 98 68 33 87 89 59 96 35 76 78 55 4 63 51 10 65 58 38 22 54 9 66 18 37 60 6 43 86 50 23 77 10 42 19 61 2 40 29 20 84 0 70 59 96 80 57 76 12 39 36 6 73 43 92 37 99 36 42 10 77 87 3 57 4 20 35 18 7 46 91 11 17 98 8 53 61 22 37 89 51 9 71 6 72 87 32 13 79 86 53 98 16 2 93 48 38 63 82 66 61 69 73 90 85 54 65 9 66 28 5 63 91 50 70 59 80 95 68 92 72 67 69 88 36 43 53 36 81 66 78 90 2 25 94 82 55 34 45 1 14 37 13 4 70 48 75 67 73 32 18 91 33 93 71 48 47 8 79 69 53 82 5 31 80 45 37 67 77 41 56 97 65 46 62 42 81 67 70 59 24 88 84 11 29 52 78 4 39 12 90 2 44 3 10 75 89 30 93 22 14 8 79 60 98 99 49 23 26 86 91 38 77 45 95 66 75 81 42 85 21 3 40 37 65 20 50 12 54 0 86 52 15 56 29 39 94 66 79 14 65 26 3 4 59 60 40 47 48 19 13 85 32 44 69 90 21 35 8 1 59 56 72 71 84 18 11 96 38 23 37 79 92 20 33 94 17 1 94 42 21 82 92 60 9 32 38 71 3 37 77 18 89 16 74 76 2 83 30 28 11 70 94 3 1 71 87 6 66 19 76 28 10 86 22 62 2 67 0 31 46 27 8 33 43 92 29 35 90 8 30 27 67 60 82 68 1 5 29 93 44 34 56 65 48 37 51 57 45 63 94 77 67 80 45 57 43 37 81 25 84 82 50 8 9 64 7 29 18 52 16 14 73 28 11 76 6 5 76 67 18 16 68 47 15 29 59 46 32 40 9 84 30 17 20 22 3 35 80 38 72 88 35 44 14 89 72 75 67 56 2 3 58 41 49 12 52 92 9 22 34 88 65 39 93 61 47 38 67 33 18 60 34 50 69 31 83 29 30 9 12 95 79 2 24 54 87 46 68 48 58 42 61 87 46 26 34 74 85 9 54 38 50 29 84 40 4 49 39 33 99 53 77 59 0 42 35 86 68 23 62 5 96 92 7 4 1 50 70 12 83 46 34 63 91 56 11 76 90 71 88 95 19 18 13 3 62 42 29 57 79 85 39 64 14 28 98 99 36 91 9 63 69 66 2 17 31 51 43 49 98 94 31 64 53 54 57 3 28 10 12 2 24 99 95 35 17 76 27 48 0 41 80 62 13 38 98 32 15 16 8 96 93 43 81 99 40 20 57 37 24 3 94 17 70 14 7 52 71 49 95 84 76 38 45 59 89 1 7 27 0 98 92 64 8 50 68 13 91 26 51 2 31 45 25 1 5 50 68 77 61 53 74 20 99 38 63 76 44 15 42 51 67 87 86 12 24 49 0 70 82 9 2 24 96 74 60 68 16 40 32 20 48 6 98 11 65 94 10 54 8 95 74 41 11 33 76 2 10 44 89 23 56 45 78 60 34 15 5 26 83 71 20 72 85 75 54 15 59 93 53 8 4 10 84 44 36 17 62 24 27 98 87 54 73 13 35 9 48 52 33 7 56 80 70 74 35 53 69 75 25 27 47 91 85 62 32 93 26 89 18 52 16 73 49 55 77 42 40 54 67 73 11 10 49 35 59 12 93 37 15 69 97 41 47 39 2 75 99 21 29 26 23 75 41 10 86 71 67 66 38 99 91 92 63 40 28 69 97 42 77 60 44 53 12 84 57 72 51 31 90 37 35 89 55 73 87 46 32 45 0 58 50 81 13 18 66 38 4 40 62 22 14 48 35 76 83 13 70 26 4 1 30 22 91 93 29 69 41 74 40 63 80 65 66 72 23 23 65 33 56 38 84 41 34 21 2 4 78 27 17 11 22 53 52 32 80 24 25 42 91 99 54 51 0 23 52 92 69 10 46 7 20 35 12 37 73 19 56 26 79 32 27 74 34 5 57 75 10 24 32 7 96 54 22 78 5 23 69 65 43 20 29 85 44 92 71 41 87 73 0 48 54 92 16 36 37 42 59 4 9 44 52 14 12 6 47 57 38 70 82 0 53 81 32 35 3 17 22 62 80 30 8 28 15 42 46 79 64 32 29 75 5 0 9 90 69 41 71 85 1 6 68 89 40 31 39 32 48 64 38 28 80 98 88 14 97 6 60 52 11 55 95 34 63 81 4 80 33 14 83 68 78 69 81 59 15 72 0 74 21 75 49 6 67 73 64 8 25 87 3 45 34 97 86 1 79 49 12 63 10 59 88 30 84 74 87 67 47 26 0 57 71 40 2 76 98 15 89 23 65 44 27 87 54 38 12 43 29 18 39 94 48 0 7 57 61 70 28 60 68 50 13 34 49 67 40 88 74 99 20 26 63 69 62 24 32 35 45 96 79 1 92 7 17 76 30 95 21 75 46 74 39 7 58 23 90 61 64 37 81 82 92 36 54 9 53 17 51 33 10 27 67 35 44 22 23 28 96 1 56 29 0 12 5 50 99 70 42 8 24 25 39 53 51 89 85 50 15 94 84 27 72 26 51 3 85 63 45 1 64 44 17 80 13 88 2 12 97 91 25 18 59 14 9 67 63 6 18 26 98 50 86 74 75 56 34 48 7 99 20 64 8 53 10 15 57 6 35 13 68 24 90 19 91 71 86 95 58 10 44 98 8 41 60 1 16 29 59 43 84 48 48 56 8 74 4 66 30 77 35 90 94 0 75 49 84 5 39 11 54 87 33 58 96 22 2 5 38 57 63 65 74 58 22 8 81 45 96 78 3 11 28 42 30 39 51 87 33 34 75 14 56 34 67 70 17 7 80 10 31 85 68 59 63 74 40 13 81 99 62 6 92 84 71 37 39 85 99 74 16 10 12 21 91 2 83 4 94 38 51 36 41 97 45 65 24 50 82 92 52 35 28 65 6 13 23 7 57 86 18 67 26 85 29 22 89 99 62 94 31 96 14 17 50 56 9 98 10 63 4 8 46 21 58 89 3 27 12 11 55 16 61 38 43 33 54 53 14 99 31 25 25 70 24 40 14 75 82 58 68 41 22 71 72 93 1 47 97 6 81 45 92 42 2 76 12 31 84 30 0 85 55 70 72 45 57 78 52 67 60 22 43 32 8 44 34 14 64 91 89 18 70 19 62 16 56 84 49 41 3 20 85 5 76 95 22 63 55 37 31 72 42 17 28 65 1 85 17 57 62 48 34 29 69 52 28 90 64 54 21 38 0 50 84 44 60 93 80 75 89 83 39 84 78 12 5 29 4 35 7 85 73 25 58 27 45 22 90 91 47 74 60 96 15 24 26 13 30 82 31 43 23 71 1 51 36 40 62 25 54 86 8 83 2 47 34 33 41 27 98 24 13 25 53 50 56 77 4 41 19 22 68 70 75 9 65 30 33 60 74 80 31 83 34 79 11 11 90 38 78 73 17 16 14 37 4 80 68 21 70 92 47 26 81 67 25 10 31 23 41 22 90 62 2 50 79 77 51 8 11 13 32 29 43 88 33 39 34 89 45 23 91 9 6 68 3 62 70 89 27 87 45 65 96 80 29 1 54 90 68 16 72 50 28 95 12 21 71 81 10 60 33 14 60 44 78 6 65 87 11 8 79 21 59 35 19 26 69 67 42 27 25 36 80 10 45 71 24 80 87 56 7 61 43 38 18 52 46 41 28 48 0 74 20 34 63 3 84 42 85 9 36 64 41 7 49 91 92 13 94 88 73 98 79 0 12 76 66 86 67 9 2 85 74 5 34 8 81 7 56 28 36 13 42 29 75 12 27 85 45 9 26 25 62 41 22 79 11 95 0 24 72 76 81 67 16 96 41 94 58 7 0 79 38 27 11 61 36 56 88 39 89 63 31 75 8 62 51 5 46 28 77 97 89 86 13 87 55 73 90 57 84 44 40 49 34 25 0 58 6 21 7 56 15 41 94 42 89 16 18 74 57 79 96 35 3 14 45 20 19 80 87 85 28 69 17 27 88 54 62 65 44 93 69 13 9 85 63 43 11 47 83 57 30 20 56 71 46 49 7 77 45 24 75 39 69 48 74 44 49 64 65 25 22 46 93 88 52 27 37 50 19 35 47 54 67 44 32 71 13 57 7 38 26 98 65 46 1 21 8 55 30 62 92 27 3 69 50 99 85 11 86 6 64 34 97 47 98 7 38 9 26 68 75 92 54 58 42 13 78 37 85 28 81 16 51 82 74 15 4 86 55 0 70 88 24 50 79 63 40 21 47 39 61 49 36 89 16 13 2 37 89 19 9 82 13 84 34 58 56 10 27 92 46 4 94 44 24 52 86 55 39 23 22 99 5 65 92 8 86 77 98 79 72 28 78 16 23 3 55 48 68 95 66 30 43 50 31 15 11 45 32 70 25 59 31 47 68 77 56 23 66 78 54 88 50 55 60 58 89 83 84 99 86 97 95 53 46 1 94 87 8 80 38 77 81 17 51 47 19 69 86 50 71 5 93 61 66 36 58 0 90 58 17 29 92 67 1 8 64 99 63 22 57 19 68 78 36 93 53 2 27 48 62 39 14 8 49 22 90 54 26 4 99 27 34 78 25 11 85 28 31 42 36 53 15 64 75 60 45 35 99 84 26 53 90 61 51 98 39 86 47 37 52 80 63 67 49 35 70 11 32 45 94 73 43 91 92 74 94 32 27 56 50 33 54 67 46 35 25 10 93 97 30 90 4 57 15 69 83 39 71 68 74 81 11 44 98 60 17 73 43 40 32 38 39 61 56 97 94 70 23 2 86 91 54 19 98 93 42 88 0 16 30 32 71 89 86 81 76 68 29 2 14 72 63 7 27 67 59 1 24 18 28 98 95 10 62 80 71 36 3 89 20 63 46 47 65 84 22 6 82 19 81 38 45 54 85 67 34 79 25 58 38 73 61 72 98 4 19 40 32 10 29 31 89 15 33 5 7 63 49 48 71 81 88 70 5 39 41 22 19 20 7 75 23 69 46 63 14 54 80 45 94 6 55 88 62 76 78 95 64 65 36 58 22 7 21 98 93 42 79 99 9 89 10 6 5 33 92 72 
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Data validation

Code comment:

Just to test a personal hypothesis of mine that every card will [win] for sure

It's good to validate input... But, there may be better (and more stringent) ways to do this.

Visual inspection can check that every 'bingo card' is populated "5x5" with 1 or 2 digit numbers. Perhaps using, for instance, double digit "00" instead of " 0" might be an improvement to the given input.

A very quick edit check shows there are 99 commas in the "draw sequence" row (meaning 100 values are present.) But, this does not preclude an erroneous duplication of one or more of those values (meaning one or more values can never be 'drawn'.)

Likewise, any (poorly formed) 5x5 collection of numbers could contain duplicates of one number. If such a bingo card were in the collection, one drawn value would 'stamp' multiple cells on the card. (An unfair advantage!)


Ambiguity

The term 'draw' can be interpreted as "draw the next number" OR "two-or-more bingo cards 'win' at this stage; a draw has occurred".

Perhaps the bingo cards randomly selected by the squid and the human both result in a win when a particular number is drawn. Do the players 'split' the prize 50:50? Or, does the lowest/highest calculated result take the entire prize?

Perhaps there are two-or-more cards that would be the "worst" choice, achieving a "Bingo!" at the same time (after many fruitless numbers have been drawn.) Likewise, which of two such cards should be appraised as the "worst case" card? Discovery seems to depend on where each card appears in the collection of cards! The "score" evaluated (sum of unmarked numbers) does not necessarily correlate with the sequence of being declared "the worst".


Results...

With little to do, I've written my own version (in C, not Python) to investigate my claims made above. Happy to report that the 100 'drawn numbers' are all unique (meaning all 2 digit possibilities are covered.) Also, none of the 100 "bingo cards" contains any duplicate values, so all are fair.

Below are the crude results output by this program. For the cards provided and the sequence of numbers drawn, I'm somewhat surprised by how long it took for the first winner to be declared, and before the final card could claim a 'bingo' on completing one row or column.

Not done is the final flourish of "summing 'unmarked' values" on each winning card(s) (and multiplying those sums by the sequence number.)

A bit of fun for a few hours!

At iteration 22, Drawn 32, Card 48 has a bingo! At iteration 25, Drawn 39, Card 72 has a bingo! At iteration 33, Drawn 96, Card 20 has a bingo! At iteration 36, Drawn 92, Card 61 has a bingo! At iteration 36, Drawn 92, Card 93 has a bingo! At iteration 37, Drawn 80, Card 98 has a bingo! At iteration 38, Drawn 99, Card 43 has a bingo! At iteration 39, Drawn 6, Card 8 has a bingo! At iteration 40, Drawn 31, Card 60 has a bingo! At iteration 40, Drawn 31, Card 62 has a bingo! At iteration 41, Drawn 57, Card 7 has a bingo! At iteration 42, Drawn 98, Card 30 has a bingo! At iteration 43, Drawn 65, Card 35 has a bingo! At iteration 45, Drawn 33, Card 28 has a bingo! At iteration 46, Drawn 63, Card 40 has a bingo! At iteration 46, Drawn 63, Card 97 has a bingo! At iteration 47, Drawn 42, Card 11 has a bingo! At iteration 47, Drawn 42, Card 59 has a bingo! At iteration 47, Drawn 42, Card 91 has a bingo! At iteration 48, Drawn 17, Card 52 has a bingo! At iteration 49, Drawn 47, Card 0 has a bingo! At iteration 49, Drawn 47, Card 71 has a bingo! At iteration 49, Drawn 47, Card 85 has a bingo! At iteration 50, Drawn 66, Card 1 has a bingo! At iteration 50, Drawn 66, Card 4 has a bingo! At iteration 50, Drawn 66, Card 42 has a bingo! At iteration 51, Drawn 26, Card 19 has a bingo! At iteration 51, Drawn 26, Card 55 has a bingo! At iteration 52, Drawn 22, Card 41 has a bingo! At iteration 52, Drawn 22, Card 47 has a bingo! At iteration 52, Drawn 22, Card 58 has a bingo! At iteration 52, Drawn 22, Card 99 has a bingo! At iteration 53, Drawn 73, Card 38 has a bingo! At iteration 53, Drawn 73, Card 92 has a bingo! At iteration 54, Drawn 27, Card 6 has a bingo! At iteration 54, Drawn 27, Card 63 has a bingo! At iteration 54, Drawn 27, Card 73 has a bingo! At iteration 54, Drawn 27, Card 90 has a bingo! At iteration 55, Drawn 7, Card 32 has a bingo! At iteration 55, Drawn 7, Card 44 has a bingo! At iteration 55, Drawn 7, Card 80 has a bingo! At iteration 55, Drawn 7, Card 83 has a bingo! At iteration 56, Drawn 0, Card 51 has a bingo! At iteration 56, Drawn 0, Card 78 has a bingo! At iteration 56, Drawn 0, Card 89 has a bingo! At iteration 57, Drawn 55, Card 3 has a bingo! At iteration 57, Drawn 55, Card 65 has a bingo! At iteration 57, Drawn 55, Card 66 has a bingo! At iteration 58, Drawn 8, Card 12 has a bingo! At iteration 58, Drawn 8, Card 33 has a bingo! At iteration 58, Drawn 8, Card 56 has a bingo! At iteration 58, Drawn 8, Card 74 has a bingo! At iteration 59, Drawn 56, Card 16 has a bingo! At iteration 59, Drawn 56, Card 29 has a bingo! At iteration 59, Drawn 56, Card 70 has a bingo! At iteration 59, Drawn 56, Card 94 has a bingo! At iteration 60, Drawn 29, Card 22 has a bingo! At iteration 60, Drawn 29, Card 27 has a bingo! At iteration 60, Drawn 29, Card 95 has a bingo! At iteration 61, Drawn 86, Card 18 has a bingo! At iteration 61, Drawn 86, Card 84 has a bingo! At iteration 61, Drawn 86, Card 87 has a bingo! At iteration 62, Drawn 25, Card 49 has a bingo! At iteration 62, Drawn 25, Card 68 has a bingo! At iteration 63, Drawn 4, Card 86 has a bingo! At iteration 64, Drawn 12, Card 26 has a bingo! At iteration 64, Drawn 12, Card 50 has a bingo! At iteration 64, Drawn 12, Card 76 has a bingo! At iteration 64, Drawn 12, Card 77 has a bingo! At iteration 65, Drawn 51, Card 10 has a bingo! At iteration 65, Drawn 51, Card 13 has a bingo! At iteration 65, Drawn 51, Card 23 has a bingo! At iteration 65, Drawn 51, Card 53 has a bingo! At iteration 66, Drawn 60, Card 9 has a bingo! At iteration 67, Drawn 35, Card 37 has a bingo! At iteration 67, Drawn 35, Card 54 has a bingo! At iteration 68, Drawn 50, Card 67 has a bingo! At iteration 69, Drawn 5, Card 5 has a bingo! At iteration 69, Drawn 5, Card 79 has a bingo! At iteration 70, Drawn 75, Card 39 has a bingo! At iteration 70, Drawn 75, Card 64 has a bingo! At iteration 71, Drawn 95, Card 31 has a bingo! At iteration 71, Drawn 95, Card 96 has a bingo! At iteration 72, Drawn 44, Card 2 has a bingo! At iteration 72, Drawn 44, Card 36 has a bingo! At iteration 72, Drawn 44, Card 57 has a bingo! At iteration 72, Drawn 44, Card 82 has a bingo! At iteration 73, Drawn 16, Card 46 has a bingo! At iteration 74, Drawn 93, Card 17 has a bingo! At iteration 76, Drawn 3, Card 21 has a bingo! At iteration 77, Drawn 24, Card 34 has a bingo! At iteration 77, Drawn 24, Card 45 has a bingo! At iteration 80, Drawn 76, Card 24 has a bingo! At iteration 80, Drawn 76, Card 25 has a bingo! At iteration 81, Drawn 43, Card 69 has a bingo! At iteration 82, Drawn 41, Card 75 has a bingo! At iteration 83, Drawn 9, Card 14 has a bingo! At iteration 83, Drawn 9, Card 81 has a bingo! At iteration 84, Drawn 84, Card 88 has a bingo! At iteration 92, Drawn 82, Card 15 has a bingo! 

In terms of a Code Review, having used 'C' bit flags, masks and integers instead of string manipulations, one might question whether Python is the right language for a problem such as this. How long would the program run if there were 1000 bingo cards? or 1,000,000? Just something to think about.


Data validation (reprise)

One hundred 'bingo cards' is a large-enough sample of groups of 25 'random' values that tallying should exhibit moderation toward a flat distribution.

Below are the results of tallying the provided 'cards' for instances of each value, 0 to 99:

30,28,28,24,28,25,25,28,33,29, 32,27,27,26,25,24,21,23,19,23, 21,18,31,23,23,22,25,29,26,29, 21,23,27,24,30,29,24,24,32,27, 24,27,29,20,25,29,26,24,25,25, 31,22,20,22,32,20,28,28,24,24, 26,22,24,29,21,27,21,34,26,25, 27,30,21,22,30,28,22,18,19,23, 27,25,16,15,27,31,27,23,21,29, 23,22,28,22,24,18,19,14,28,28 // <-- Only 14! 

An outlier like 14 instances of 97 shows that the preparation of these 100 cards could be improved. In an ideal distribution, each value should appear exactly 25 times (across 100 cards).


Going out on a limb...

__init__() contains the line:

 self.columns = self.lines.T 

that appears to instantiate a (2nd) transposed version of the given 5x5 array of values of one bingo card.

The reader encounters self.lines and self.columns in check_win() where a "Bingo!" may be discovered.

However, in check(), it appears that only 'cells' of self.lines may be actually marked as having been "drawn" in the current iteration. It would serve little purpose to look for a bingo resulting from a complete column when self.columns does not track those values.

\$\endgroup\$
1
  • \$\begingroup\$ If anyone wants to play with a C source version of this project, here is one attempt that goes a step beyond. Not only is the Python's "missing column" code fixed, but, contrary to instructions, even the two diagonals can be easily found to declare "Bingo!" (It's actually very little code, in the end.) \$\endgroup\$ Commented Dec 31, 2024 at 5:25
2
\$\begingroup\$

Efficiency

Two functions in the Bingo class call the flatten function, and each of these 2 functions are called many time apiece. This means flatten is called many times.

However, it only needs to be called once if you create another variable to store a flat list of all the numbers in the card. This can be added to the class constructor:

def __init__(self, bingo): self.lines = np.array(bingo, dtype='object') # specifying the element type in `np.ndarray` self.columns = self.lines.T self.all_numbers = flatten(self.lines) 

Then the new variable can be used in the other functions, for example:

def check(self, x): if x in self.all_numbers: # if the number is in the card 

Naming

Naming the class Bingo is a bit vague. Since it describes a single bingo card, a better name would be BingoCard.

It is great that you added a docstring to describe the class:

""" A class to store bingo cards and tracking whether they win """ 

There is no need to mention it is a class sine that is obvious from the code. It would be clearer to mention that the class is just for a card, not "cards":

""" Store a bingo card and track whether it wins """ 

check is not a very descriptive name for a function. It would be better to describe what is being checked.

def check(self, x): 

Perhaps this would be better:

def is_winner(self, x): 

The variable x is also vague and would be better as number.

def is_winner(self, number): 

Typo

In the comment, "cosing" should be "closing".

\$\endgroup\$
0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.