After reading this puzzle, I was excited and concerned to see “spreadsheet”:
- Excited: R is good at rectangular numeric data, and I work with a lot of spreadsheets
- Concerned: Will I be starting with a single long string? How do I get that into rectangular form?
After considering trying to chop up one long string, I copy-pasted my input to a text file and read.delim()
worked on the first try.
Part 1 was simple, Part 2 was tricky. The idea of row-wise iteration got in my head and I spent a lot of time with apply
and lapply
… I still hold a grudge against them from battles as a new R user, but having (mostly) bent them to my will I went to them here. Maybe I should spend more time with the purrr
package.
Part 1
Straightforward:
library(dplyr) library(testthat) ex <- data.frame( a = c(5, 7, 2), b = c(1,5,4), c = c(9,3,6), d = c(5, NA, 8) ) checksum <- function(x){ row_max <- apply(x, 1, max, na.rm = TRUE) row_min <- apply(x, 1, min, na.rm = TRUE) sum(row_max - row_min) } expect_equal(checksum(ex), 18) # works! my_input <- read.delim("02_1_dat.txt", header = FALSE) checksum(my_input)
Part 2
Got the idea quickly, but executing took a bit longer. I decided right away that I would:
- Work on a function that tackled a row (as a vector) at a time
- The function would divide every value in the vector by every other value in the vector
- It would then pick out the integer that wasn’t 1, as well as its reciprocal, and divide them
- Then run this function row-wise on the input
Actually building up that function took ~20 minutes. I felt confident the approach would work so didn’t stop to consider anything else… I’m excited to see other solutions after I publish this, as maybe there’s something elegant I missed.
Along the way I learned: is.integer
doesn’t do what I thought it would. Come on, base R.
I don’t work with matrices much (not a mathematician, don’t have big data) and was a somewhat vexed by the matrix -> data.frame conversion.
# Will apply something row-wise through the data.frame # Need to get the dividend and divisor # example data for part 2: ex2 <- data.frame( a = c(5, 9, 3), b = c(9,4,8), c = c(2,7,6), d = c(8, 3, 5) ) # start with the top row to play with x <- ex2[1, ] # I'll create an x by x matrix with every number in a vector divided by itself # I want the numbers corresponding to matrix entries containing the sole non-1 integer and its reciprocal. # Annoying, is.integer() doesn't do what I thought it would, so need to make my own function: https://stackoverflow.com/q/3476782/4470365 is_integer <- function(x) { (x %% 1) == 0 } is_recip_integer <- function(x){ ((1/x) %% 1) == 0 } get_quotient <- function(x){ # create x by x matrix dividing each entry by itself divided <- sapply(x, function(a) a/x) %>% unlist %>% matrix(nrow = sqrt(length(.))) %>% as.data.frame() divided[divided == 1] <- NA # don't want the diagonal values # Get the position where the vector contains an integer bigger_index <- which( lapply(divided, is_integer) %>% lapply(sum, na.rm = TRUE) %>% unlist(.) == 1) # I drafted this function as nested parentheses, # converted to pipes for blogging... it looks weird. # Get the position where the vector contains the reciprocal of an integer smaller_index <- which( lapply(divided, is_recip_integer) %>% lapply(sum, na.rm = TRUE) %>% unlist(.) == 1) # Compute quotient of values at those indices x[bigger_index] / x[smaller_index] } # Test expect_equal( # apply the function rowwise and sum apply(ex2, 1, get_quotient) %>% sum, 9 ) # Get part 2 answer: apply(my_input, 1, get_quotient) %>% sum