Today, we are going to introduce and compare some concepts of Functional Programming like “Reduce”, “Recursion” and “Memoization” taking as an example the factorial:
\(n!=n \times (n-1)!=n \times (n-1) \times (n-2) \times … \times1\)
Iteration
Write a function that calculates the factorial of an integer \(n\) using a for loop.
factorial_iteration <- function(x){ y <- 1 if(x == 0) return(1) for (i in 1:x){ y <- y*i } return(y) } # test the outcome of factorial(6) factorial_iteration(6)
[1] 720
Reduce
Write a function which calculates the factorial of an integer \(n\) using the reduce function of purrr package.
library(purrr) factorial_reduce <- function(x){ if(x == 0) return(1) else{ reduce(as.numeric(c(1:x)), function(x, y){ x * y }) } } # test the outcome of factorial(6) factorial_reduce(6)
[1] 720
Recursion
Write a function which calculates the factorial of an integer \(n\) using the recursive approach.
factorial_recursive<- function(x) { if(x==0) {return(1)} else if(x==1) {return(1)} else {return(x * factorial_recursive(x-1))} } # test the outcome of factorial(6) factorial_recursive(6)
[1] 720
Recursion with Memoization
We’ll create a very simple table which is just a vector containing 1 and then 100 NAs. First, the factorial_mem () function will check if the number is in the table, and if it is then it is returned. Otherwise, the factorial number is recursively calculated and stored in the table. Notice that we’re using the complex assignment operator <<- in order to modify the table outside the scope of the function.
mem_tbl <- c(1, rep(NA, 100)) factorial_mem <- function(x){ if (x == 0) return(1) if(!is.na(mem_tbl[x])) return(mem_tbl[x]) else{ mem_tbl[x] <<- x * factorial_mem(x-1) return(mem_tbl[x]) } } # test the outcome of factorial(6) factorial_mem(6)
[1] 720
Comparison of the Performance in terms of Speed
We will use the library microbenchmark in order to compare the performance of these 4 functions. We will consider a relatively big number, which is the factorial 100.
library(microbenchmark) Comparison<- microbenchmark( iteration=factorial_iteration(100), reduce=factorial_reduce(100), recursion=factorial_recursive(100), memoization=factorial_mem(100) ) Comparison
Unit: nanoseconds expr min lq mean median uq max neval cld iteration 4500 5300 104515 9500 10500 9605300 100 a reduce 218600 228450 433508 445050 458750 6820500 100 b recursion 59500 61650 86308 69750 108300 145400 100 a memoization 700 1300 2231 1600 3000 6100 100 a
library(ggplot2) autoplot(Comparison)
Discussion
It is obvious that the Memoization is much faster compared to the other approaches. Then, the more efficient appears to be the Iteration. Finally, the Reduce seems to be the least efficient in terms of speed.