Skip to content

Latest commit

 

History

History
407 lines (283 loc) · 12.6 KB

lab08-simple-loops.md

File metadata and controls

407 lines (283 loc) · 12.6 KB

Lab 8: Simple Loops

Gaston Sanchez

Learning Objectives

  • Forget about vectorized code (pretend it doesn't exist)
  • Practice writing simple loops
  • Get familiar with the syntax of a for loop
  • Get familiar with the syntax of a while loop
  • Get familiar with the syntax of a repeat loop
  • Encapsulate loops inside a function call

Introduction

The majority of functions that work with vectors are vectorized. Remember that vectorized operations are calculations that are applied to all the elements in a vector (element-wise operations).

In order to learn about loops and iterations, it's good to forget about vectorized operations in R. This means that you will be asked to write code, using some sort of loop structure, to perform tasks for which there is already a vectorized implementation. For example, in this lab you will have to write code with various types of loops to calculate the mean of a numeric vector. This can easily be done using the function mean(). But we don't want you to use mean(). We want you to think about control-flow structures, which are essential in any programming activity.

For loops

Let's start with a super simple example. Consider a vector vec <- c(3, 1, 4). And suppose you want to add 1 to every element of vec. You know that this can easily be achieved using vectorized code:

vec <- c(3, 1, 4) 

vec + 1
## [1] 4 2 5

In order to learn about loops, I'm going to ask you to forget about the notion of vectorized code in R. That is, pretend that R does not have vectorized functions.

Think about what you would manually need to do in order to add 1 to the elements in vec. This addition would involve taking the first element in vec and add 1, then taking the second element in vec and add 1, and finally the third element in vec and add 1, something like this:

vec[1] + 1
vec[2] + 1
vec[3] + 1

The code above does the job. From a purely arithmetic standpoint, the three lines of code reflect the operation that you would need to carry out to add 1 to all the elements in vec.

From a programming point of view, you are performing the same type of operation three times: selecting an element in vec and adding 1 to it. But there's a lot of (unnecessary) repetition.

This is where loops come very handy. Here's how to use a for () loop to add 1 to each element in vec:

vec <- c(3, 1, 4)

for (j in 1:3) {
  print(vec[j] + 1)
}
## [1] 4
## [1] 2
## [1] 5

In the code above we are taking each vec element vec[j], adding 1 to it, and printing the outcome with print() so you can visualize the additions at each iteration of the loop.

Your turn: rewrite the for loop in order to triple every element in vec, and printing the output at each step of the loop:

vec <- c(3, 1, 4) # Change this value!

for (j in c()) { # Replace c() with an appropriate sequence.
  # Fill in.
  
}

What if you want to create a vector vec2, in which you store the values produced at each iteration of the loop? Here's one possibility:

vec <- c(3, 1, 4)  # Change this value!
vec2 <- rep(0, length(vec))  # "empty" of zeros vector to be filled in the loop

for (i in c()) {# Replace c() with an appropriate sequence.
  # Fill in.
}

Summation Series

Write a for loop to compute the following two series. Your loop should start at step k = 0 and stop at step n. Test your code with different values for n. And store each k-th term at each iteration. Does the series converge as n increase?

series1

$$ \sum_{k=0}^{n} \frac{1}{2^k} = 1 + \frac{1}{2} + \frac{1}{4} + \frac{1}{8} + \dots + \frac{1}{2^n} $$

series2

$$ \sum_{k=0}^{n} \frac{1}{9^k} =1 + \frac{1}{9} + \frac{1}{81} + \dots + \frac{1}{9^n} $$

Arithmetic Series

Write a for loop to compute the following arithmetic series an = a1 + (n − 1)d when a1 = 3, and d = 3. For instance: 3 + 6 + 12 + 24 + ….

arithmetic series

an = a1 + (n − 1)d

Test your code with different values for n. And store each n-th term at each iteration. Does the series converge as n increase?

Geometric Sequence

A sequence such as 3, 6, 12, 24, 48 is an example of a geometric sequence. In this type of sequence, the n-th term is obtained as:

geometric sequence

an = a1 × rn − 1

where: a1 is the first term, r is the common ratio, and n is the number of terms.

Write a for loop to compute the sum of the first n terms of: 3 + 6 + 12 + 24 + …. Test your code with different values for n. Does the series converge as n increase?


Sine Approximation

Consider the following series that is used to approximate the function sin(x):

sine approximation

$$ sin(x) \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \dots $$

Write a for loop to approximate sin(x). Try different number of terms, n = 5, 10, 50, 100. Compare your loop with the sin() function.


For loop with a matrix

Consider the following matrix A:

A <- matrix(1:20, nrow = 5, ncol = 4)
A
##      [,1] [,2] [,3] [,4]
## [1,]    1    6   11   16
## [2,]    2    7   12   17
## [3,]    3    8   13   18
## [4,]    4    9   14   19
## [5,]    5   10   15   20

Say we want to add 1 to all elements in row 1, add 2 to all elements in row 2, add 3 to all elements in row 3, and so on. To do this without using vectorized coe, you need to work with two nested for() loops. One loop will control how you traverse the matrix by rows, the other loop will control how you traverse the matrix by columns. Here's how:

# empty matrix B
B <- matrix(NA, nrow = 5, ncol = 4)

# for loop to get matrix B
for (i in 1:nrow(A)) {
  for (j in 1:ncol(A)) {
    B[i,j] <- A[i,j] + i
  }
}

B
##      [,1] [,2] [,3] [,4]
## [1,]    2    7   12   17
## [2,]    4    9   14   19
## [3,]    6   11   16   21
## [4,]    8   13   18   23
## [5,]   10   15   20   25

Your turn

Consider the following matrix X:

set.seed(123)
X <- matrix(rnorm(12), nrow = 4, ncol = 3)
X
##             [,1]       [,2]       [,3]
## [1,] -0.56047565  0.1292877 -0.6868529
## [2,] -0.23017749  1.7150650 -0.4456620
## [3,]  1.55870831  0.4609162  1.2240818
## [4,]  0.07050839 -1.2650612  0.3598138

Write code in R, using loops, to get a matrix Y such that the negative numbers in A are transformed into squared values, while the positive numbers in A are transformed into square root values


Dividing a number by 2 multiple times

The following examples involve dividing a number by 2 until it becomes odd.

Using a repeat loop

# Divide a number by 2 until it becomes odd.
val_rep <- 898128000 # Change this value!

repeat {
  print(val_rep)
  if (val_rep %% 2 == 1) { # If val_rep is odd,
    break                  # end the loop.
  }
  val_rep <- val_rep / 2 # Divide val_rep by 2 since val_rep was even.
  # When the end of the loop is reached, return to the beginning of the loop.
}
## [1] 898128000
## [1] 449064000
## [1] 224532000
## [1] 112266000
## [1] 56133000
## [1] 28066500
## [1] 14033250
## [1] 7016625

Using a while Loop

# Divide a number by 2 until it becomes odd.
val_while <- 898128000 # Change this value!

while (val_while %% 2 == 0) { # Continue the loop as long as val_while is even.
  print(val_while)
  val_while <- val_while / 2
}
## [1] 898128000
## [1] 449064000
## [1] 224532000
## [1] 112266000
## [1] 56133000
## [1] 28066500
## [1] 14033250
print(val_while)
## [1] 7016625

Make a reduce() function

Now generalize the above code to create a function reduce() which performs the same operation. (You should change very little.)

# your reduce() function
reduce <- function(x) {
  # Fill in.
  
}

reduce(898128000)

Average

The average of n numbers x1, x2, …, xn is given by the following formula:

arithmetic mean

$$ \bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i = \frac{x_1 + x_2 + \dots + x_n}{n} $$

Write R code, using each type of loop (e.g. for, while, repeat) to implement the arithmetic mean of the vector x = 1:100

Standard Deviation

The sample standard deviation of a list of n numbers x1, x2, …, xn is given by the following formula:

standard deviation

$$ SD = \sqrt{ \frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})^2 } $$

Write R code, using each type of loop (e.g. for, while, repeat) to implement the sample standard deviation of the vector x = 1:100

Geometric Mean

The geometric mean of n numbers x1, x2, …, xn is given by the following formula:

geometric mean

$$ \bar{x} = \left ( \prod_{i=1}^{n} x_i \right )^{1/n} $$

Write R code, using each type of loop (e.g. for, while, repeat) to implement the geometric mean of the vector x = 1:50


Distance Matrix of Letters

The following code generates a random matrix distances with arbitrary distance values among letters in English:

# random distance matrix
num_letters <- length(LETTERS)
set.seed(123)
values <- sample.int(num_letters) 
distances <- values %*% t(values)
diag(distances) <- 0
dimnames(distances) <- list(LETTERS, LETTERS)

The first 5 rows and columns of distances are:

distances[1:5, 1:5]
##     A   B   C   D   E
## A   0 160  80 168 184
## B 160   0 200 420 460
## C  80 200   0 210 230
## D 168 420 210   0 483
## E 184 460 230 483   0

Consider the following character vector vec <- c('E', 'D', 'A'). The idea is to use the values in matrix distances to compute the total distance between the letters: that is from E to D, and then from D to A:

# (E to D) + (D to A)
483 + 168
## [1] 651

Hence, you can say that the word 'E' 'D' 'A' has a value of 651.

Your Turn: Write a function get_dist() that takes two inputs:

  • distances = the matrix of distance among letters.
  • ltrs = a character vector of upper case letters.

The function must return a numeric value with the total distance. Also, include a stopping condition---via stop()---for when a value in ltrs does not match any capital letter. The error message should be "Unrecognized character"

Here's an example of how you should be able to invoke get_dist():

vec <- c('E', 'D', 'A')
get_dist(distances, vec)

And here's an example that should raise an error:

err <- c('E', 'D', ')')
get_dist(distances, err)

Test your function with the following character vectors:

  • cal <- c('C', 'A', 'L')
  • stats <- c('S', 'T', 'A', 'T', 'S')
  • oski <- c('O', 'S', 'K', 'I')
  • zzz <- rep('Z', 3)
  • lets <- LETTERS
  • a vector first with letters for your first name, e.g. c('G', 'A', 'S', 'T', 'O', 'N')
  • a vector last for your last name, e.g. c('S', 'A', 'N', 'C', 'H', 'E', 'Z')

Your turn: Assuming that you already created the objects listed above, now create an R list strings like this:

# use your own 'first' and 'last' objects
strings <- list(
  cal = cal,
  stats = stats,
  oski = oski,
  zzz = zzz,
  lets = lets,
  first = first,
  last = last
)

Write a for() loop to iterate over the elements in strings, and compute their distances. At each iteration, store the calculated distances in a list called strings_dists; this list should have the same names as strings.

How does your list strings_dists look like?