good-code ex 4: reducing cyclomatic complexity, leap years

Here’s a function that accepts a year (as a number) and returns TRUE or FALSE depending upon whether or not that year is a leap year. The logic is that leap years occur on years divisible by 4, excluding centuries, but including every fourth century. Recall that the %% operator means remainder after division.

is_leap_year <- function(year)
{
  if(year %% 4 == 0)
  {
    if(year %% 100 == 0)
    {
      if(year %% 400 == 0)
      {
        TRUE
      } else
      {
        FALSE
      }
    } else
    {
      TRUE    
    }
  } else
  {
    FALSE
  }
}

The nested if statements make the code hard to understand, and stop the code being vectorized (it only accepts one input number at a time). Rewrite the function to make it easier to read. For bonus points, vectorize it.

It’s best to completely remove the if statements and use R’s built-in vectorization of its operators.

is_leap_year2 <- function(year)
{
  yn <- year %% 4 == 0
  yn[year %% 100 == 0] <- FALSE
  yn[year %% 400 == 0] <- TRUE
  setNames(yn, year)
}

A slightly fancier variation is to use assertive’s is_divisible_by function.

library(assertive)
## assertive has some important changes.  Read ?changes for details.
is_leap_year3 <- function(year)
{
  yn <- is_divisible_by(year, 4)
  yn[is_divisible_by(year, 100)] <- FALSE
  yn[is_divisible_by(year, 400)] <- TRUE
  setNames(yn, year)
}

Usage is as:

is_leap_year2(c(2015, 2016, 2100, 3000))
##  2015  2016  2100  3000 
## FALSE  TRUE FALSE FALSE
is_leap_year3(c(2015, 2016, 2100, 3000))
## There were 3 failures:
##   Position Value       Cause
## 1        1  2015 indivisible
## 2        3  2100            
## 3        4  3000