2024: Day 02 Red-Nosed Reports

loops
tidyr
NAs
one_star
Published

December 2, 2024

Setup

The original challenge

Input data

Each report has varying number of levels, so I used readLines to import the whole thing in as a data frame, and then used separate_wider_delim to split the character vector out into columns. I don’t know the full length of rows, so i chose a large value and then dropped columns containing no data. Then convert to a numeric matrix.

Toggle the code
library(aochelpers)
input <- readLines("~/GitHub/AdventOfCode/2024/day/2/input") |> data.frame()
head(input)
  readLines....GitHub.AdventOfCode.2024.day.2.input..
1                                      74 76 78 79 76
2                                      38 40 43 44 44
3                                      1 2 4 6 8 9 13
4                                65 68 70 72 75 76 81
5                                   89 91 92 95 93 94
6                                   15 17 16 18 19 17
Toggle the code
input2 <- tidyr::separate_wider_delim(input, 
                                                        cols = everything(), 
                                                        delim = " ",
                                                        names = paste0("rpt", 1:8), 
                                                        too_few = "align_start")

input.num <- apply(input2, 2, as.numeric)
            
# apply(input2, 2, function(x)mean(is.na(x))) # identify blank columns

TLDR; Solutions

Part 1 ⭐

❓ How many reports are safe?

Toggle the code
input.diffmat <- matrix(NA, nrow = NROW(input.num), ncol = NCOL(input.num)-1)

for(i in 1:NROW(input.num)){
    for(j in 2:NCOL(input.num)){
        input.diffmat[i, j-1] <- input.num[i, j]-input.num[i, j-1]
    }
}

flag.size <- input.diffmat ==0 | abs(input.diffmat) > 3
# adjusted here for missing values
unsafe <- rowSums(flag.size, na.rm = TRUE) >0

for(i in 1:NROW(input.diffmat)){
    if(unsafe[i] == FALSE){
        # adjusted for missing values
        unsafe[i] <- length(rle(input.diffmat[i,which(!is.na(input.diffmat[i,]))]> 0)$lengths)>1
    }
}
sum(!unsafe)
[1] 257

Part 2 ⭐⭐

Walkthrough / Explainer

Part 1

First place to look for the chief is the Red-Nosed Reindeer nuclear fusion/fission plant. But they need help with some data crunching.

Example Data

One line per report, one column per level. Example data has 6 reports with 5 levels each.

Toggle the code
exa <- c("7 6 4 2 1", "1 2 7 8 9", "9 7 6 2 1", "1 3 2 4 5", "8 6 4 4 1", "1 3 6 7 9") |>
    lines_to_matrix(split = ' ')
class(exa) <- "numeric"
exa
     [,1] [,2] [,3] [,4] [,5]
[1,]    7    6    4    2    1
[2,]    1    2    7    8    9
[3,]    9    7    6    2    1
[4,]    1    3    2    4    5
[5,]    8    6    4    4    1
[6,]    1    3    6    7    9

The Red-Nosed reactor safety systems can only tolerate levels that are A) either all increasing or all decreasing, or B) Any two adjacent levels differ by at least one and at most three.

okay, so we need a difference between column j+1 and j. Easiest way would probably be to do a loop.

Toggle the code
exa.diffmat <- matrix(NA, nrow = NROW(exa), ncol = NCOL(exa)-1)
for(i in 1:NROW(exa)){
    for(j in 2:NCOL(exa)){
        exa.diffmat[i, j-1] <- exa[i, j]-exa[i, j-1]
    }
}
exa.diffmat
     [,1] [,2] [,3] [,4]
[1,]   -1   -2   -2   -1
[2,]    1    5    1    1
[3,]   -2   -1   -4   -1
[4,]    2   -1    2    1
[5,]   -2   -2    0   -3
[6,]    2    3    1    2

Now to flag the unsafe rows based on size of difference. I want the result to be TRUE/FALSE for later indexing.

Toggle the code
flag.size <- exa.diffmat ==0 | abs(exa.diffmat) > 3
unsafe <- apply(flag.size, 1, sum)|> as.logical()

To flag the sign changes, use rle() Run Length Encoding. If the length of the lengths output is greater than 1, then there was a sign change.

Toggle the code
rle(exa.diffmat[6,]>0)
Run Length Encoding
  lengths: int 4
  values : logi TRUE
Toggle the code
rle(exa.diffmat[6,]>0)$lengths
[1] 4
Toggle the code
length(rle(exa.diffmat[6,]>0)$lengths)>1
[1] FALSE

Only run this on the rows that haven’t already been flagged as unsafe

Toggle the code
for(i in 1:NROW(exa.diffmat)){
    if(unsafe[i] == FALSE){
        unsafe[i] <- length(rle(exa.diffmat[i,]>0)$lengths)>1
    }
}

unsafe
[1] FALSE  TRUE  TRUE  TRUE  TRUE FALSE
Toggle the code
!unsafe
[1]  TRUE FALSE FALSE FALSE FALSE  TRUE

number of safe reports

Toggle the code
sum(!unsafe)
[1] 2

Matches.

Part 2

The same rules apply as before, except if removing a single level from an unsafe report would make it safe, the report instead counts as safe.

I was stumped, so I’m trying a tactic idea from Angel Martinez.

Check between each level for whether or not it was a safe transition. Then count the number of unsafe levels per row.

Toggle the code
angels.if_safe.fun <- function(i, j){    
    diff <- abs(exa[i,j] - exa[i,j-1])
    flip <- (sign(exa[i,j]) != sign(exa[i,j-1]))
        
    status <- dplyr::case_when(diff == 0 ~ "unsafe", 
                                                         diff > 3 ~ "unsafe", 
                                                         flip == TRUE ~ "unsafe",
                                                         .default = "safe")
    return(status)
}
    
angels.if_safe.fun(3, 5)
    
# set an indicator of the number of unsafe levels
is.safe <- rep("safe", NROW(exa))
    
for(i in 1:NROW(exa)){
    J <- length(na.omit(exa[i,])) # number of non-NA entries
    err <- 0
    for(j in 2:J){
        # run the function to check for an unsafe level.
        if(angels.if_safe.fun(i, j) == "unsafe"){
            # if j is unsafe, and not at J, delete it and put j+1 where j is at
            exa[i, j:J-1] <- exa[i, j+1:J]
            exa[i, J] <- NA
            err <- 1
        }
        # re-run safe check function on the same j. If another error then flag as unsafe
        if(angels.if_safe.fun(i, j) == "unsafe" & err==1){
            is.safe[i] <- "unsafe"
        }
    }
}

(safe.if.remove.1 <- n.unsafe<2)

Yea.. this isn’t working b/c when j=J, there is no j+1.

Session info

Toggle
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.4.1 (2024-06-14)
 os       macOS Sonoma 14.6.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Los_Angeles
 date     2024-12-13
 pandoc   3.1.11 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)

─ Packages ───────────────────────────────────────────────────────────────────
 package    * version    date (UTC) lib source
 aochelpers * 0.1.0.9000 2024-12-02 [1] Github (EllaKaye/aochelpers@d4ccd91)

 [1] /Users/rdonatello/Library/R/arm64/4.4/library
 [2] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────