40  40. while loops

A “while loop” in R is one of several ways to get R to do something over and over again (we’ll discuss other ways later). A relatively straight forward example of code that could be written with a “while loop” is the factorial function (see below). Now, since R has a built-in version of the factorial function, you don’t actually have to write the factorial function yourself. However, it will be instructive to reproduce this function with our own code that uses a while loop - which we do below. In order to understand the example code below, let’s first understand what a factorial is.

40.1 What is the “factorial” of a number?

In math, the factorial of a number is the product of that number and all the positive whole numbers below it. For example:
The factorial of 5 is: 5 × 4 × 3 × 2 × 1 = 120
Mathematicians write factorials using an exclamation point.
For example a mathematician would write 5! to mean 5 × 4 × 3 × 2 × 1 = 120
However, 5! is NOT valid R code. Rather, R has a builtin function, factorial(SOME_NUMBER) that calculates factorials. For example:

factorial(3)  # same as 3*2*1 which is 6
[1] 6
factorial(5)  # same as 5*4*3*2*1 which is 120
[1] 120

40.1.1 factorial is not defined for negative numbers

The concept of a factorial is not defined for negative numbers. When given a negative number, R’s factorial function returns NaN (i.e. R’s special value meaning “not a number”)

factorial(-1)  # NaN
[1] NaN

40.1.2 factorial(0) and factorial(1) are both 1

The factorial of 0 and the factorial of 1 are both defined to be 1.

factorial(0)  # defined by mathematicians to be 1
[1] 1
factorial(1)  # defined by mathematicians to be 1
[1] 1

To learn more about factorials, see this page: https://www.mathsisfun.com/numbers/factorial.html

40.2 Let’s rewrite the factorial function - we’ll use a while loop.

Let’s write a function called myFactorial that reproduces the results of R’s builtin factorial function that was shown above.

The code introduces a new concept - the “while loop”. We’ll explain all of the code below, but first let’s see the full definition of the function and make sure that it works:

myFactorial <- function(num){
  if(!is.numeric(num) || trunc(num) != num || length(num) != 1) {
    stop("num must be a single positive whole number")
  }
  
  if (num < 0) {
    return(NaN)
  }

  answer = 1
  
  while(num > 1) {
    answer <- answer * num
    num <- num - 1
  }
  
  return(answer)
}

Some examples of using the myFactorial function:

myFactorial(0)   # factorial of 0 is defined to be 1
[1] 1
myFactorial(1)   # factorial of 1 is defined to be 1
[1] 1
myFactorial(3)   # 6, ie. 3 * 2 * 1
[1] 6
myFactorial(5)   # 120 i.e. 5*4*3*2*1
[1] 120
myFactorial(10)  # 3628800 - i.e. 10*9*8*7*6*5*4*3*2*1
[1] 3628800
myFactorial(100) # 9.332622e+157   -  this is a VERY VERY large number (see section on "scientific notation")
[1] 9.332622e+157
myFactorial(c(1,2,3,4))  # ERROR - only one number in our version of myFactorial
Error in !is.numeric(num) || trunc(num) != num: 'length = 4' in coercion to 'logical(1)'
myFactorial(-1)          # NaN
[1] NaN

40.2.1 Explanation of the code

Let’s go through the myFactorial function in detail. Portions of the code are highlighted with numbers and explained below.

myFactorial <- function(num){
1  if(!is.numeric(num) || trunc(num) != num || length(num) != 1) {
    stop("num must be a single positive whole number")
  }
  
2  if (num < 0) {
    return(NaN)
  }

3  answer = 1
  
4  while(num > 1) {
    answer <- answer * num
    num <- num - 1
  }
  
5  return(answer)
}
1
make sure that num is just one whole number
2
make sure that num is not negative
3
setup the answer variable - this is necessary to do before the while loop starts
4
the while loop (explinaed in detail below)
5
return the answer

40.2.2 How the while loop works

A while loop looks similar to an if, except that a while loop starts with the word “while” and an if starts with the word “if”.

  • Similar to an “if”, a “while” has a logical condition in parentheses followed by some code in {curly braces}. (The code in the {curly braces} is called the “body” of the while loop.)

  • Similar to an “if”, the condition must evaluate to TRUE or FALSE.

  • Similar to an “if” - when the condition for the while is TRUE - the code in the {curly braces} is executed and when the condition is FALSE, the code in the {curly braces} is NOT executed.

  • What makes a while different from an if is the following:

    o if : when the code in the {curly braces} (i.e. the “body” of the if) finishes, the execution of the code continues with the first line after the body of the “if”

    o while : when the code in the {curly braces} (i.e. the “body” of the while) finishes, the entire “while” is repeated - i.e. the condition is checked and if it is still TRUE - the body of the while is done again. This keeps happening as long as the condition is TRUE (i.e. “while” the condition is TRUE). If/when the condition eventually becomes FALSE, the execution of the code continues with the first line after the body of the “while”.

40.3 Terminology: “iteration” , “iterate”

As a while loop executes, the code in the “body” of the while loop may be executed (i.e. run) several times. Each time the code of the body is executed is known as a single “iteration” of the while loop. In general we say that the while loop “iterates over” the code in the body of the loop.

40.4 Using the debugger to understand the code

It is often helpful to use R’s debugger to help understand the code for a loop. Information about how to use R’s debugger is above in the debugger section.

As a quick refresher - to use the debugger, you should first run the code shown above that defines the function myFactorial. Then type the command debugonce(myFactorial) and finally call the function, e.g. myFactorial(5). At that point the debugger should start. To see how the code works, you can repeatedly press the “next” button (or type n). Every time you do so, the code will advance one line. You can use this technique to see what “path” the code takes through ifs and loops. You should pay attention to how the values of the variables in the “Environment” pane change as the code is executed. Tracing through code in this way is one of the best ways to understand how the code works.

40.5 Things to keep in mind when writing while loops

When writing while loops, keep the following in mind:

  1. The condition, which appears in the parentheses, should depend on at least one variable.

    In this example, the condition is (num > 1). This particular condition contains one variable, i.e. num. It is very possible for a condition to be more complicated than this and contain more than one variable.

  2. The condition must be true for the code in the body to be executed. The condition must be false for the code in the body to NOT be executed.

  3. The value of the variable(s) in the condition must be setup in the code that appears BEFORE the while.

    In this example, the value of num was passed into the function, therefore its value was set before the while loop started.

  4. The code inside the {curly braces} is known as the “body” of the loop.

  5. The code in the body should eventually cause the condition to become false. The most common way to do that is for the body to change the value of a variable in the condition in some way.

    In this example, the line : num <- num - 1 changes the value of num by subtracting one from it. If this happens enough times, the value of num will eventually become 1, thus making the condition FALSE.

40.6 Infinite loop - press the STOP button (and/or ESC key)

#########################################################################
#
# *** IMPORTANT ***
#
# Writing code with loops can be tricky, especially if you're new at it.
# Watch out for potential coding errors ... be CAREFUL!!!
#########################################################################

#....................................................................
# Infinite loops
#....................................................................
# IMPORTANT ... When writing code with while loops it is possible to 
# introduce errors in which the loop will "never end". 
# This is called an "infinite loop". If your code enters an "infinite loop",
# RStudio will become unresponsive. If you don't know what to do it can be
# very frustrating!!  When this happens, either: 
#
#   - a little red "stop sign" button usually appears above the console
#     window pane in RStudio. Pressing the "stop sign" will stop the function
#     from running and let you once again use RStudio normally.
#
#   - If you don't see the stop sign, try pressing the ESC key. This can 
#     happen if you while loop is running with a call to readline() or a
#     similar function inside the loop. 
#
# In the following code, I purposely introduced an "infinite loop". You 
# will not be able to move on until you press the "stop sign" button that
# is above the console window pane in RStudio.
#....................................................................

# The following version of myFactorial has a bug that 
# causes an infinite loop.

badMyFactorial <- function(num){
 if (num <0 || !is.numeric(num) || trunc(num) != num || length(num) != 1) {
  stop("num must be a single positive whole number")
 }
 
 answer = 1
 
 # A while loop is similar to an if in that if the condition is true
 # then the code in the body runs. If the condition is false the body does not run
 # and the next line of the program after the body runs.
 
 # For every while loop you must keep in mind the following
 # 1. The condition must depend on SOME variable
 # 2. The body must eventually cause the condition to become false.
 #    The most common way to do that is for the body to change
 #    the value of a variable in the condition in some way.
 
 while(num > 1) {
  answer <- answer * num
  #   num <- num - 1       (I purposely "commented out" this line to cause an infinite loop)
 }
 
 return(answer)
}

# The following calls actually work correctly.
# This is becuase the while loop never even starts since the condition, (num > 1), 
# is FALSE even before the first "iteration" of the while loop.


badMyFactorial(0)
[1] 1
badMyFactorial(1)
[1] 1
# The following calls to badMyFactorial cause an infinite loop.

# badMyFactorial(2)
# badMyFactorial(5)

# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
# inifinite loop - press the "stop sign button" (above the console window pane)
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
badFunction = function (num){

  # This is an infinite loop  
  while (TRUE) {
    cat ('You are in an "infinite loop".\n')
    cat('Press the "stop sign" button (above the console window pane) to ',
        'stop the infinite loop\n\n')
    
    Sys.sleep(0.9)  # this causes R to "go to sleep" for 0.9 seconds
  }
}

# badFunction(1)  # This will result in an "infinite loop". Press the "stop button".


# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
# Another inifinite loop - there is no stop sign button.
#
# If you enter an infinite loop while the computer is waiting for the user
# to type something, you will NOT see a "stop sign button". Instead to 
# get out of the loop
# 
#   - click on the console window pane
#   - then click on the ESC key
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
anotherBadFunction = function() {
  
  # This is an infinite loop. You will NOT see a stop sign button.
  # Instead press ESC to get out of the loop.
  while(TRUE){
    x = readline("What's your name? (to stop looping, click console window, then press ESC)")    
  }

}


# The following will cause another infinite loop
# Click console window then press ESC key to stop the loop and get back the prompt.
#
#
# anotherBadFunction() # another infinite loop -

40.7 Practice with finding errors in loops

# The following is the same exact code as above, without all the 
# comments. There are many different subtle errors that can pop up
# when writing while loops. Think about what would happen in each of the
# following situations ..
#
# What would happen in each of the situations listed below?
# To find out, change the code and try it.
#
# For each of the following questions, try to figure out what will happen
# before actually running the code. Then change the code and run it.
# To help you figure out what will happen, keep track of the values of all
# the variables and arguments on a piece of paper.
# Then, every time you "run a line of code in your head" keep track of any
# changes to the variables on the piece of paper.
#
# You can also use the debugger ...
#
# What would happen if ...
#    1. ... instead of "answer=1"     the programmer typed "answer=0" ?
#    2. ... instead of "while(num>1)" the programmer typed "while(num<1)" ?
#    3. ... instead of "num<-num+1"   the programmer typed "num<-num-1"
#    4. ... the programmer forgot to type the line "num<-num-1" and just left it out?
#    5. ... the programmer wrote the lines "answer<-answer*num" and "num<-num-1"
#           in the opposite order, i.e. "num<-num-1" and then "answer<-answer*num"

myFactorial <- function(num){
  if (num <0 || !is.numeric(num) || trunc(num) != num || length(num) != 1) {
    stop("num must be a single positive whole number")
  }
  
  answer = 1
  
  while(num > 1) {  
    answer <- answer * num
    num <- num - 1
  }
  
  return(answer)
}

myFactorial(4)
[1] 24
#debugonce(myFactorial)

myFactorial(4)  # The result SHOULD BE 24
[1] 24
myFactorial(100)
[1] 9.332622e+157

40.8 Another example: is.prime(num)

#--------------------------------------
# Another example ...
#--------------------------------------

# The following function 
# returns TRUE if num is a prime number and
# returns FALSE is num is not a prime number
#
# A prime number is a whole number (2 or more) that is 
# divisible by only 1 and itself.
#
# Technically 1 is NOT a prime number
# https://blogs.scientificamerican.com/roots-of-unity/why-isnt-1-a-prime-number/
 

is.prime <- function( num ) {

  if (num < 2){
    return(FALSE)
  }
  
  divisor <- 2
  
  # if at any point you find that num is divided
  # evenly by some divisor, return FALSE
  
  while ( divisor < num ) {
    if (num %% divisor == 0){
      return(FALSE)   
    }
    divisor <- divisor + 1    
  }
  
  return(TRUE)
}

is.prime(7) # TRUE
[1] TRUE
is.prime(35) # FALSE
[1] FALSE
is.prime(37) # TRUE
[1] TRUE
is.prime(77) # FALSE
[1] FALSE
## Making the code "more efficient"
#----------------------------------------------------
# Making the code "more efficient"
#----------------------------------------------------

# When the number gets large the while loop will need to "loop" many times.
# This can take some time even on a computer.

is.prime(181) # TRUE
[1] TRUE
#is.prime(15485867)   # TRUE  (takes a few seconds to run)

#is.prime(236887699)  # TRUE  (takes some time to run) - press "stop button" to cancel

is.prime(236887695)  # FALSE - very fast ... why?
[1] FALSE
#---------------------------------------------------------------------------
# Making a program more "efficient"
#---------------------------------------------------------------------------
# Do you really need to check all of the divisors from 2 through num-1 ?
#
# Obvious improvements:
#   - if a num is even you know that result is FALSE
#   - if num ends in 5 or 0 you know it is divisible by 5 so result is FALSE
#
# Non-obvious improvment:
#   - you only need to check the divisors from 2 through the sqrt(num) ... not through num
#     This speeds up the code A LOT.
#
# "Computer science" classes focus a lot on how to improve the "efficiency" of
# programs. We will NOT focus on efficiency. However, you should be familiar
# with the general issue.
#---------------------------------------------------------------------------

# With the knowledge of the above "non-obvious improvement"
# let's write a "more efficient" version of the function. 
# The following version also works but is "faster", i.e. it doesn't need to 
# check as many numbers. 
# 
# The code is the same as the previous version except for the 
# line below that says "#This line changed"

better.is.prime <- function( num ) {
  
  if (num < 2){
    return(FALSE)
  }
  
  divisor <- 2
  
  while ( divisor <= sqrt(num) ) {     # This line changed
    
    if (num %% divisor == 0){
      return(FALSE)   
    }
    divisor <- divisor + 1
  }
  
  return(TRUE)
}

better.is.prime(181) # TRUE
[1] TRUE
better.is.prime(15485867)   # TRUE  - returns right away
[1] TRUE
better.is.prime(236887699)  # TRUE  - returns right away
[1] TRUE

40.9 More practice finding errors in loops

# The following is the same function again, this time without the comments.
#
# For each of the following questions, try to figure out what will happen
# before actually running the code. Then change the code and run it.
# To help you figure out what will happen, keep track of the values of all the variables
# and arguments on a piece of paper. Every time you "run a line of code in
# your head" keep track of any changes to the variables on the piece
# of paper. You can also use the debugger ...
#
# What would happen if ...
# 1. ... instead of "divisor <- 2"        the programmer typed "divisor <- 1" ?
# 2. ... instead of "while(divisor<num)"  the programmer typed "while(divisor>num)" ?
# 3. ... instead of "while(divisor<num)"  the programmer typed "while(divisor<=num)" ?
# 4. ... the line   "divisor<-divisor+1"  was mistakenly left out?
# 5. ... the line   "divisor<-divisor+1"  was inside the body of the if?
# 6. ... the line   "divisor<-divisor+1"  was before the if instead of after the if?
# 7. ... the line   "divisor<-divisor+1"  was before the if instead of after the if ...
#        and instead of "divisor <- 2"    the programmer typed "divisor <- 1" ?
# 8. ... the programmer forgot to type the last line "return(TRUE)".
# 9. ... instead of "while(divisor<num)"  the programmer typed "while(divisor<num/2)" ?
# 10. ... instead of "while(divisor<num)"  the programmer typed "while(divisor<sqrt(num))" ?

is.prime <- function( num ) {
  if (num < 2){
    return(FALSE)
  }

  divisor <- 2
  while ( divisor < num ) {       
    if (num %% divisor == 0){
      return(FALSE)   
    }
    divisor <- divisor + 1
  }
  return(TRUE)
}

is.prime(35)
[1] FALSE
is.prime(37)
[1] TRUE

40.10 There are MANY ways to write the same function

#----------------------------------------------------
# Another way to write the same function.
#
# This version has a single return statement at the end of the function.
# Some people argue that this style is "cleaner" and
# easier to understand when reading the code.
#----------------------------------------------------

is.prime2 <- function( num ) {
  answer <- TRUE   # assume answer is TRUE unless we find out otherwise
  
  if (num < 2){
    answer <- FALSE
    
  } else {
  
    divisor <- 2
    while ( divisor < num ) {
      if (num %% divisor == 0){
        answer <- FALSE
      }
      divisor <- divisor + 1
    }
  }
  
  return(answer)
}

is.prime2(35)  # FALSE
[1] FALSE
is.prime2(37)  # TRUE
[1] TRUE
# check to make sure that both versions return the same values

#all(sapply(1:100,is.prime) == sapply(1:100,is.prime2))   # TRUE

40.11 another example - divisors(num)

#--------------------------------------------------
# Write a function to find all divisors of a number
# (assume that num is a positive whole number)
#--------------------------------------------------

divisors <- function(num){
  if (!is.numeric(num) || trunc(num)!=num || num<1 || length(num)!=1){
    stop("num must be a single positive whole number")
  }
  
  # This is the variable we will return at the end of the function.
  answer <- 1  # 1 is a divisor of all positive whole numbers
  
  divisor <- 2              
  while(divisor <= num){
    if (num %% divisor == 0){
      answer <- c(answer, divisor)  # add another number to the answer
    }
    divisor <- divisor + 1
  }
  
  return(answer)
}

#debugonce(divisors)
divisors(12)
[1]  1  2  3  4  6 12
divisors(15)
[1]  1  3  5 15
divisors(36)
[1]  1  2  3  4  6  9 12 18 36
divisors(100)
[1]   1   2   4   5  10  20  25  50 100
divisors(101)  # this is prime, only divisors are 1 and 101
[1]   1 101
#divisors(15485863)  # This will take a few seconds.
#divisors(67867979)  # This will take a few seconds.
#divisors(67867970)  # This will take a few seconds.

40.12 more efficient version

#-------------------------------------
# a more efficient version
#-------------------------------------

divisors.faster <- function(num){
  if (!is.numeric(num) || trunc(num)!=num || num<1 || length(num)!=1){
    stop("num must be a single positive whole number")
  }
  
  answer <- c(1,num)                    # changed this line
  
  divisor <- 2
  while(divisor <= sqrt(num)){          # changed this line (why?)
    if (num %% divisor == 0){
      answer <- c(answer, divisor)
      answer <- c(answer, num/divisor)  # added this line  (why?)
    }
    divisor <- divisor + 1
  }
  
  answer <- sort(unique(answer))        # added this line  (why?)
  return(answer)
}

#debugonce(divisors.faster)
divisors.faster(36)
[1]  1  2  3  4  6  9 12 18 36
divisors.faster(12)
[1]  1  2  3  4  6 12
divisors.faster(10)
[1]  1  2  5 10
divisors.faster(97)
[1]  1 97
divisors.faster(100)
[1]   1   2   4   5  10  20  25  50 100
divisors.faster(15485863)  # much faster now!!!
[1]        1 15485863
divisors.faster(15485864)  # much faster now!!!
 [1]        1        2        4        8       31       41       62       82
 [9]      124      164      248      328     1271     1523     2542     3046
[17]     5084     6092    10168    12184    47213    62443    94426   124886
[25]   188852   249772   377704   499544  1935733  3871466  7742932 15485864
divisors.faster(67867979)  # much faster now!!!
[1]        1 67867979
divisors.faster(67867970)  # much faster now!!!
[1]        1        2        5       10  6786797 13573594 33933985 67867970
# check to see that both functions are equivalent

#all ( divisors.faster(15485864) == divisors(15485864) )  # TRUE if all nums are the same

40.13 More practice - mysum(NUMS)      blastoff(SECONDS)

###############################################################################
# The next two "QUESTIONS" (i.e. to write the "mysum" and "blastoff" functions)
# do not present any new concepts. They are just additional examples. 
###############################################################################


#----------------------------------------------------------
# QUESTION
# Write a function that simulates the sum function
#
#    mysum = function( nums )
#
# nums is expected to be a numeric vector
# mysum should return the sum of all the numbers in nums
# DO NOT USE THE SUM FUNCTION
#----------------------------------------------------------

mysum = function( nums ){

  theSum = nums[1]
  position = 2
  
  while(position <= length(nums)){
    theSum = theSum + nums[position]
    position = position + 1
  }
    
  theSum  # return the answer
}

mysum(c(10,20,5)) # 35
[1] 35
mysum(10) # 10
[1] 10
# Note that the following returns NA, which makes sense because a sum
# is not applicable (i.e. Not Available) if there are no numbers specified.
mysum( numeric(0) ) 
[1] NA
# You should use the debugger to understand why we get NA. 
#
# debugonce(mysum)
mysum( numeric(0) ) 
[1] NA
#-------------------------------------------------------------------
# The following is a SLIGHTLY different version of the function.
# In this version we returned a sum of 0 when the numeric vector
# is an empty vector. This is also a reasonable answer. 
#-------------------------------------------------------------------

mysum = function( nums ){
  
  theSum = 0                             # this line changed
  position = 1                           # this line changed   
  
  while(position <= length(nums)){
    theSum = theSum + nums[position]
    position = position + 1
  }
  
  theSum  # return the answer
}

mysum(c(10,20,5))    # 35
[1] 35
mysum(character(0))  # 0
[1] 0
#---------------------------------------------------------
# QUESTION
#
# Write a function
#
#    countdown = function(from)
#
# that counts down as shown below. There should be 1 second
# pause between each line of output HINT: use Sys.sleep(1)
# 
# > countdown(5)
# T minus 5 seconds
# T minus 4 seconds
# T minus 3 seconds
# T minus 2 seconds
# T minus 1 second
# BLASTOFF!!!
#
# > countdown(3)
# T minus 3 seconds
# T minus 2 seconds
# T minus 1 second
# BLASTOFF!!!
#
# > countdown(0)
# BLASTOFF!!!
#-----------------------------------------------------------

###########.
# ANSWER
###########.
countdown = function(from){
  
  while(from > 0){
    
    if(from == 1){
      cat("T minus", from, "second\n")
    } else {
      cat("T minus", from, "seconds\n")
    }
    
    Sys.sleep(1)
    from = from - 1
    
  }
  
  cat("BLASTOFF!!!")
  
}

countdown(5)
T minus 5 seconds
T minus 4 seconds
T minus 3 seconds
T minus 2 seconds
T minus 1 second
BLASTOFF!!!
#countdown(3)
#countdown(0)

40.14 Random numbers : runif()

####################################################################
####################################################################
##
## Generating random numbers
##
##   - runif                       : returns a random number
##   - set.seed(SOME_WHOLE_NUMBER) : resets the random number generator
##   - sample
##
####################################################################
####################################################################

# There are several different functions that are built into R for
# generating "random numbers". These are useful for "simulations"
# e.g. to generate random 


# View the help pages by typing: 
#
#    ?runif
#    ?set.seed
#    ?sample

#--------------------------------------------------------------
#
# runif  
#
# runif stands for "Random number from a UNIForm distribution
#
#--------------------------------------------------------------

runif(1)   # one random number between 0 and 1  (not including 0.0 or 1.0)
[1] 0.4403264
runif(1)   # another random number between 0 and 1  (not including 0.0 or 1.0)
[1] 0.7603103
runif(3)   # three random numbers between 0 and 1
[1] 0.5786796 0.1536499 0.2840610
runif(3, min=0, max=100)   # three random numbers between 0 and 100
[1] 52.16045 20.15071 32.86659
runif(3, min=500, max=505)   # three random numbers between 500 and 505
[1] 503.6439 502.6814 504.9066
trunc(runif(25, min=1, max=11))   # 25 random whole numbers between 1 and 10
 [1]  3  1  8  5  9  3  3  6 10  8  3  6 10  3  9  5  2  9  1  9  7  9  9  3  4
trunc(runif(25, min=1, max=11))   # another 25 random whole numbers between 1 and 10
 [1]  3 10  3  9  5  9  2  7  5  7  2  2  6  1  7  9  9  8  3  5  8  8  7  3  1
#-----------------------------
# set.seed(SOME_WHOLE_NUMBER)
#-----------------------------
set.seed(1)
trunc(runif(3, min=1, max=10))   # 5 random whole numbers between 1 and 9
[1] 3 4 6
trunc(runif(3, min=1, max=10))   # another 5 random whole numbers between 1 and 9
[1] 9 2 9
trunc(runif(3, min=1, max=10))   # another 5 random whole numbers between 1 and 9
[1] 9 6 6
set.seed(1)
trunc(runif(3, min=1, max=10))   # start again with same numbers
[1] 3 4 6
set.seed(1)
trunc(runif(3, min=1, max=10))   # start again with same numbers
[1] 3 4 6
trunc(runif(3, min=1, max=10))   # continue in with the same numbers as when seed was 1
[1] 9 2 9
trunc(runif(3, min=1, max=10))   # continue in with the same numbers as when seed was 1
[1] 9 6 6
set.seed(99)  # different seed starts again with different numbers
trunc(runif(3, min=1, max=10))   #
[1] 6 2 7
trunc(runif(3, min=1, max=10))   #
[1] 9 5 9
set.seed(99)  # start again with same seed
trunc(runif(3, min=1, max=10))   #
[1] 6 2 7
trunc(runif(3, min=1, max=10))   #
[1] 9 5 9
set.seed(1)   # back to first sequence of numbers  
trunc(runif(3, min=1, max=10))   # start again with same numbers
[1] 3 4 6
trunc(runif(3, min=1, max=10))   # continue in with the same numbers as when seed was 1
[1] 9 2 9

40.15 Random numbers : sample()

#-----------------------------------------------------------------------------
# sample  
# 
# NOTE: This is review. We already covered the sample function in an earlier class.
#-----------------------------------------------------------------------------
# View the help page by typing: 
#
#   ?sample

sample(c(10,20,30,40,50,60,70,80,90,100), 3)  # sample 3 items from the set
[1] 70 20 30
sample(c(10,20,30,40,50,60,70,80,90,100), 7)  # sample 7 items from the set
[1]  30  10  50  80  20  60 100
sample(c(10,20,30,40,50,60,70,80,90,100), 10)  # sample 10 items from the set
 [1]  90  50 100  10  70  80  60  20  30  40
sample(c(10,20,30,40,50,60,70,80,90,100))  # sample 10 items from the set
 [1]  90  10  40  30  60  20  50  80 100  70
# with replacement
sample(c(10,20,30,40,50,60,70,80,90,100), 7, replace=TRUE)  # allow same item more than once
[1]  40 100  90  70  60  90  80
sample(c(10,20,30,40,50,60,70,80,90,100), 25, replace=TRUE)  # allow same item more than once
 [1]  90  70  80  60 100  70  30 100  60  80  20  20  60  60  10  30  30  80  60
[20]  70  60  80  70  10  40
sample(c(10,20,30,40,50,60,70,80,90,100), 25, replace=FALSE)  # ERROR
Error in sample.int(length(x), size, replace, prob): cannot take a sample larger than the population when 'replace = FALSE'
sample(c(10,20,30,40,50,60,70,80,90,100), 25)  # ERROR
Error in sample.int(length(x), size, replace, prob): cannot take a sample larger than the population when 'replace = FALSE'
sample(1:10)               # sample all items without replacement
 [1]  8  9 10  1  6  4  3  7  5  2
sample(1:10, replace=TRUE) # sample 10 times with replacement
 [1] 6 1 5 6 1 9 7 7 3 6
# set probabilities on specific values
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 1 1 2 1 1 1 3 1 1 1 2 1 1 1 1 1 1 1 1 1 3 1 1 1 3
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 1 1 1 1 1 2 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 2
# set.seed works for sample too
set.seed(9876)
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 2 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 3 2 1 1 1
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 1 1 2 1 1 1 1 3 1 1 3 3 1 1 2 2 1 2 1 1 2 1 1 1 1
set.seed(9876)
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 2 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 3 2 1 1 1
sample(c(1,2,3), 25, replace=TRUE, prob=c(.7,.2,.1)) # 70% prob 1, 20% prob 2, 10% prob 3
 [1] 1 1 2 1 1 1 1 3 1 1 3 3 1 1 2 2 1 2 1 1 2 1 1 1 1

40.16 Another exmaple - guessingGame()

#-------------------------------------------------------------------------
# Write a guessing game function
#-------------------------------------------------------------------------
# Function should
# 1. pick a random number between 1 and 100
# 2. allow the user to guess the number
# 3. keep looping until the user guesses correctly
# 4. return the number of times it took the user to guess correctly
#-------------------------------------------------------------------------
# NOTE: The following line of code that appears in the function is important
#       but easy to forget to include. This line of code converts the guess
#       from character to numeric. Without this line you would wind up with a
#       very hard to catch bug.
#
#                    guess <- as.numeric(guess) 
#
# Remember that readline always returns a character value.
# Without the code shown above, guess would be character and num would be numeric.
# This would have caused the following problem:
#
#    1. The code "if(guess < num)", which appears in the code below will
#       be comparing a character value with a numeric value.
#
#    2. Remember that character values sort and compare differently than
#       numeric values. For the purpose of this dicussions, remember
#       that "29" and "3" are both character values, while
#       29 and 3 (without the quotes) are numeric values.
#
#         29 < 3 is obviously FALSE, however ..
#
#         "29" < "3" is TRUE!!!
#
#       This is because character values have different rules for 
#       comparison than numeric values do. (Remember - because the first
#       character, in "29" i.e. the "2" is less than the first charcter
#       in "3", i.e. "3", "29" is less than "3".
#
#    3. Because the code "guess < num" compares a character value (i.e. guess)
#       and a numeric value (i.e. guess) the rules of "implicit conversions"
#       determines that both values will be implicitly converted
#       to character values. That means that if guess was "29" and num was 3
#       "29" < 3  would be implicitly converted to "29" < "3" which would
#       be TRUE! That would cause the block of code following
#       if(guess < num) be executed and the user would be told
#       "higher, guess again:" instead of the correct answer of
#       "lower, guess again".
#
#    4. By converting the guess to numeric, the code if(guess < num) will 
#       now correctly compare two numeric values and will correctly 
#       figure out that 29 < 3 is FALSE and will correctly tell the user
#       "lower, guess again".
#-------------------------------------------------------------------------

guessingGame <- function(low=1, high=100){
  
  if (!is.numeric(low) || length(low) != 1 || trunc(low) != low ||
      !is.numeric(high) || length(high) != 1 || trunc(high) != high ) {
    stop("min and max must each be single whole numbers")
  }
  
  if (low >= high){
    stop("low must be less than high")
  }
  
  num <- sample(low:high, 1)
  
  numGuesses <- 1
  guess <- readline("guess: ")
  guess <- as.numeric(guess) # IMPORTANT LINE - see the NOTE in comments above
  
  while(guess != num) {
    if (guess < num){
      guess <- readline("higher, guess again: ")
    } else if (guess > num) {
      guess <- readline("lower, guess again: ")
    }
    
    guess <- as.numeric(guess) # IMPORTANT LINE - see comment above for more info
    numGuesses <- numGuesses + 1
  } 

  return(numGuesses)  
}

#guessingGame()   
#guessingGame()
#guessingGame()

40.17 Another example - Fibonacci sequence

#-------------------------------------
# Another example - Fibonacci sequence
#-------------------------------------

# The numbers 0 1 1 2 3 5 8 13 21 34 55 89 144 ...
# are the first few number in the infinite sequence
# of "Fibonacci numbers". The first two numbers are 0 and 1
# Every other number in the sequence is the sum of the
# two numbers that precede it. 
#
# Write a function fib(n)  that returns the first
# n numbers from the fibonacci sequence. 
#
# n is expected to be a single non-negative whole number.
# The function should stop with an appropriate 
# error message if it is not.
#
# EXAMPLE:
#    > fib(1)
#    0
#
#    > fib(4)
#    0 1 1 2
#
#    > fib(8)
#    0 1 1 2 3 5 8 13

fib <- function(n){
  
  if (!is.numeric(n) || length(n) != 1 || n <= 0){
    stop("n must be a single whole non-negative number")
  }
  
  if (n == 1){
    return(0)
  } else if (n == 2) {
    return(c(0,1))
  }
  
  # set up the variables for the condition in the while
  # (n already has a value since it is an argument to the function)
  answer <- c(0,1)    
  
  while(length(answer) < n){   # a condition that will eventually become FALSE
    
    twoPrevious <- answer[length(answer)-1]
    onePrevious <- answer[length(answer)]
    
    # change a variable that's in the condition
    answer<-c(answer,onePrevious+twoPrevious) 
    
  }
  
  return(answer)
}

fib(0)
Error in fib(0): n must be a single whole non-negative number
fib(1)
[1] 0
fib(4)
[1] 0 1 1 2
fib(8)
[1]  0  1  1  2  3  5  8 13
fib(20)
 [1]    0    1    1    2    3    5    8   13   21   34   55   89  144  233  377
[16]  610  987 1597 2584 4181
lapply(1:10, fib)
[[1]]
[1] 0

[[2]]
[1] 0 1

[[3]]
[1] 0 1 1

[[4]]
[1] 0 1 1 2

[[5]]
[1] 0 1 1 2 3

[[6]]
[1] 0 1 1 2 3 5

[[7]]
[1] 0 1 1 2 3 5 8

[[8]]
[1]  0  1  1  2  3  5  8 13

[[9]]
[1]  0  1  1  2  3  5  8 13 21

[[10]]
 [1]  0  1  1  2  3  5  8 13 21 34

40.18 another example - primesUpTo(maxNum)

rm(list=ls())  # start over ...

# The following code was already created above. This is the exact same 
# code. It is copied here for reference, since the next function,
# primesUpTo, calls this code.

is.prime <- function( num ) {
  if (num < 2){
    return(FALSE)
  }
  divisor <- 2
  
  while ( divisor <= sqrt(num) ) {
    if (num %% divisor == 0){
      return(FALSE)   
    }
    divisor <- divisor + 1
  }
  return(TRUE)
}

#-----------------------------------------
# Get all primes up to a certain number
#-----------------------------------------

# Things to think about in the next function.
# 
# 1. What would happen if the line: numToCheck = numToCheck + 1
#    were placed inside of the block of code for the if?

# All primes up to n
primesUpTo = function( maxNum ){
  primes = numeric(0)
  
  # Set up the variables that are used in the condition 
  # maxNum already has a value since it is an argument to the function
  numToCheck = 2
  
  while (numToCheck <= maxNum){  # a condition that will eventually become FALSE
    
    if(is.prime(numToCheck)){
      primes = c(primes, numToCheck)
    }
    
    # change a variable that is in the condition in a way that will eventually
    # make the condition become FALSE
    numToCheck = numToCheck + 1
  }
  
  primes
}

primesUpTo(10)
[1] 2 3 5 7
primesUpTo(100)
 [1]  2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

40.19 another example - firstNPrimes ( N )

#---------------------------------
# Get first n primes
#---------------------------------
# Things to think about in the following function.
# 
# 1. What would happen if the line: numberToCheck = numberToCheck + 1
#    was not typed at all?

firstNPrimes = function( numPrimes ){
  
  # This is the variable that will be returned.
  # It is also a variable that is used in the condition.
  # We MUST give it a value for both of these reasons.
  primes = numeric(0)
  
  # Setup any other values that the while loop will need.
  numberToCheck = 2
  
  while(length(primes)<numPrimes){ # condition that will evenutually become FALSE
    
    if (is.prime(numberToCheck)){
      primes = c(primes, numberToCheck)
    }
    
    # change a variable that is used the the condition in a way that 
    # eventually the condition will become FALSE
    numberToCheck = numberToCheck + 1
  }

  primes
}

firstNPrimes(10)
 [1]  2  3  5  7 11 13 17 19 23 29
firstNPrimes(20)
 [1]  2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71
firstNPrimes(100)
  [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61
 [19]  67  71  73  79  83  89  97 101 103 107 109 113 127 131 137 139 149 151
 [37] 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
 [55] 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359
 [73] 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
 [91] 467 479 487 491 499 503 509 521 523 541
primes3000 = firstNPrimes(3000)
length(primes3000)   # 3000
[1] 3000
tail(primes3000, 10)
 [1] 27337 27361 27367 27397 27407 27409 27427 27431 27437 27449

40.20 Another example - flightsBeforeReturning() , itineraray()

#############################################################.
# The following example is interesting but doesn't add any new 
# concepts.
#
# 2022 - We skipped going over this example in class for both
# Wilf and Beren classes but I told students to look at this example
# on their own.
#
# -Prof. Rosenthal
#############################################################.

#---------------------------------------------------------------------
# How many flights are needed before you get back to the city you 
# started?
#---------------------------------------------------------------------

# Given the following data:

# The vector cities has names and values that are the same set of cities
# just arranged in a different order. Interpret the names of the vector 
# positions as the cities where an airline has flights. The management of 
# the company schedules the flights based on the data in the vector.
# (see below for more info)

cities = c("new york", "london", "tokyo", "l.a.", "tel aviv", "brussels", "moscow")
names(cities) = sort(cities)
cities
  brussels       l.a.     london     moscow   new york   tel aviv      tokyo 
"new york"   "london"    "tokyo"     "l.a." "tel aviv" "brussels"   "moscow" 
# The airline has a complex scheduling system for each of their planes.
# Each plane flies from city to city to city based on the data shown above.
# Eventually a plane will return to its original city.
#
# For example, a plane that 
#    starts in Brussels will fly to New York
#    From New york, that same plane will fly to Tel Aviv.
#    From Tel Aviv, that same plane will fly back to Brussels
# for a total of 3 flights before it returns to where it started.
#
# Similarly, a plane that 
#    starts in L.A. office will fly to London.
#    From London, that same plane will fly to Tokyo.
#    From Tokyo, that same plane will fly to Moscow.
#    From Moscow that same plane will fly back to L.A.
# for a total of 4 flights before it returns to where it started.
#
# Write a function
#      flightsBeforeReturning = function(staringCity, schedulingVector)
#
# that figures out how many flights it will take for a plane that 
# starts in startingCity will have to fly before it returns to the same
# startingCity based on the data in the schedulingVector.

flightsBeforeReturning = function(startingCity, schedulingVector){
  
  currentCity = startingCity
  numberOfFlights = 0
  
  while( schedulingVector[ currentCity ] != startingCity ){
    currentCity = schedulingVector[ currentCity ]
    numberOfFlights = numberOfFlights + 1
  }
  
  if (schedulingVector[startingCity] != startingCity)
    numberOfFlights = numberOfFlights + 1
  
  numberOfFlights
}

cities
  brussels       l.a.     london     moscow   new york   tel aviv      tokyo 
"new york"   "london"    "tokyo"     "l.a." "tel aviv" "brussels"   "moscow" 
flightsBeforeReturning("brussels", cities)  # 3
[1] 3
flightsBeforeReturning("l.a.", cities)      # 4
[1] 4
flightsBeforeReturning("london", cities)    # 4
[1] 4
flightsBeforeReturning("moscow", cities)    # 4
[1] 4
flightsBeforeReturning("new york", cities)  # 3
[1] 3
flightsBeforeReturning("tel aviv", cities)  # 3
[1] 3
flightsBeforeReturning("tokyo", cities)     # 4
[1] 4
# Similar function to above, but this time return the actual sequence of
# cities that are visited
itinerary = function(startingCity, schedulingVector){
  
  currentCity = startingCity
  visitedCities = startingCity
  
  while( schedulingVector[ currentCity ] != startingCity ){
    
    visitedCities = c(visitedCities, schedulingVector[currentCity])

    currentCity = schedulingVector[ currentCity ]
  }

  # Add the last leg of the itinerary but only if we flew SOMEWHERE first
  if (schedulingVector[startingCity] != startingCity){
    visitedCities = c(visitedCities, startingCity)
  }
  
  names(visitedCities) = NULL   # remove the names
  
  visitedCities
}

cities
  brussels       l.a.     london     moscow   new york   tel aviv      tokyo 
"new york"   "london"    "tokyo"     "l.a." "tel aviv" "brussels"   "moscow" 
itinerary("brussels", cities)  # "brussels" "new york" "tel aviv" "brussels"
[1] "brussels" "new york" "tel aviv" "brussels"
itinerary("l.a.", cities)      # "l.a."   "london" "tokyo"  "moscow" "l.a."  
[1] "l.a."   "london" "tokyo"  "moscow" "l.a."  
itinerary("london", cities)    # "london" "tokyo"  "moscow" "l.a."   "london"
[1] "london" "tokyo"  "moscow" "l.a."   "london"
itinerary("moscow", cities)    # "moscow" "l.a."   "london" "tokyo"  "moscow"
[1] "moscow" "l.a."   "london" "tokyo"  "moscow"
itinerary("new york", cities)  # "new york" "tel aviv" "brussels" "new york"
[1] "new york" "tel aviv" "brussels" "new york"
itinerary("tel aviv", cities)  # "tel aviv" "brussels" "new york" "tel aviv"
[1] "tel aviv" "brussels" "new york" "tel aviv"
itinerary("tokyo", cities)     # "tokyo"  "moscow" "l.a."   "london" "tokyo" 
[1] "tokyo"  "moscow" "l.a."   "london" "tokyo" 

40.21 Using lapply and sapply to call a function multiple times

Suppose we wanted to get multiple factorials and put them in a vector. We could do so by putting the code for a while loop inside the body of another while loop (i.e. this is called a nested loop). We will explore how to do that in the next section. However, nested loops can be confusing, especially for new programmers.

For now, a simpler way to get the factorials of each value in a numeric vector is to use the lapply function. (For a review of lapply see this section.

#----------------------------------------------------------------
# The myFactorial function above only works with a single number.
# You can get the factorials of many numbers by using lapply
# to get a list of the answers for several numbers
#----------------------------------------------------------------
lapply(c(1,3,5,10), myFactorial)  # find the myFactorials of 1,2,3 and 4
Error: object 'myFactorial' not found

40.21.1 sapply is similar to lapply

#---------------------------------------------------------------------
# sapply
#---------------------------------------------------------------------
# The sapply function is similar to the lapply function.
#
# The "l" in lapply stands for "list" since the return value of 
# lapply is always a list.
#
# The "s" in sapply stands for "simplify". The idea of sapply is that 
# sapply can sometimes return the data in a "simpler" structure 
# than lapply does. In this regard, vectors and matrices are 
# considered "simpler" than lists.
# 
# sapply processes the data in a similar way to lapply. Each value 
# in the vector is passed to the specified function. The return values
# from the function calls are then gathered into a single object 
# (a list, a matrix or a vector) in the following way:
# 
# - If every "answer" (i.e. the return value from the function call) 
#is a single vector of length 1, then sapply will return 
#   all of the answers in a single vector instead of returning a list of answers.
#
# - If every "answer" is a vector that has more than one value but all answers
#   are the same length (e.g. all answers have 2 values or all answers have 3 values, etc)
#   then sapply returns a matrix. Each column in the matrix will contain one of the answers.
#
# - If different "answers" are of different lengths or different classes
#   of data, then sapply returns a list of answers. In this case, sapply
#   and lapply return the same value.
#
# The "s" in "sapply" stands for "simplify". In other words, sapply 
# might return a vector or a matrix, instead of a list. Vectors and matrices
# can be thought of as "simpler" data structures than lists, hence the name "sapply".
#
# The "l" in "lapply" stands for "list". This is because the result
# of calling lapply is ALWAY a list of answers.
#---------------------------------------------------------------------

# compare the following call to sapply with the next call to lapply

# sapply returns a vector in this case since myFactorial always returns a single number
sapply(c(1,3,5,10), myFactorial) 
Error: object 'myFactorial' not found
# lapply always returns a list of answers (remember "l" stands for "list")
lapply(c(1,3,5,10), myFactorial) 
Error: object 'myFactorial' not found

40.21.2 sapply - returns vector, matrix or list

As mentioned above, depending on the return values from the function calls, sapply might return a vector, a matrix or a list.

  • As shown above with the myFactorial function, when all function calls return a single value, sapply returns a vector.

  • The examples below show that when all function calls return vectors of more than one value, but all of the same length, then sapply returns a matrix.

  • The examples below also show that when the function calls return vectors of different lengths, or different classes of data (e.g. dataframes, lists, etc), then sapply returns a list.

#..................................................................
# These values and functions will be used below to demonstrate the 
# differences between the lapply and sapply functions.
#..................................................................

# the following function always returns a vector of 3 numbers
# regardless of the value of num. See examples below.
getThreeNumbers = function(num){
  if(length(num) > 1 || !is.numeric(num)){
    stop("num is expected to be a single number")
  }
  c(num, num+10, num+100)
}
getThreeNumbers(2)
[1]   2  12 102
getThreeNumbers(3)
[1]   3  13 103
getThreeNumbers(5)
[1]   5  15 105
# the following function returns different length vectors
# for different values of num. See examples below.
repNumTimes  = function(num){
  if(length(num) > 1 || !is.numeric(num)){
    stop("num is expected to be a single number")
  }
  rep(num, num)  
}
repNumTimes(2)
[1] 2 2
repNumTimes(3)
[1] 3 3 3
repNumTimes(5)
[1] 5 5 5 5 5
# These are some numbers we will use with the following examples
nums = c(1,3,5,10)

# Function f always returns 3 numbers so sapply returns a matrix of 3 rows.
# There will be as many columns in the matrix as there are numbers in nums.
lapply(nums, getThreeNumbers)   # a list
[[1]]
[1]   1  11 101

[[2]]
[1]   3  13 103

[[3]]
[1]   5  15 105

[[4]]
[1]  10  20 110
sapply(nums, getThreeNumbers)   # a matrix
     [,1] [,2] [,3] [,4]
[1,]    1    3    5   10
[2,]   11   13   15   20
[3,]  101  103  105  110
# Function g returns different length vectors for different numbers
# so sapply returns a list, just as lapply does.
lapply(nums, repNumTimes)   # a list
[[1]]
[1] 1

[[2]]
[1] 3 3 3

[[3]]
[1] 5 5 5 5 5

[[4]]
 [1] 10 10 10 10 10 10 10 10 10 10
sapply(nums, repNumTimes)   # also a list
[[1]]
[1] 1

[[2]]
[1] 3 3 3

[[3]]
[1] 5 5 5 5 5

[[4]]
 [1] 10 10 10 10 10 10 10 10 10 10

40.22 Example: Combining techniques - loops, lapply/sapply, named lists

The following examples show how to use lapply and sapply with the is.prime function.

The following gets the first 20 numbers and checks if they are prime. It’s relatively easy to see which number is prime and which is not as the results are in order - 1st answer is TRUE or FALSE for 1, 2nd answer is TRUE or FALSE for 2, etc.

lapply(1:20, is.prime) 
[[1]]
[1] FALSE

[[2]]
[1] TRUE

[[3]]
[1] TRUE

[[4]]
[1] FALSE

[[5]]
[1] TRUE

[[6]]
[1] FALSE

[[7]]
[1] TRUE

[[8]]
[1] FALSE

[[9]]
[1] FALSE

[[10]]
[1] FALSE

[[11]]
[1] TRUE

[[12]]
[1] FALSE

[[13]]
[1] TRUE

[[14]]
[1] FALSE

[[15]]
[1] FALSE

[[16]]
[1] FALSE

[[17]]
[1] TRUE

[[18]]
[1] FALSE

[[19]]
[1] TRUE

[[20]]
[1] FALSE

The following gets the prime status of the first few odd numbers. It’s harder to see which number is prime and which is not as the first TRUE/FALSE is for 1, the second TRUE/FALSE is for 3, the third TRUE/FALSE is for 5, etc.

lapply(seq(1,19,by=2), is.prime)  # harder to read ... 2nd answer is for 3 not 2
[[1]]
[1] FALSE

[[2]]
[1] TRUE

[[3]]
[1] TRUE

[[4]]
[1] TRUE

[[5]]
[1] FALSE

[[6]]
[1] TRUE

[[7]]
[1] TRUE

[[8]]
[1] FALSE

[[9]]
[1] TRUE

[[10]]
[1] TRUE

We can make it easier to see which number is prime and which is not by creating a named vector where the name of each element in the vector is the number that was checked.

# add names
x = lapply(seq(1,19,by=2), is.prime)
names(x) = seq(1,19,by=2)
x      # names must start with a letter. If they don't you can include the name in `backticks`
$`1`
[1] FALSE

$`3`
[1] TRUE

$`5`
[1] TRUE

$`7`
[1] TRUE

$`9`
[1] FALSE

$`11`
[1] TRUE

$`13`
[1] TRUE

$`15`
[1] FALSE

$`17`
[1] TRUE

$`19`
[1] TRUE
x$`9`  # FALSE
[1] FALSE
x$`17` # TRUE
[1] TRUE
#------------------------------------------.
# Use sapply to get multiple results
#------------------------------------------.

sapply(1:20, is.prime)     # result is below
 [1] FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE
[13]  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE
# FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE

sapply(seq(1,19,by=2), is.prime)  # # check just the odd numbers
 [1] FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE
#  [1] FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE


# add names
x = sapply(seq(1,19,by=2), is.prime)
names(x) = seq(1,19,by=2)

x      # result is below
    1     3     5     7     9    11    13    15    17    19 
FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE 
#     1     3     5     7     9    11    13    15    17    19 
# FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE  TRUE

x[9]   # result is below - we only checked odd numbers so the 9th number checked is 17
  17 
TRUE 
#   17 
# TRUE 


x["9"] # result is below - the value whose "name" is "9"
    9 
FALSE 
#     9 
# FALSE 

40.23 NESTED LOOPS

#####################################################################
#####################################################################
##
## NESTED LOOPS 
##
## A "nested loop" is a loop inside of another loop (similar
## to "nested ifs")
##
## We often refer to the "outer loop" and the "inner loop".
##
#####################################################################
#####################################################################

rm(list=ls())   # start over

example = function( maxOuter, maxInner){
  outer = 1
  while(outer <= maxOuter){
    cat("OUTER LOOP (before inner loop): outer=", outer, "\n\n")
    
    inner = 1
    while(inner <= maxInner){
      cat("   INNER LOOP: outer=",outer,"inner=",inner,"\n")
      inner = inner + 1
    }
    
    outer = outer + 1
    
  }
}

example(maxOuter = 2, maxInner = 3)
OUTER LOOP (before inner loop): outer= 1 

   INNER LOOP: outer= 1 inner= 1 
   INNER LOOP: outer= 1 inner= 2 
   INNER LOOP: outer= 1 inner= 3 
OUTER LOOP (before inner loop): outer= 2 

   INNER LOOP: outer= 2 inner= 1 
   INNER LOOP: outer= 2 inner= 2 
   INNER LOOP: outer= 2 inner= 3 
example(maxOuter = 3, maxInner = 5)
OUTER LOOP (before inner loop): outer= 1 

   INNER LOOP: outer= 1 inner= 1 
   INNER LOOP: outer= 1 inner= 2 
   INNER LOOP: outer= 1 inner= 3 
   INNER LOOP: outer= 1 inner= 4 
   INNER LOOP: outer= 1 inner= 5 
OUTER LOOP (before inner loop): outer= 2 

   INNER LOOP: outer= 2 inner= 1 
   INNER LOOP: outer= 2 inner= 2 
   INNER LOOP: outer= 2 inner= 3 
   INNER LOOP: outer= 2 inner= 4 
   INNER LOOP: outer= 2 inner= 5 
OUTER LOOP (before inner loop): outer= 3 

   INNER LOOP: outer= 3 inner= 1 
   INNER LOOP: outer= 3 inner= 2 
   INNER LOOP: outer= 3 inner= 3 
   INNER LOOP: outer= 3 inner= 4 
   INNER LOOP: outer= 3 inner= 5 

40.24 example - primesUpTo() with nested loops

###############################################################################
# 
# Examples of nested loops 
#
###############################################################################

rm(list=ls())  # start over ...

#-----------------------------------------------------------------------------
# Above we defined a
#
#     primesUpTo = function( n )
#
# That returns a vector of all the primes up to n. For example:
#
#     > primesUpTo(15)
#     [1] 2 3 5 7 11 13
#
# In that version of the function, we used a single loop. We also
# called the function is.prime that we had defined earlier inside of the loop.
# Both the funciton is.prime and the function primesUpTo, used a single 
# loop for each function.
#
#
#   ***********************************************************************
#   *** THE FOLLOWING IS ANOTHER WAY OF WRITING THE SAME FUNCTION.      ***
#   *** THIS VERSION DOES NOT CALL is.prime AT ALL. RATHER THIS SINGLE  ***
#   *** FUNCTION DOES ALL OF THE WORK USING TWO DIFFERENT LOOPS -       ***
#   *** ONE INSIDE THE OTHER (i.e. a "nested loop")                     ***
#   ***********************************************************************
#
# The following function, primesUpTo_nestedLoops, returns the exact same values 
# as the primesUpTo function above. However, this version of the function
# does NOT call is.prime. Rather, this version calculates whether a number
# is prime directly in the same function by using a nested loop (i.e. 
# one loop inside of another loop)
#-----------------------------------------------------------------------------

rm(list=ls())

# VERSION WITH "nested loops"
primesUpTo_nestedLoops = function( maxNum ){
 primes = numeric(0)
 numToCheck = 2
 
 while (numToCheck <= maxNum){
  
  # Use an inner loop to figure out if numToCheck is in fact prime
  # and if it is add numToCheck to the vector primes
  isPrime = TRUE  
  divisor = 2
  while(divisor < numToCheck){
   if( numToCheck %% divisor == 0){
    isPrime = FALSE
   }
   
   divisor = divisor+1
  }
  
  # Add the numToCheck to the primes if it in fact is prime
  if(isPrime) {
   primes = c(primes, numToCheck)
  }    
  
  numToCheck = numToCheck + 1
 }
 
 primes
}

primesUpTo_nestedLoops(10)
[1] 2 3 5 7
primesUpTo_nestedLoops(100)
 [1]  2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

40.25 Practice - Rewrite the function firstNPrimes to use nested loops.

We wrote the firstNPrimes function above by using the function is.prime that we had created earlier. Each function, is.prime and firstNPrimes used a single loop.

Rewrite the function firstNPrimes. However, this time the firstNPrimes should not call the is.prime function. Rather write the firstNPrimes function to use a nested loop.

  • The outside loop should keep looping until N primes have been found. Each time through the outer loop another number should be checked to see if it is prime.

  • Use an “inner” loop to deterimine if the number that is currently being evaluated is prime. This inner loop should run to completion each time through the outer loop. This inner loop essentially does the job of the is.prime function.

  • NOTE: It can be argued that the “original” version of the code is better than coding a single function with a nested loop. Having two simple functions can be easier to understand than coding a single more complicated function with a nested loop.

    Nevertheless, others might argue that having a single function where everything is in one place is better.

    Bottom line - they are both valid approaches to this problem and you need to understand both.

#~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
# For your reference, below are the original versions of the is.prime function
# and the firstNPimes function that calls the is.prime function and does NOT
# use nested loops.
#~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

rm(list=ls())    # start over

is.prime <- function( num ) {
 if (num < 2){
  return(FALSE)
 }
 divisor <- 2
 while ( divisor <= sqrt(num) ) {
  if (num %% divisor == 0){
   return(FALSE)   
  }
  divisor <- divisor + 1
 }
 return(TRUE)
}

firstNPrimes = function( numPrimes ){
 primes = numeric(0)
 numberToCheck = 2
 while ( length(primes) < numPrimes  ){
  if (is.prime(numberToCheck)){
   primes = c(primes, numberToCheck)
  }
  numberToCheck = numberToCheck + 1
 }
 primes
}

firstNPrimes(10)
 [1]  2  3  5  7 11 13 17 19 23 29
firstNPrimes(100)
  [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61
 [19]  67  71  73  79  83  89  97 101 103 107 109 113 127 131 137 139 149 151
 [37] 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
 [55] 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359
 [73] 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
 [91] 467 479 487 491 499 503 509 521 523 541
rm(list=ls())

firstNPrimes = function( numPrimes ){
 primes = numeric(0)
 numberToCheck = 2
 while ( length(primes) < numPrimes  ){
  
  isPrime = TRUE
  
  # Check if numberToCheck is prime. If it isn't prime set the variable
  # isPrime to FALSE.
  
  divisor = 2
  while(divisor < numberToCheck){
   if ( numberToCheck %% divisor == 0){
    isPrime = FALSE
   }
   
   divisor = divisor + 1
  }
  
  if (isPrime == TRUE){
   primes = c(primes, numberToCheck)
  }
  numberToCheck = numberToCheck + 1
 }
 primes
}

firstNPrimes(10)
 [1]  2  3  5  7 11 13 17 19 23 29
firstNPrimes(100)
  [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61
 [19]  67  71  73  79  83  89  97 101 103 107 109 113 127 131 137 139 149 151
 [37] 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
 [55] 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359
 [73] 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
 [91] 467 479 487 491 499 503 509 521 523 541

40.26 Practice - Rewrite your own version of the t function

The t function takes a matrix and returns a copy of the matrix with the rows and columns swapped, i.e. mat[i,j] becomes returnValue[j,i]. See the example below.

mat = matrix(seq(10,180,10), nrow=3, ncol=6)
mat     # 3 rows, 4 columns
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]   10   40   70  100  130  160
[2,]   20   50   80  110  140  170
[3,]   30   60   90  120  150  180
t(mat)  # 4 rows, 3 columns (the rows became cols and the cols became rows)
     [,1] [,2] [,3]
[1,]   10   20   30
[2,]   40   50   60
[3,]   70   80   90
[4,]  100  110  120
[5,]  130  140  150
[6,]  160  170  180

Write the function myt that does the same thing as the t function. Do NOT call the t function in your code.

Suggestions:

  1. Create a new matrix to store your answer
  2. Create two loops - one nested indide the other
  3. The “outer loop” should generate the row numbers of the original matrix, one at a time
  4. The “inner loop” should generate the column numbers of the original matrix, one at a time.
  5. Each time through the inner loop, copy one value from the orignal matrix to its appropriate location in the new matrix.
  6. After the loops are all finished, return the new matrix.
#############.
# ANSWER
#############.
myt = function( m ){
 
 # make the answer the right number of rows and columns
 answer = matrix( 0 ,  nrow=ncol(m) , ncol=nrow(m) )
 
 row = 1
 while(row <= nrow(m)) {
  
  col = 1
  while(col <= ncol(m)) {
   
   # assign the value at row,col in m to the correct place in the answer
   answer[col,row] = m[row,col]
   col = col + 1
  }
  
  row = row + 1
 }
 
 answer
}

#---------------------------
# Test our function
#---------------------------

# Create a sample matrix
mat = matrix(seq(10,120,10), nrow=3, ncol=4)
mat
     [,1] [,2] [,3] [,4]
[1,]   10   40   70  100
[2,]   20   50   80  110
[3,]   30   60   90  120
# Run our function
myt(mat)
     [,1] [,2] [,3]
[1,]   10   20   30
[2,]   40   50   60
[3,]   70   80   90
[4,]  100  110  120

40.27 Practice - Rewrite the function myt to use a single loop (not a nested loop)

See the previous question.
This time, see if you can rewrite the function myt to use a single loop (not a nested loop).

Suggestions:

  • The single loop should keep updating a variable, eg. inRow, that contains the number of a row from the input matrix. For example

    • If the input matrix has 3 rows then the loop should go around 3 times.
    • The 1st time through the loop, the variable inRowNumber, should contain 1,
    • The 2nd time through the loop, the variable inRowNumber, should contain 2,
    • The 3rd time through the loop, the variable inRowNumber, should contain 3
  • Inside the loop use matrix notation to assign the values from a row in the input matrix to the corresponding column in the output matrix

myt=function( m ){
 returnValue = matrix(1 ,nrow=ncol(m) ,ncol=nrow(m))
 
 inRow = 1
 while(inRow <= nrow(m)) {
  
  returnValue[ , inRow] = m[inRow , ]
  inRow = inRow + 1
  
 }
 returnValue
}

mat = matrix(seq(10,180,10), nrow=3, ncol=6)
mat
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]   10   40   70  100  130  160
[2,]   20   50   80  110  140  170
[3,]   30   60   90  120  150  180
myt(mat)
     [,1] [,2] [,3]
[1,]   10   20   30
[2,]   40   50   60
[3,]   70   80   90
[4,]  100  110  120
[5,]  130  140  150
[6,]  160  170  180

40.28 More practice - drawing shapes with loops

Below are a few “toy” examples of using nested loops. Exercises like these help you to become more familiar with the concepts of nested loops. This is similar to playing “scales” when learning to play the piano - no one will play scales in real life - but it is important to get the hang of things when you’re first starting out.

40.28.1 Drawing shapes with single loops (not nested)

The following functions each use a single loop (i.e. NOT nested loops). These are provided for comparison with the equivalent versions that use nested loops which we will show you below.

rm(list=ls())   # start over

# draw a line of x's of the specified width
# Do not use the rep function. Use the cat function and a loop.
drawLine_WithXs = function( width ) {
  
  while(width > 0){
    cat("x")
    width = width - 1
  }
}

drawLine_WithXs(4)  # xxxx
xxxx
drawLine_WithXs(5)  # xxxxx
xxxxx
# draw a box of x's
drawBox_WithXs = function(h, w){
  while(h > 0){
    drawLine_WithXs(w)
    cat("\n")
    h = h - 1
  }
}

drawBox_WithXs(3,4)  # box of 3 rows and 4 columns of x's
xxxx
xxxx
xxxx
# Draw a horizontal line with calls to cat that displays the numbers as shown below
drawLine_WithNums = function(width){
  num1 = 1
  while(num1 <= width){
    cat(num1)
    num1 = num1 + 1
  }  
}

drawLine_WithNums(4)  # 1234
1234
drawLine_WithNums(5)  # 12345
12345

40.28.2 Nested loop versions

40.28.3 Practice - drawBox1

# Write a function 
#   drawBox1 = function(height, width)
#
# height and width are expected to be whole numbers between 1 and 9.
# The function should draw a box that has dimensions height rows and width columns.
# The box should be drawn with numbers such that each number represent the 
# number of the column it is in. For example:
#
#       > drawBox1(3, 5)
#       12345
#       12345
#       12345
drawBox1 = function(height, width){

  rowNumber = 1
  while(rowNumber <= height){
    
      colNumber = 1
      while(colNumber <= width){
        cat(colNumber)
        colNumber = colNumber + 1
      }  
      
      cat("\n")

      rowNumber = rowNumber + 1      
  }
  
}

drawBox1(3, 4)
1234
1234
1234
drawBox1(4,9)
123456789
123456789
123456789
123456789

40.28.4 Practice - drawBox2

#-----------------------------------------------------------------------------
# QUESTION:
# Write a function 
#   drawBox2 = function(height, width)
#
# height and width are expected to be whole numbers between 1 and 9.
# The function should draw a box that has dimensions height rows and width columns.
# The box should be drawn with numbers such that each number represent the 
# number of the row it is in. For example:
#
#       > drawBox1(3, 5)
#       11111
#       22222
#       33333
#-----------------------------------------------------------------------------
drawBox2 = function(height, width){
  
  rowNumber = 1
  while(rowNumber <= height){
    
    colNumber = 1
    while(colNumber <= width){
      cat(rowNumber)
      colNumber = colNumber + 1
    }  
    
    cat("\n")
    
    rowNumber = rowNumber + 1      
  }
  
}

# Testing our function
drawBox2(3,4)
1111
2222
3333

40.28.5 Practice - drawBox3

#-----------------------------------------------------------------------------
# Write the function drawBox3 to produce the results according to the pattern
# demonstrated in the following example:
#
# EXAMPLE: 
#   > drawBox3(3,4)
#   3333
#   2222
#   1111
#-----------------------------------------------------------------------------
drawBox3 = function(height, width){
  
  rowNumber = height
  while(rowNumber > 0){
    
    colNumber = 1
    while(colNumber <= width){
      cat(rowNumber)
      colNumber = colNumber + 1
    }  
    
    cat("\n")
    
    rowNumber = rowNumber - 1      
  }
  
}

drawBox3(3,4)
3333
2222
1111

40.28.6 Practice - drawBox4

Write the function drawBox4 to produce the results according to the pattern demonstrated in the following example:

> drawBox4(3,4)
4321
4321
4321
drawBox4 = function(height, width){
 
  row = 1
  while(row <= height){
    
    col = width
    while( col >= 1){
      cat(col)
      
      col = col - 1      
    }
    
    cat("\n")
    row = row + 1 
  }
}


drawBox4(3,4)
4321
4321
4321

40.28.7 Practice - drawTriangle1

# Write the function drawTriangle1 to produce the results according to the pattern
# demonstrated in the following example:
#
# EXAMPLE: 
#    > drawTriangle1(3)
#    1
#    12
#    123
#
#    > drawTriangle(5)
#    1
#    12
#    123
#    1234
#    12345
#-----------------------------------------------------------------------------
drawTriangle1 = function(size){

  row = 1
  while(row <= size){
    
    col = 1
    while(col <= row){
      cat(col)
      col = col + 1      
    }
    cat("\n")
    row = row + 1    
  }
  
}

drawTriangle1(3)
1
12
123
drawTriangle1(9)
1
12
123
1234
12345
123456
1234567
12345678
123456789

40.28.8 Practice - drawTriangle2

#-----------------------------------------------------------------------------
# Write the function drawTriangle1 to produce the results according to the pattern
# demonstrated in the following example:
#
# EXAMPLE: 
#    > drawTriangle2(3)
#    111
#    22
#    3
#-----------------------------------------------------------------------------
drawTriangle2 = function(size){

  outer = 1
  while( outer <= size ){
    
    inner = 1
    while( inner <= size - outer + 1){
      
      cat ( outer )
      
      inner = inner + 1
    }
    
    cat("\n")
    outer = outer + 1
  }
}

drawTriangle2(3)
111
22
3
drawTriangle2(5)
11111
2222
333
44
5

40.29 Other ways to do something over and over …

In R there are a few ways to do something over and over again … Often it is possible to recreate the same effect as a while loop by using one of these other approaches.

  1. Vector arithmetic automatically performs the same operation over and over to each item in the vector. For example:
# the folloiwng adds 1 to each number
nums = c(10,20,30)
nums + 1
[1] 11 21 31
  1. R has many “vectorized functions” that do something over and over. For example, the prod function takes the product of all the values in a vector. This can be used to very quickly calculate the factorial of a number without actually using the factorial (or myFactorial) function.
prod(1:5)   # product of the values in the vector, i.e. 120 - same as myFactorial(5)
[1] 120
  1. The apply family of R functions, e.g. lapply and sapply can very often be used instead of a loop. These functions perform a function for each value in a vector (or a list).

  2. R has other types of loops - e.g. for loop (covered in another chapter) and repeat loop (not covered in this book)

© 2025 Y. Rosenthal. All rights reserved.