See the explanation for today’s challenge here. Most of the data we’ve dealt with so far has been 1 dimensional vectors, but for Day 4 we’re going to start working with 3-dimensional data. We’re playing BINGO!
The first line of data is a vector of numbers that are called out in the bingo game. We will extract these separately.
#Read first line of data that includes the announced numbersnumber_calls_chr <-readLines("./data/Day4.txt", n =1)#Covert this to a vector of integersnumber_calls_int <-as.integer(stringr::str_split(number_calls_chr, pattern =",", simplify =TRUE))number_calls_int
We then have a set of 100 bingo boards, each of which has 2 dimensions (5 rows and 5 columns). So how do we deal with this? One solution is to build a multi-dimensional array. A multi-dimensional array can be indexed just like a vector (1D) or matrix (2D), so we can easily work with and manipulate the data.
#Read in the bingo boards as integersallboards <-as.integer(scan("./data/Day4.txt", what ="list", skip =2))#Convert into 3D matrixboard_array <-array(allboards, dim =c(5, 5, length(allboards)/25))#Index the 1st and 2nd boardsboard_array[,,1:2]
We can return a 3-dimensional position of a given number using which() and the argument arr.ind = TRUE. This will come in handy for our challenges!
#Find the location of the number 0 on the first two boards#It is at position 2,4 on the 2nd boardwhich(board_array[,,1:2] ==0, arr.ind =TRUE)
dim1 dim2 dim3
[1,] 2 4 2
The Challenges
Challenge 1
For the first challenge we need to determine which of our 100 boards will win first. To keep track of all the boards I’ll create a corresponding 3D array of logical information (TRUE/FALSE) that records whether a number on a board has been marked.
#Array of logical data for all boardsresult_array <-array(rep(FALSE, n =length(allboards)), dim =c(5, 5, length(allboards)/25))#Show array for the same 2 boardsresult_array[,,1:2]
Now we need to work through each of the called numbers and work out which board gets a full row or column first (we’re ignoring diagonals here). We’ll just use a for loop to run through all these numbers.
#Loop through all numbers calledresult <-NULLfor (number in number_calls_int){#Use which to find all locations where this number occurs#Update results on the corresponding array of logicals. result_array[which(board_array == number, arr.ind =TRUE)] <-TRUE#Use apply to run through every board (the 3rd dimension, thus MARGIN = 3)#Check if any boards have full rows or columns board_status <-apply(result_array, MARGIN =3, FUN =function(x){any(rowSums(x) ==5) |any(colSums(x) ==5) }) has_winner <-any(board_status)#If there is a winner compute our answerif (has_winner) { winner <- board_array[,,board_status] unmarked <- winner[!result_array[,,board_status]]#Challenge asks for unmarked numbers * current number called result <-sum(unmarked) * numberbreak() }}result
[1] 49686
Challenge 2
For the second challenge, we need to find the board that will win last. This time around I’ll practice building a recursive function again.
#Reset our results arrayresult_array <-array(rep(FALSE, n =length(allboards)), dim =c(5, 5, length(allboards)/25))
#Create recursive function.play_bingo <-function(bingo_boards, numbers, i, current_results){#Find current number being called number <- numbers[i]#Update results board with new number called. current_results[which(bingo_boards == number, arr.ind =TRUE)] <-TRUE#Check status of all boards board_status <-apply(current_results, MARGIN =3, FUN =function(x){any(rowSums(x) ==5) |any(colSums(x) ==5) })#If there is only one board left and it is finished...if (length(board_status) ==1&sum(board_status) ==1) {#Then return our answer last_winner <- bingo_boards[,,1] unmarked <- last_winner[!current_results[,,1]]return(sum(unmarked) * number)#Otherwise, remove all winning boards and call the function again... } else {#Filter only losing boards... losing_boards <- bingo_boards[,,!board_status, drop =FALSE] losing_results <- current_results[,,!board_status, drop =FALSE]#Recall function with next numberRecall(bingo_board = losing_boards, numbers = numbers, i = i +1, current_results = losing_results) }}
#Let's get our result!play_bingo(bingo_boards = board_array, numbers = number_calls_int, i =1, current_results = result_array)
[1] 26878
By using a 3D array we were able to easily work with this data. And there’s no reason to stop at 3 dimensions, you can build larger multi-dimensional arrays that can be indexed and searched through in just the same way.
#Go crazy and build a 4D array with 2 sets of boards!fourD_array <-array(c(allboards, allboards), dim =c(5, 5, length(allboards)/25, 2))#Return 1st board being played in the 2nd gamefourD_array[,,1,2]