Search code examples
rfunctionvectorization

Validation function testing a value only if of a given character length


I'm writing a function that will validate certain codes.

Codes are integers that should meet two criteria:

  1. be of n length (for the sake of this example, let's say 2)
  2. more complex checks (irrelevant for this simplified example, but the key aspect is that this will require many lines of code and separate assignments)

To avoid useless calculations, I only want the function to evaluate criterion 2 if the first one is met.

codes <- c(16, 19, 12, 8)

is_valid <- function(code) {
    if (nchar(code) != 2) {
        return(FALSE)
    } else {
        # This part is not relevant
        val1 <- as.integer(substr(code, 1, 1))
        val2 <- as.integer(substr(code, 2, 2))
        sum <- val1 + val2
        validator <- 10 - sum %% 3
        return(validator == 0)       
    } 
}

Issue: this works well if I pass a single element (e.g. is_valid(16), however if I try passing a vector is_valid(codes), I get this error:

Error in if (nchar(code) != 2) { : the condition has length > 1

I understand this is because if is not vectorised. I tried replacing this with ifelse() but it doesn't really work in this circumstance because the second criteria is a multi-line bit of code.

I have a feeling there's a simple programming solution to this.


Solution

  • The problem lies in the logical test if (nchar(code) != 2). In R, the logical test inside an if statement must return a single logical TRUE or FALSE. In your case, it will return a logical vector:

    codes <- c(16, 19, 12, 8)
    nchar(codes) != 2
    #> [1] FALSE FALSE FALSE  TRUE
    

    The if statement will use the first element of this vector for flow control (while also emitting a warning), so your function will process the entire vector codes via the else statement.

    My guess is that you want a vectorized output. In this case we can use ifelse, or even simply check the two logical tests independently and return their results combined by a logical AND:

    is_valid <- function(code) {
        
        test1 <- nchar(code) == 2
    
        val1 <- as.integer(substr(code, 1, 1))
        val2 <- as.integer(substr(code, 2, 2))
        sum <- val1 + val2
        validator <- (10 - sum) %% 3
        test2 <- validator == 0 & !is.na(validator)
        
        return(test1 & test2)
    
    }
    
    is_valid(codes)
    #> [1]  TRUE  TRUE FALSE FALSE
    

    Created on 2023-02-07 with reprex v2.0.2