Flow Control: Loops

(Section 4.3)

for-Loops

Three Types of Flow Control

Flow-control in R (and in many computer languages) comes in three forms:

  1. Pausing Execution (as in readline())
  2. Making Decisions (if and if ... else)
  3. Repeating Execution
    • for-loops
    • while-loops

for-Loop

The reserved word for is used to make R perform execute a body of code on each of the elements of a specified vector. Here is a simple example:

people <- c("Dorothy", "Tin Man", "Scarecrow", "Lion")
for ( person in people ) {
  cat("Hello, ", person, "!\n", sep = "")
}
Hello, Dorothy!
Hello, Tin Man!
Hello, Scarecrow!
Hello, Lion!

Note that the index variable person has a name that describes its value.

A Closer Look

greeter <- function(people) {
  for (person in people) {
    cat("Hello, ", person, "!\n", sep = "")
  }
}
debug(greeter)
greeter(c("Sita", "Rama", "Ravana"))

General Form

The general form of a loop is:

for ( index in iterable ) {
  expression
  another expression
  ...
  final expression
}
  • index is the index variable. It becomes each element of the vector iterable.
  • iterable can be any vector. It’s elements will be traversed, one by one.
  • The value of the index variable index becomes each element of the vector iterable in turn.
  • With every change in the value of index, the code in the brackets is executed.

Know Your Definitions

To iterate is to do a thing again and again.

Index Variable

The variable in a for loop that takes on each of the values of the iterable in succession.

Iterable

The vector that provides the sequence of values for the index variable in a for loop.

Another Example (Doing the Same Thing Many Times)

for (i in 1:3) {
  cat("Hello, Dorothy!\n")
}
Hello, Dorothy!
Hello, Dorothy!
Hello, Dorothy!
  • the index i does not appear in the body of the loop;
  • the iterable is there only to make the same action repeat a specified number of times. Its values were not used; only its length is important.

More Than One “Information” Vector

Suppose we have information in two vectors:

people <- c("Dorothy", "Tin Man", "Scarecrow", "Lion")
desires <- c("Kansas", "brains", "a heart", "courage")

And we want to say:

Hello, Dorothy, you desire Kansas!
Hello, Tin Man, you desire brains!
Hello, Scarecrow, you desire a heart!
Hello, Lion, you desire courage!

How to do it?

Iterate “by Index”!

We can iterate through the indices of of the vectors:

for ( i in 1:4 ) {
  cat(
    "Hello, ", people[i], ", you desire ", 
    desires[i], "!\n", sep = ""
    )
}
Hello, Dorothy, you desire Kansas!
Hello, Tin Man, you desire brains!
Hello, Scarecrow, you desire a heart!
Hello, Lion, you desire courage!

(Note: Single letters (i, j, etc.) are often used as names for index-variables in indirect iteration.)

A Closer Look

greeter <- function(peeps, wants) {
  for ( i in 1:4 ) {
    cat(
      "Hello, ", peeps[i], ", you desire ", 
      wants[i], "!\n", sep = ""
    )
  }
}
debug(greeter)
greeter(peeps = people, wants = desires)

Iteration by Index, More General

Use length() to make R figure out when to stop the loop:

people <- c("Dorothy", "Tin Man", "Scarecrow", "Lion")
for ( i in 1:length(people) ) {
  cat("Hello, ", people[i], "!\n", sep = "")
}
Hello, Dorothy!
Hello, Tin Man!
Hello, Scarecrow!
Hello, Lion!

Practice

Write a for-loop that produces the following in the Console:

The square of 1 is 1.
The square of 2 is 4.
The square of 3 is 9.
The square of 4 is 16.
The square of 5 is 25.

Hint:

i <- 3
cat("The square of ", i, " is ", i^2, ".\n", sep = "")
The square of 3 is 9.

Solution to the Practice Problem

for (i in 1:5) {
  cat("The square of ", i, " is ", i^2, ".\n", sep = "")
}
The square of 1 is 1.
The square of 2 is 4.
The square of 3 is 9.
The square of 4 is 16.
The square of 5 is 25.

Storing Results

Let’s store the squares of the first ten whole numbers.

Quick way:

squares <- (1:10)^2
squares
 [1]   1   4   9  16  25  36  49  64  81 100

But let’s also do it with a for-loop.

Storing Results

First, create a numeric vector of length 10, currently empty:

squares <- numeric(10)

Now iterate through a loop:

for (i in 1:length(squares) ) {
  squares[i] <- i^2 # put i-th square in i-th place of squares
}
squares
 [1]   1   4   9  16  25  36  49  64  81 100

This is slower than vectorization, but sometimes is the only way to store results.

A Closer Look

Run this:

storeSquares <- function(n) {
  squares <- numeric(n)
  for (i in 1:n) {
    squares[i] <- i^2
  }
  squares
}
debug(storeSquares)
storeSquares(4)

Practice

This code simulates flipping a fair coin 10 times:

outcomes <- c("Heads", "Tails")
sample(outcomes, size = 10, replace = TRUE)
 [1] "Tails" "Heads" "Heads" "Tails" "Tails" "Heads" "Tails" "Tails" "Tails"
[10] "Heads"

Accomplish the same thing with a for-loop that stores results.

Solution to the Practice Problem

outcomes <- c("Heads", "Tails")
flipResults <- character(10)
for (i in 1:10) {
  flipResults[i] <- sample(outcomes, size = 1)
}
flipResults

Template for Storing Results

results <- numeric(howManyYouWant) # or character() or logical() ...
for (i in 1:howManyYouWant) {
  # Do stuff to get single result
  # ...
  # Then store the result:
  results[i] <- singleResult    
}
results # prints them out
        # (or returns them if this is 
        # the last expression in a function)

If you don’t learn anything else in this Chapter, at least learn this!!

break-ing Out of a Loop

First Try

verboseSearch <- function(elem, vec) {
  for ( i in 1: length(vec) ) {
    if ( vec[i] == elem ) {
      cat("Found ", elem, " at index ", i, ".\n", sep = "")
    } else {
      cat("Checked index ", i, 
          " in the vector.  No match there ...\n", sep = "")
    }
  }
}

Try It Out

verboseSearch("Tin Man", people)
Checked index 1 in the vector.  No match there ...
Found Tin Man at index 2.
Checked index 3 in the vector.  No match there ...
Checked index 4 in the vector.  No match there ...

It seems silly to go on after we found a match!

Patch Using break

The reserved word break gets you out of a loop.

Execution continues after the closing bracket of the loop.

Implement break Patch

verboseSearch <- function(elem, vec) {
  found <- FALSE
  for ( i in 1: length(vec) ) {
    if ( vec[i] == elem ) {
      found <- TRUE
      cat("Found ", elem, " at index ", i, ".\n", sep = "")
      break
    } else {
      cat(
        "Checked index ", i, 
        " in the vector.  No match there ...\n", 
        sep = "")
    }
  }
  if ( found ) {
    cat("Success!")
  } else {
    cat("No match:  sad!")
  }
}

Try It Out

verboseSearch("Tin Man", people)
Checked index 1 in the vector.  No match there ...
Found Tin Man at index 2.
Success!
verboseSearch("Colin", people)
Checked index 1 in the vector.  No match there ...
Checked index 2 in the vector.  No match there ...
Checked index 3 in the vector.  No match there ...
Checked index 4 in the vector.  No match there ...
No match:  sad!

while-Loops

A Flowery Meadow

Scarecrow is walking through a meadow that contains flowers of several colors:

flower_colors <- c("blue", "pink", "crimson")

He picks until he gets two blue flowers. We would like reports of his activities.

Vector To Hold Results

A vector for the :

reports <- character()

The default value for length is 0, so this is an empty character vector:

reports
character(0)

We will add to it.

The Loop

count <- 0
i <- 1
while (count < 2) {
  flower <- sample(flower_colors, size = 1)
  outcome <- paste(
    "On pick ", i, ", Scarecrow got a ",
    flower, " flower.", sep = ""
  )
  reports[i] <- outcome
  i <- i + 1
  if (flower == "blue") count <- count + 1
}

See what happened:

reports
[1] "On pick 1, Scarecrow got a blue flower."   
[2] "On pick 2, Scarecrow got a crimson flower."
[3] "On pick 3, Scarecrow got a crimson flower."
[4] "On pick 4, Scarecrow got a blue flower."   

sample() is Random, So …

… if you try it, your results will likely be different!

Ecapsulate Work Into a Function:

scarecrow_walk <- function(flower_colors, blues_desired) {
  reports <- character()
  count <- 0
  i <- 1
  while (count < blues_desired) {
    flower <- sample(flower_colors, size = 1)
    outcome <- paste(
      "On pick ", i, ", Scarecrow got a ",
      flower, " flower.", sep = ""
    )
    reports[i] <- outcome
    i <- i + 1
    if (flower == "blue") count <- count + 1
  }
  reports
}

Try It

Let’s try it:

pick_record <- scarecrow_walk(
  flower_colors = c("red", "pink", "burlywood", "blue"),
  blues_desired = 5
)

Examine Results

How many flowers did Scarecrow pick? This many:

picks <- length(pick_record)
picks
[1] 19

What Will Happen?

Suppose we ran the function like this:

scarecrow_walk(
  flower_colors = c("red", "pink", "burlywood"),
  blues_desired = 5
)

We have to avoid infinite loops!

Reject Improper Input

scarecrow_walk <- function(flower_colors, blues_desired) {
  ## stop if no blue flowers in meadow:
  if (!("blue" %in% flower_colors)) {
    return(cat("Your field needs blue flowers."))
  }
  ## if we get here, it is safe to proceed:
  reports <- character()
  count <- 0
  i <- 1
  while (count < blues_desired) {
    flower <- sample(flower_colors, size = 1)
    outcome <- paste(
      "On pick ", i, ", Scarecrow got a ",
      flower, " flower.", sep = ""
    )
    reports[i] <- outcome
    i <- i + 1
    if (flower == "blue") count <- count + 1
  }
  reports
}

Guess the Number I

First version, with if:

number <- sample(1:4, size = 1)
guess <- as.numeric(readline("Guess the number (1-4):  "))
# give your number before you run the rest:
if ( guess == number ) {
  cat("Congratulations!  You are correct.")
}

Cuess the Number II

Second Version, with if ... else:

number <- sample(1:4, size = 1)
guess <- as.numeric(readline("Guess the number (1-4):  "))
# give your number before you run the rest:
if ( guess == number ) {
  cat("Congratulations!  You are correct.")
} else {
  cat("Sorry, you were wrong!")
}

Make It Better

Better to let the user guess until correct.

Use a while-loop for this.

The Code

n <- 20
number <- sample(1:n, size = 1)
cat("I'm thinking of a whole number from 1 to ", n, ".\n", sep = "")
guessing <- TRUE
while (guessing) {
  guess <- readline("What's your guess? (Enter q to quit.)  ")
  if ( guess == "q"  ) {
    cat("Bye!\n")
    break
  } else if ( as.numeric(guess) == number ) {
    guessing <- FALSE
    cat("You are correct!  Thanks for playing!")
  }
}

Encapsulate Into Function

guess_my_number <- function(n) {
  number <- sample(1:n, size = 1)
  cat("I'm thinking of a whole number from 1 to ", n, ".\n", sep = "")
  guessing <- TRUE
  while (guessing) {
    guess <- readline("What's your guess? (Enter q to quit.)  ")
    if ( guess == "q"  ) {
      cat("Bye!\n")
      break
    } else if ( as.numeric(guess) == number ) {
      guessing <- FALSE
      cat("You are correct!  Thanks for playing!")
    }
  }
}

Avoid Infinite Loops!

What do you think will happen? (Do not run this.)

countUp <- function(n) {
  i <- 1
  while (i <= n) {
    if (i < n) {
      cat(i, "...\n")
    } else {
      cat(i, "!  All done.", sep = "")
    }
  }
}

How to Fix

Increment the counter:

countUp <- function(n) {
  i <- 1
  while (i <= n) {
    if (i < n) {
      cat(i, "...\n")
      i < i + 1
    } else {
      cat(i, "!  All done.", sep = "")
    }
  }
}

while-loop Summary

while (Boolean condition) {
  ## This is the body of the loop.
  ## Expressions to evaluate go here.
}
  • The reserved word while constructs a loop that runs as long as a specified condition is true.
  • The condition says whether to run the code in the body:
    • If TRUE, you run it;
    • If FALSE, don’t, and go past the loop.
  • To avoid an infinite loop, make sure the condition can become false eventually.