4.3 Repeating Things: Looping
We have looked a bit into the aspect of flow control that pertains to making decisions. Let us now turn to the R-constructs that make the computer repeat actions.
4.3.1 For Loops
The reserved word for
is used to make R repeat an action a specified number of times.
We begin with an very simple example:
for ( i in 1:4 ) {
cat("Hello, Dorothy!\n")
}
## Hello, Dorothy!
## Hello, Dorothy!
## Hello, Dorothy!
## Hello, Dorothy!
Here’s how the loop works. Recall that the vector 1:4
is simply the sequence of integers from 1 to 4:
1:4
## [1] 1 2 3 4
When R sees the code (i in 1:4)
it knows that it will have to go four times through the body of the loop.
cat("Hello, Dorothy!\n")
(The body of a loop is what’s contained in the brackets after for(i in 1:4)
). At the start of the loop, the index variable i
is set to 1. After the body is executed the first time, R sets i
to 2, then executes the body again. Then R sets i
to 3 and executes the body yet again. Then R sets i
to 4, and executes the body for the final time. The result is four lines printed out to the console.
The more you need to repeat a particular pattern, the more it makes sense to write your code with a loop.
The general form of a loop is:
for ( var in seq ) {
# code involving var
}
var
is the index variable, and it can be any permitted name for a variable, and seq
can be any vector. As R traverses the loop, the value of the index variable var
becomes each element of the vector seq
in turn. With every change in the value of var
, the code in the brackets is executed.
To iterate is to do a thing again and again. The vector seq
is sometimes called an iterable, since it is “iterated over.” It contains the values that the index variable will assume, one by one, as the loop is repeated.
It’s important to realize that the index variable can have any valid name, and the sequence can be any type of vector at all—not just a sequence of consecutive whole numbers beginning with 1. This level of generality permits a for-loop to automate a wide variety of tasks, and for its code to be written in a way that evokes the operations being performed.
For example, here is a loop to print out some greetings;
<- c("Dorothy", "Tin Man", "Scarecrow", "Lion")
people for ( person in people ) {
cat("Hello, ", person, "!\n", sep = "")
}
## Hello, Dorothy!
## Hello, Tin Man!
## Hello, Scarecrow!
## Hello, Lion!
Or perhaps you want to abbreviate a vector-ful of weekday-names:
<- c("Saturday", "Monday", "Friday", "Saturday")
weekdays for ( day in weekdays ) {
print(abbrDay(day))
}
## [1] "Sat"
## [1] "Mon"
## [1] "Fri"
## [1] "Sat"
Quite often you will want to store the results of your trips through the loop. Let’s do this for our abbreviated weekdays:
<- c("Saturday", "Monday", "Friday", "Saturday")
weekdays <- character(length(weekdays)) abbrDays
We used the character()
function to create the character vector abbrDays
. We specified that the number of elements in abbrDays
shall be the same as the number of elements in weekdays
. Right now abbrDays
isn’t very interesting, as all of its elements are empty strings:
abbrDays
## [1] "" "" "" ""
We will now write a for-loop to fill it up with abbreviations:
for ( i in 1:length(weekdays) ) {
<- abbrDay(weekdays[i])
abbrDays[i] }
Now each of the four elements of abbrDays
contains the abbreviation for the corresponding element of weekdays
:
abbrDays
## [1] "Sat" "Mon" "Fri" "Sat"
You will often have reason to set up an empty vector of a definite length and use a loop to store information in it. The general format looks like this:
<- numeric(someLength) # empty numerical vector
results
for ( i in 1:someLength ) {
# computations involving i, and then:
<- some_result_depending_on_i
results[i]
}
# then do something with results, such as:
print(results)
4.3.2 For-Loop Caution
You might need to exercise some caution in your choice name for the index variable. If you are already using it as the name for a variable in the same environment, then you will overwrite that variable, as demonstrated by the following code:
<- "Thursday"
day cat("Today is ", day, ".\n", sep = "")
## Today is Thursday.
<- c("Saturday", "Monday", "Friday", "Saturday")
weekdays <- character(length(weekdays))
abbrDays for ( day in weekdays ) {
print(day)
}
## [1] "Saturday"
## [1] "Monday"
## [1] "Friday"
## [1] "Saturday"
cat("Today is ", day, ".\n", sep = "")
## Today is Saturday.
Of course, that won’t happen if the for-loop is inside of a function and your variable is outside of it.
<- "Thursday"
day cat("Today is ", day, ".\n", sep = "")
## Today is Thursday.
<- c("Saturday", "Monday", "Friday", "Saturday")
weekdays <- function(days) {
listDays for ( day in days ) {
print(day)
}
}listDays(weekdays)
## [1] "Saturday"
## [1] "Monday"
## [1] "Friday"
## [1] "Saturday"
cat("Today is still ", day, ".\n", sep = "")
## Today is still Thursday.
4.3.3 Breaking Out of a Loop
Sometimes you finish the task at hand before you are done with the loop. If this is a possibility for you then you may arrange to break out of the loop with the break
reserved-word.
Suppose for example that you want a function that searches through a vector for a given element, updating the user along the way as to the progress of the search. You can try something like this:
# function to find index of element in vector.
# returns -1 if elem is not in vector
<- function(elem, vec) {
verboseSearch
# The following logical keeps track of whether
# we have found the element.
# We have not yet begun the search so start it
# at FALSE.
<- FALSE
found
# validate input:
if ( length(vec) == 0 ) {
cat("The vector empty. No way does it contain ",
".", sep = "")
elem, return(-1)
}
# check the elements of vector:
for ( i in 1: length(vec) ) {
if ( vec[i] == elem ) {
# record that we found the element:
<- TRUE
found break
else {
} # report no match at this index:
cat("Checked index ", i,
" in the vector. No match there ...\n", sep = "")
}
}
if ( found ) {
# report success:
cat("Found ", elem, " at index ", i, ".\n", sep = "")
return(i)
else {
} # report failure:
cat(elem, " is not in the vector.\n", sep = "")
return(-1)
} }
Let’s see our function in action:
<- c("Dorothy", "Tin Man", "Scarecrow", "Lion")
people <- verboseSearch("Scarecrow", people) scarecrowPlace
## Checked index 1 in the vector. No match there ...
## Checked index 2 in the vector. No match there ...
## Found Scarecrow at index 3.
In the code for verboseSearch()
you will notice that there is an initial check on the length of the vector. This is actually important. If a user were to enter an empty vector, then its length would be 0. Then in the loop the sequence would be 1:0
, which is the vector with elements 1 and 0. But look at what happens when you ask for any element of a zero-length vector:
<- character(0)
emptyVec 1] # You get NA emptyVec[
## [1] NA
Then check out what happens if you compare an NA
to a string:
NA == "Scarecrow"
## [1] NA
Now look at what happens in an if
statement where the condition is NA
:
if ( NA ) {
cat("We are in the bracket!\n")
}
## Error in if (NA) { : missing value where TRUE/FALSE needed
Checking that the input vector has positive length is an another example of validating input. Remember: when you write functions for other people to use it can be helpful to have the function validate its input instead of allowing R to throw obscure error messages at the user.
4.3.4 Solving the Empty-Vector Problem in for
-Loops with seq_along()
In the previous section we considered a possible problem with for
-loops of the following form:
for ( i in 1:length(vec) ) {
## do something ...
}
In the above loop vec
could be thought of as a “loop-defining” vector: its length determines the sequence of values 1, 2, 3 … for the index i
. This sequence of values is supposed to end at the length of vec
.
The problem is that if vec
happens to be an empty vector then we probably don’t want to enter the loop at all. However, the length of an empty vector is 0, and so the vector 1:length(vec)
actually works out to be a vector with two elements:
c(1,0)
Hence R will go through the loop twice: once when i
is 1, and again when i
is 0. Depending on what the loop does, very unexpected results could be produced.
In the previous section we dealt with the problem by writing an if
-statement that provides the proper response when vec
is empty. In many circumstances however, all we need to do is to make sure that the loop is skipped when the vector is empty.
A handy way to ensure skipping is to use the function seq_along()
. Given any non-empty vector, seq_along()
produces a sequence-vector that begins with 1 and ends with the length of the vector, thus:
<- c("a", "d", "f")
vec seq_along(vec)
## [1] 1 2 3
On the other hand, if the vector is empty, then seq_long()
returns an empty numeric vector:
<- character()
vec seq_along(vec)
## integer(0)
Now consider the loop inside the following function:
<- function(vec) {
loopy for ( i in seq_along(vec) ) {
cat("This time i is ", i, ".\n", sep = "")
} }
Given a non-empty vector, it goes through the loop a number of times equal to the length of the vector:
loopy(c("a", "d", "f"))
## This time i is 1.
## This time i is 2.
## This time i is 3.
On the other hand, when given an empty vector the function does not enter the loop at all:
loopy(character()) # no output to console!
When you are writing a program that is complex enough that you don’t know whether the loop-defining vector might be empty, it is good practice to use seq_along()
as a safeguard.
4.3.5 Skipping Ahead in a Loop
Depending on what happens within a loop, you might sometimes wish to skip the remaining code within the loop and proceed to the next iteration. R provides the reserved-word next
for this purpose. Here is a simple example:
<- c("a","e", "e", "i", "o", "u", "e", "z")
vec # shout ahoy when you see the specified element
<- function(elem, vec) {
verboseAhoy if (length(vec) > 0) {
for ( i in 1: length(vec) ) {
if ( vec[i] != elem) next
cat("Ahoy! ", elem, " at index ", i, "!\n", sep = "")
}
}
}verboseAhoy("e", vec)
## Ahoy! e at index 2!
## Ahoy! e at index 3!
## Ahoy! e at index 7!
When the vec[i] !== elem
condition is true, R immediately skips the rest of the loop, increments the value of the index variable i
, and runs through the loop again.
You can always accomplish the skipping without using next
explicitly, but it’s nice to have on hand.
4.3.6 Repeat
For-loops are pretty wonderful, but they are best used in circumstances when you know how many times you will need to loop. When you need to repeat a block of code until a certain condition occurs, then the repeat
reserved-word might be a good choice.
For example, suppose you want to play the number-guessing game with the user, but let her keep guessing until either she gives up or gets the correct answer. Here’s an implementation using repeat
:
<- 20
n <- sample(1:n, size = 1)
number cat("I'm thinking of a whole number from 1 to ", n, ".\n", sep = "")
repeat {
<- readline("What's your guess? (Enter q to quit.) ")
guess if ( guess == "q" ) {
cat("Bye!\n")
break
else if ( as.numeric(guess) == number ) {
} cat("You are correct! Thanks for playing!")
break
}# If we get here, the guess was not correct:
# loop will repeat!
}
The game works well enough, but if you give it a try, you are sure to find it a bit fatiguing. It wold be nice to give the user a hint after an incorrect guess. Let’s revise the game to tell the reader whether her guess was high or low. While we are at it, let’s cast the game into the form of a function.
<- function(n) {
numberGuess <- sample(1:n, size = 1)
number cat("I'm thinking of a whole number from 1 to ", n, ".\n", sep = "")
repeat {
<- readline("What's your guess? (Enter q to quit.) ")
guess if (guess == "q") {
cat("Bye!\n")
break
else if (as.numeric(guess) == number) {
} cat("You are correct! Thanks for playing!")
break
}# If we get to this point the guess was not correct.
# Issue hint:
<- ifelse(as.numeric(guess) > number, "high", "low")
hint cat("Your guess was ", hint, ". Keep at it!\n", sep = "")
# Repeat loop
} }
A typical game:
> numberGuess(100)
I'm thinking of a whole number from 1 to 100.
What's your guess? (Enter q to quit.) 50
Your guess was high. Keep at it!
What's your guess? (Enter q to quit.) 25
Your guess was high. Keep at it!
What's your guess? (Enter q to quit.) 12
Your guess was low. Keep at it!
What's your guess? (Enter q to quit.) 18
Your guess was low. Keep at it!
What's your guess? (Enter q to quit.) 22
Your guess was high. Keep at it!
What's your guess? (Enter q to quit.) 20
You are correct! Thanks for playing!
4.3.7 While
The reserved word while
constructs a loop that runs as long as a specified condition is true. Unlike repeat
, which launches directly into the loop, the condition for while
is evaluated prior to the body of the loop. If the condition is false at the beginning, the code in the body of the loop is never executed.
verboseSearch()
could be re-written with while
:
<- function(elem, vec) {
verboseSearch <- FALSE
found if ( length(vec) == 0 ) {
cat("The vector is empty. No way does it contain ",
".\n", sep = "")
elem, return(-1)
}# index of vec (start looking at 1):
<- 1
i while ( !found & i <= length(vec) ) {
if ( vec[i] == elem ) {
<- TRUE
found break
}cat("No match at position ", i, " ...\n")
<- i + 1
i
}
if ( found ) {
# report success:
cat("Found ", elem, " at index ", i, ".\n", sep = "")
return(i)
else {
} # report failure:
cat(elem, " is not in the vector.\n", sep = "")
return(-1)
} }
4.3.8 Practice Problems
You want to compute the square roots of the whole numbers from 1 to 10. You could do this quite easily as follows:
sqrt(1:10)
## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 ## [10] 3.162278
However, you want to practice using
for
-loops. Write a small program that uses a loop to compute the square roots of the whole numbers from 1 to 10.Think about the practice problem
findKarl()
from the previous section. Write a new version that uses a loop.You want to append
"the Dog"
to the end of each element of a character vector of dog-names. Sadly, you have not yet realized that thepaste()
function is very much up to the task:<- c("Bo", "Gracie", "Rover", "Anya") doggies paste(doggies, "the Dog")
## [1] "Bo the Dog" "Gracie the Dog" "Rover the Dog" "Anya the Dog"
You decide to use a
for
-loop. Write the code.Write a function called
triangleOfStars()
that produces patterns liks this:**** *** ** *
It should take a single parameter
n
, the number of asterisks in the first “line” of the triangle. Use afor
-loop in the body of the function.Write a function called
trianglePattern()
that produces patterns like the ones from the previous problem, but using any character that the user specifies. The function should take two parameters:char
, a string giving the character to use. The default value should be"*"
.n
, the number of asterisks in the first “line” of the output.
Typical examples of use should be as follows:
trianglePattern(char = "x", 5)
## xxxxx ## xxxx ## xxx ## xx ## x
trianglePattern(n = 6)
## ****** ## ***** ## **** ## *** ## ** ## *
Rewrite
triangleOfStars()
so that it uses awhile
-loop in its body.Rewrite
triangleOfStars()
so that it still uses awhile
-loop in its body, but produces output like this:* ** *** ****
Write a function called
cheerleader()
that badgers you for each of the letters in a word until you give it all of the letters, one by one. It should work like this:<- c("T", "I", "G", "E", "R", "S") schoolMascot cheerleader(cheer = schoolMascot)
> cheerleader(cheer = schoolMascot) Gimme a T! T Gimme a I! i Gimme a I! I Gimme a G! gee Gimme a G! I said gee!! Gimme a G! G Gimme a E! E Gimme a R! Are you going to do this all day? Gimme a R! R Gimme a S! Stop it, please! Gimme a S! S >
Write a function called
ageReporter()
that reports the ages of given individuals. It should take two parameters:people
: a character vector of names of the people of interest.ages
: a vector containing the ages of the people of interest.
The function should perform some validation: it should stop the user with a helpful message if the number of people supplied and the number of ages supplied are not the same, or if one of the supplied vectors has length zero.
Typical examples of use would be as follows:
<- c("Gina", "Hector") folks <- c(25, 22) theirAges ageReporter(folks, theirAges)
## Gina is 25 year(s) old. ## Hector is 22 year(s) old.
<- c("Gina", "Hector", "Tom") folks <- c(25, 22) theirAges ageReporter(folks, theirAges)
## Number of people must equal number of ages!
<- c("Gina", "Hector") folks <- numeric(0) theirAges ageReporter(folks, theirAges)
## Provide at least one age!
4.3.9 Solutions to the Practice Exercises
Here’s a suitable program:
<- numeric(10) roots for ( i in 1:10 ) { <- sqrt(i) roots[i] } roots
## [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 ## [10] 3.162278
You could do this:
<- function(x) { findKarl <- character(length(x)) results for ( i in 1: length(x) ) { if ( x[i] == "Karl" ) { <- "Yep, that's Karl!" results[i] else { } <- "Sorry, not our guy." results[i] } } results }
Here you go:
<- c("Bo", "Gracie", "Rover", "Anya") doggies <- character(length(doggies)) titledDoggies for (i in 1:length(doggies) ) { <- paste(doggies[i], "the Dog") titledDoggies[i] } titledDoggies
## [1] "Bo the Dog" "Gracie the Dog" "Rover the Dog" "Anya the Dog"
Here’s the code:
<- function(n) { triangleOfStars for ( i in n:1 ) { <- rep("*", times = i) line cat(line, "\n", sep = "") } }
Try it out:
triangleOfStars(5)
## ***** ## **** ## *** ## ** ## *
Here’s the code:
<- function(char = "*", n) { trianglePattern for (i in n:1) { <- rep(char, times = i) line cat(line, "\n", sep = "") } }
Here’s the code:
<- function(n) { triangleOfStars while ( n >= 1 ) { <- rep("*", times = n) line cat(line, "\n", sep = "") <- n - 1 n } }
Try it out:
triangleOfStars(5)
## ***** ## **** ## *** ## ** ## *
Here’s the code:
<- function(n) { triangleOfStars <- 1 m while ( m <= n ) { <- rep("*", times = m) line cat(line, "\n", sep = "") <- m + 1 m } }
Try it out:
triangleOfStars(5)
## * ## ** ## *** ## **** ## *****
Here is the requested annoying function, using a
repeat
-loop withbreak
inside afor
-loop:<- function(cheer) { cheerleader for ( letter in cheer ) { repeat { <- paste("Gimme a ", letter, "! ", sep = "") call <- readline(call) response if ( response == letter ) break } } }
Here is one possibility:
<- function(people, ages) { ageReporter ## validation: <- length(people) p <- length(ages) a if (p == 0) { return(cat("Provide at least one person!\n")) }if (a == 0) { return(cat("Provide at least one age!\n")) }if (!(p == a & p > 0)) { return(cat("Number of people must equal number of ages!\n")) }## process: for (i in 1:length(people)) { cat(people[i], " is ", ages[i], " year(s) old.\n", sep = "") } }