---
title: "05 - Logical Functions"
output:
  litedown::html_format:
    options:
      toc: true
      number_sections: true
vignette: >
  %\VignetteIndexEntry{05 - Logical Functions}
  %\VignetteEngine{litedown::vignette}
  %\VignetteEncoding{UTF-8}
editor:
  markdown:
    wrap: sentence
bibliography: ["references.bib"]
---

## Notes

* These examples were adapted from @vaughan24.
* These functions ignore `NA` values for now. Adjustments for handling `NA` values are covered in a separate vignette.
* R already provides efficient versions of the functions covered here. This is just to illustrate how to use C++ code.
* The benchmarks show that some of these C++ implementations are slower than base R.

## Is any value in a vector 'true'?

Base R's `any()` function returns `TRUE` if there is at least one `TRUE` element in a vector, and `FALSE` otherwise.
Below is one possible C++ implementation:

```cpp
[[cpp4r::register]]
bool any_cpp(logicals x) {
  int n = x.size();
  
  for (int i = 0; i < n; ++i) {
    if (x[i]) {
      return true;
    }
  }
  return false;
}
```

Its R equivalent would be:

```r
any_r <- function(x) {
  n <- length(x)
  
  for (i in 1:n) {
    if (x[i]) {
      return(TRUE)
    }
  }
  FALSE
}
```

Add and document the functions, update the package as in the previous vignettes, and
then compare the functions speed with:

```r
# install.packages("microbenchmark")
library(microbenchmark)
library(mypkg)

set.seed(123) # for reproducibility
x <- rpois(1e6, lambda = 2) # 1,000,000 elements
y <- ifelse(x > 2, TRUE, FALSE)

any(y)
any_cpp(y)
any_r(y)

microbenchmark(
  any(y),
  any_cpp(y),
  any_r(y)
)
```

```r
Unit: nanoseconds
       expr min  lq mean median  uq   max neval cld
     any(y) 212 232  294    245 257  4320   100   a
 any_cpp(y) 810 833 1090    859 887 22900   100   a
   any_r(y) 746 822 1510    867 912 63300   100   a
```

## Which elements in a vector are 'true'?

Base R's `which()` function returns the indices of the `TRUE` elements in a vector. Here is a possible C++ implementation:

```cpp
[[cpp4r::register]]
integers which_cpp(logicals x) {
  int n = x.size();
  writable::integers res;
  int j = 0;

  for (int i = 0; i < n; ++i) {
    if (x[i]) {
      ++j;
      res.push_back(i + 1);
    }
  }

  if (j == 0) {
    return integers(0);
  } else {
    return res;
  }
}
```

Its R equivalent would be:

```r
which_r <- function(x) {
  n <- length(x)
  res <- c()
  j <- 0

  for (i in 1:n) {
    if (x[i]) {
      res <- c(res, i)
      j <- j + 1
    }
  }

  if (j == 0) {
    return(0)
  } else {
    return(res)
  }
}
```

To test the functions, you can run the following benchmark code in the R console:

```r
which(y[1:100])
which_cpp(y[1:100])
which_r(y[1:100])

microbenchmark(
  which(y[1:1000]),
  which_cpp(y[1:1000]),
  which_r(y[1:1000])
)
```

```r
Unit: microseconds
                 expr    min     lq   mean median     uq   max neval cld
     which(y[1:1000])   6.30   7.08   8.27   7.75   8.86  20.2   100  a 
 which_cpp(y[1:1000])   8.52   9.93  11.90  10.90  12.00  72.1   100  a 
   which_r(y[1:1000]) 302.00 323.00 348.00 345.00 361.00 439.0   100  b
```

## Are all values in a vector 'true'?

Base R's `all()` function checks if all elements in a vector are `TRUE`. Here is a possible C++ implementation that loops over the vector:

```cpp
[[cpp4r::register]]
bool all_cpp_1(logicals x) {
  int n = x.size();
  for (int i = 0; i < n; ++i) {
    if (!x[i]) {
      return false;
    }
  }
  return true;
}
```

More concise C++ alternatives are:

```cpp
[[cpp4r::register]]
bool all_cpp_2(logicals x) {
  for (int i = 0; i < x.size(); ++i) {
    if (!x[i]) {
      return false;
    }
  }
  return true;
}

[[cpp4r::register]] bool
all_cpp_3(logicals x) {
  for (bool i : x) {
    if (!i) {
      return false;
    }
  }
  return true;
}

[[cpp4r::register]] bool
all_cpp_4(logicals x) {
  return std::all_of(x.begin(), x.end(), [](bool x) { return x; });
}
```

To test the functions, you can run the following tests and benchmark code in the R console:

```r
set.seed(123) # for reproducibility
x <- rpois(1e6, lambda = 2) # 1,000,000 elements

all(x > 2)
all_cpp_1(x > 2)
all_cpp_2(x > 2)
all_cpp_3(x > 2)
all_cpp_4(x > 2)

# also test the TRUE-only case
all(x >= 0)
all_cpp_1(x >= 0)
all_cpp_2(x >= 0)
all_cpp_3(x >= 0)
all_cpp_4(x >= 0)
```

```r
microbenchmark(
  all(x > 2),
  all_cpp_1(x > 2),
  all_cpp_2(x > 2),
  all_cpp_3(x > 2),
  all_cpp_4(x > 2)
)
```

```r
Unit: milliseconds
             expr  min   lq mean median   uq  max neval cld
       all(x > 2) 2.20 3.08 3.29   3.18 3.33 5.70   100   a
 all_cpp_1(x > 2) 2.24 3.14 3.38   3.23 3.35 5.57   100   a
 all_cpp_2(x > 2) 2.33 3.09 3.28   3.20 3.35 5.79   100   a
 all_cpp_3(x > 2) 2.22 3.13 3.41   3.25 3.39 5.91   100   a
 all_cpp_4(x > 2) 2.33 3.13 3.37   3.23 3.40 5.72   100   a
```

# References
