Lab 09: Loops

Repeating Code

We’ve seen how to control which lines of code are ran with conditionals.

Today, we’ll review how to control repetition of lines of code.

A Simple Problem

Suppose you have a vector with thousands of integers.

You’d like to find the indices of your favorite number, but you can’t use vectorization or loops.

If you’re familiar with functions, you could use recursion.

num_indices <- function(vec, num) {
  if (!length(vec)) {
    NULL
  } else if (head(vec, 1) == num) {
    c(1, 1 + num_indices(tail(vec, -1), num))
  } else {
    c(1 + num_indices(tail(vec, -1), num))
  }
}

Iterative Instead of Recusrsive

Recursive solutions are often elegant but also difficult to write.1

For this reason, we introduce an alternate approach: iterative solutions.

Iterative solutions contain a block of code that is directly repeated.

We call the repeated code the loop.

Beyond Conditional Statements

We’ve seen a series of statements (if, else, else if) for conditionally executing bodies of code.

We’ll now introduce another series of statements for repeatedly executing bodies of code (looping).

  • while: execute its body while a condition holds true.

  • for: execute its body for each object in a specified vector.

  • repeat: execute its body repeatedly and unconditionally.

The while Statement

The while statement is similar in form and behavior to if:

while(LOGICAL) <body>

where <body> is executed while LOGICAL returns TRUE,

as if an if statement that repeats until LOGICAL == FALSE,

if(LOGICAL) <body> if(LOGICAL) <body> ...

The while Statement: Example 1

Here we count up from from to to, printing the numbers along the way.

count_up <- function(from, to) {
  while (from <= to) {
    cat("...", from, sep = '')
    from <- from + 1
  }
}
count_up(1, 5)
...1...2...3...4...5

The while Statement: Example 2

Using a similar idea, we can recreate a (simplified) version of seq.

while_seq <- function(from, to, by) {
  s <- vector(typeof(from))
  while (from <= to) {
    s <- append(s, from)
    from <- from + by
  }
  s
}
while_seq(3, 12, 2)
[1]  3  5  7  9 11

For which input will this function not work?

Favorite Number with while

Let’s rewrite num_indices with a while loop.

num_indices <- function(vec, num) {
  indices <- integer(); i <- 1
  while (i <= length(vec)) {
    if (vec[i] == num) {
      indices <- append(indices, i)
    }
    i <- i + 1
  }
  indices
}

The for Statement

for (<variable> in <vector>) <body>

where <body> is executed once for each object in <vector>.

Inside <body>, we refer to the currently used object by <variable>1.

for (i in 1:7) {
  cat("i:", i, '\t')
}
i: 1    i: 2    i: 3    i: 4    i: 5    i: 6    i: 7    

for Makes Copies

We cannot modify <vector> by reassigning <variable>.

x <- 1:7
for (num in x) {
  num <- 0
}
x
[1] 1 2 3 4 5 6 7

This is because <variable> is a copy of the value in the vector. Refer to the original object instead:

x <- 1:7
for (i in 1:length(x)) {
  x[i] <- 0
}
x
[1] 0 0 0 0 0 0 0

for Statement Variables

You are free to name <variable> anything.

  • In some cases, a trivial name (e.g., i for index or n for number) may be sufficient.
  • Other times, you you may want descriptive names for clarity.

Some students always use i - please don’t do this!

for (i in starwars$eye_color) {
  # ... BAD! Doesn't express the meaning of the object.
}
for (color in starwars$eye_color) {
  # ... GOOD! The meaning of the object is obvious to the reader. 
}

Favorite Number with for

Let’s rewrite num_indices with a for loop.

num_indices <- function(vec, num) {
  indices <- integer()
  for (i in 1:length(vec)) {
    if (vec[i] == num) {
      indices <- append(indices, i)
    }
  }
  indices
}

seq_along()

Notice that to get a vector of the indices, we wrote 1:length(vec).

The idiomatic way of doing this is with the seq_along() function:

num_indices <- function(vec, num) {
  indices <- integer()
  for (i in seq_along(vec)) {
    if (vec[i] == num) {
      indices <- append(indices, i)
    }
  }
  indices
}

The repeat Statement

repeat <body>

where <body> is executed repeatedly and unconditionally until a break statement is encountered1.

break statement?

The break statement can be used in any loop (for, while, repeat) to immediately stop its execution.

The last iteration of the loop’s body is not completed. The body of the loop is immediately exited on encountering break.

all_until <- function(vec, stop_value) {
  v <- vector(typeof(vec)) 
  for (obj in vec) {
    if (obj == stop_value) {
      break
    }
    v <- append(v, obj)
  }
  v
}
all_until(c(1:5, 50:45), 48) # all values until 48 is reached
[1]  1  2  3  4  5 50 49

The repeat Statement: Example

print_hesitantly <- function(sentence, times) {
  cutoff <- sample(1:nchar(sentence))
  to_repeat <- substr(sentence, 1, cutoff)
  repeat {
    cat(to_repeat, '...'); times <- times - 1
    if (times <= 0) {
      break
    }
  }
  cat(substr(sentence, cutoff + 1, nchar(sentence)))
}
print_hesitantly("I like you", 5)
I like ...I like ...I like ...I like ...I like ... you

Favorite Number with repeat

The favorite number problem. Notice the similarity to the while solution.

num_indices <- function(vec, num) {
  indices <- integer(); i <- 1
  repeat {
    if (i > length(vec)) { # the opposite of the while condition
      break
    }
    if (vec[i] == num) {
      indices <- append(indices, i)
    }
    i <- i + 1
  }
  indices
}