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