Search code examples
rfunctionif-statementwhile-loopfactorial

How to add Results into a Vector within a Loop in R


I was prompted a question and am ever so close to solving what I need. The question is as follows-

"Write a while loop that computes and stores as a new object, the factorial of any non-negative integer mynum by decrementing mynum by 1 at each repetition of the braced code."

Another factor was that if 0 or 1 was entered, the output would be 1.

The code that I wrote as follows-

factorialcalc <- function(i){
  factorial <- 1
  if(i==0 | i==1){
    factorial <- 1
  } else{
    while(i >= 1){
      factorial <- factorial * i
      i <- i-1
    }
  }
  return (factorial)
}

with inputs-

mynum <- 5
factorialcalc(mynum)

and output-

[1] 120

You may be wondering, "your code works perfect, so what's the issue?" My issue lies in the part of the question that says "computes AND stores."

How can I modify my code to put the answers of factorialcalc into a vector?

Example- I input

mynum <- 5
factorialcalc(mynum)

and

mynum <- 3
factorialcalc(mynum)

and

mynum <- 4
factorialcalc(mynum)

When I call this new vector, I would like to see a vector with all three of their outputs (so almost like I made a vector c(120,6,24))

I'm thinking there's a way to add this vector somewhere in my function or while loop, but I'm not sure where. Also, please note that the answer must contain a loop like in my code.

The answer I would be looking for would have an empty vector say answers <- c(), and every time I were to run the function factorialcalc, during the function, I would add the return results from factorial into this new answers vector.

I've tried putting in something like this-

factorialcalc <- function(i){
  factorial <- 1
  answers <- c()
  answers[i] <- answers[factorial]
  if(i==0 | i==1){
    factorial <- 1
  } else{
    while(i >= 1){
      factorial <- factorial * i
      i <- i-1
    }
  }
  return (factorial)
}

This doesn't seem to work, but I think the idea is there.

Thanks in advance for any help.


Solution

  • Here is a solution that illustrates how to build a vector of results for a factorial function. We use the testthat package to build unit tests for the function.

    Rather than iterating from i down to 1, we iterate from 2 to i, using the j-1 value in the output vector to multiply by the current number in the for() loop.

    We also include error checking at the top of the function. Since R does not have a way to directly validate a numeric vector as an integer / whole number, we compare the absolute value of the input minus its rounded value to machine precision and stop if the comparison fails.

    factorial <- function(i){
         # return a vector of factorial values from 1 to i, where
         # i represents the i-th value in the factorial series 
         
         # first, validate the input 
         if(!is.numeric(i)) stop("i must be a whole number")
         if(!(abs(i - round(i)) < .Machine$double.eps^0.5)) stop("i must be a whole number")
         if (i < 0) stop("i must be a whole number")
    
         # now, process based on value of i 
         if (i %in% c(0,1)) return(1)
         resultVector <- 1
         # if we get here, i is greater than 1
         for(j in 2:i) resultVector <- c(resultVector,resultVector[j-1]*j)
         resultVector
    }
    

    We test the function with a variety of tests, ranging from invalid input handling to confirming the output of the function with known values. We also compare the length of the output vector to the input value of i.

    library(testthat)
    test_that("Factorial function works",{
         expect_error(factorial(-3),"i must be a whole number",ignore.case=TRUE)
         expect_equal(1,factorial(0))
         expect_equal(1,factorial(1))
         expect_equal(c(1,2),factorial(2))
         expect_equal(c(1,2,6),factorial(3))
         expect_equal(c(1,2,6,24),factorial(4))
         expect_equal(c(1,2,6,24,120),factorial(5))
         expect_equal(5,length(factorial(5))) # test that length matches input i
         expect_error(factorial(3.1),"i must be a whole number",ignore.case=TRUE)
         expect_error(factorial("zzz"),"i must be a whole number",ignore.case=TRUE)
         
         
    })
    

    ...and the test results:

    ==> Testing R file using 'testthat'
    
    ✓ |  OK F W S | Context
    ✓ |  10       | factorial
    
    ══ Results ═════════════════════════════════════════════════════════════════════
    Duration: 0.2 s
    
    OK:       10
    Failed:   0
    Warnings: 0
    Skipped:  0
    
    Test complete
    

    Finally, we run the function multiple times to illustrate its output:

    > lapply(1:8,factorial)
    [[1]]
    [1] 1
    
    [[2]]
    [1] 1 2
    
    [[3]]
    [1] 1 2 6
    
    [[4]]
    [1]  1  2  6 24
    
    [[5]]
    [1]   1   2   6  24 120
    
    [[6]]
    [1]   1   2   6  24 120 720
    
    [[7]]
    [1]    1    2    6   24  120  720 5040
    
    [[8]]
    [1]     1     2     6    24   120   720  5040 40320