<<

50 | Control flow

All Matlab commands considered thus far have been carried out one after the other, or, if documented in an .m file, line-by-line. This is a hallmark of imperative programming. Nevertheless, it is sometimes required to make certain commands dependent on pre-conditions, or to repeat certain commands many times for which writing them into the code many times would be tedious and error-prone. To take care of these requirements, imperative programming provides the concept of control flow. Control flow can be broken down into the interaction of essentially three components: (1) logical expressions, (2) conditional statements, and (3) loops. Logical expressions test whether certain pre-conditions hold, conditional statements then organize the evaluation of commands based on the conditional statements, and loops take care of carrying out certain commands many times. In Matlab, the most important conditional statements are if-statements, and to a lesser degree switch-statements, while the most important loops are for-loops, and two a lesser degree while-loops. We will discuss each of these concepts in turn.

50.1 Logical expressions

In programming, binary logic, i.e., statements that can be either true or false (but not both or something in between), is represented by the integers 1 and 0, respectively. For example, if two scalar numerical variables x,y have been assigned a value each, one can perform a number of logical tests on them by means of the following logical expressions: x == y Is x equal to y? x ∼= y Is x not equal to y? x < y Is x smaller than y? x > y Is x larger than y? x <= y Is x smaller than or equal to y? x >= y Is x larger or or equal to y? Logical expressions can be combined by and and or operations, for which the symbols & and | are used. For example, to test whether the value of a scalar variable x is smaller than another scalar variable y and also smaller than yet another scalar variable z, we have: (x < y) & (x < z) Is x smaller than y and is x smaller than z? (x < y) | (x < z) Is x smaller than y or x smaller than z? For example, if x = 2, y = 3, and z = 4, the and expression above evaluates to true, and so does the or expression. If, however, x = 2, y = 3, and z = 1, the and expression above evaluates to false, while the or expression still evaluates to true, because x is smaller than y, and the or is non-exclusive. The brackets in the expression above are not necessary, but they help to structure the expression. In addition to the and and or operators & and | Matlab also provides the short-circuit logical operators && and ||. They have the identical meaning as & and |, but operate somewhat more efficiently by only evaluating the second expression, if the statements logical state is not already determined fully determined by the first expression. They also work on different variables: the single operators & and | work on arrays (and scalars, which are arrays with only one entry), while the && and || operators only work on scalars. (x < y) && (x < z) Is x smaller than y and is x smaller than z? (x < y) || (x < z) Is x smaller than y or x smaller than z?

50.2 Conditional statements

Conditional statements offered by Matlab are the if-statement, the if-else-statement, and the switch- statement. We only consider the former two here. if-statements and if-else-statements

The if-statement allows for deciding whether another statement or a group of statements is executed or not. The general form of the if-statement is if condition action end

In the above, a condition is a logical expression that is true and false. The action is a statement, or a group of statements, that will be executed, if the condition is true. If the condition is false, the action will not be executed. The action can be any number of statements until the reserved word end. The action is usually indented for easy code readability. For example, the following if-statement checks to see whether the value of a variable called num is negative. If it is, the value is changed to zero, otherwise, nothing happens: if num < 0 num = 0; end

This may be used, for example, to ensure that the square root function is not used on a negative number. The if-statement as introduced above decides whether a given action is executed or not. Choosing between two actions, or choosing among several actions can be accomplished using if-else and nested if-else statements. The if-else statement is used to choose between two statement or sets of statements. Its general form is: if condition action 1 else action 2 end

First, the condition is evaluated. If it is true, then the set of statements designated as action 1 is executed, and that is the end of the if-else statement. If instead, the condition is false, the second set of statements designated as action 2 is executed and that is the end of the if-else statement. The first set of statements is called the action of the if clause, it will always be executed if the condition is true. The second set of statement is called the action of the else clause, it is what will be executed if the expression is false. One of these action, and only one, will be executed which one depends on the value of the condition. As an example, the following code determines and prints whether or not a random number in the range from 0 to 1 is less than 0.5 if rand < 0.5 disp('Random number was less than 0.5') else disp('Random number was not less than 0.5') end

The if-else statement is used to choose between two actions. To choose among more than two actions, if- else statements can be nested. For example, consider implementing the following continuous mathematical function ( 1 x < −1 f : R → R, x 7→ f(x) := (50.1) x2 x ≥ −1 ∧ x ≤ 2 The value of y is based on the value of x, which can be in one of the possible ranges. Determining the range can be accomplished with three separate if-statements as follows: if x < -1 y = 1; end if x >= -1 && x <= 2 y = xˆ2; end ifx>2 y = 4; end

As the three possibilities are mutually exclusive, the value of x can be determined using three separate if- statements. However, this is not very efficient: all three logical expressions must be evaluated, regardless of the range in which x falls. For example, if x is less than -1, the first expression is true and 1 would be assigned to y. However, the two expressions in the next two if-statements are still evaluated, although it is clear that they will be false, and y has already been assigned a value. Instead of writing it this way, the statements can be nested so that the entire if-else statement ends when an expression is found to be true if x < -1 y = 1; else % if we are here, x must be >= -1. we use an if-else statement to % choose between the remaining two ranges if x <= 2 y = xˆ2; else % no need to check anything, if we are here, x must be > 2 y = 4; end end

By using nested if-else statements to choose from among the three possibilities, not all conditions must be tested as they were in the previous example. In this case, if x is less than -1, the statement to assign 1 to y is executed, and the if-else statement is completed so no other conditions are tested. If, however, x is not less than -1, then the else-clause is executed. If the else-clause is executed, then it is already known that x is greater than or equal to -1 so that part does not need to be tested. Instead there are only two remaining possibilities. So, the action of the else clause was another if-else statement. Although it is long, all of the above code is one if-else statement, a nested if-else statement. It is actually an example of a particular kind of nested if-else statement called a cascading if-else statement. This is a type of nested if-else statement in which the conditions and actions cascade in a stair-like pattern: if x >= 0 ifx<4 disp('a') else disp('b') end else disp('') end

In addition to nested if-else statements, Matlab offers another method for choosing from multiple lines of actions, using the elseif-clause. For example, if there are n choices, the following general form can be used: if condition 1 action 1 elseif condition 2 action 2 elseif condition 3 action 3 . . else action n end

The previous example can be rewritten using the elseif clause, rather than nesting if-else statements, as follows: if x < -1 y = 1 elseif x <= 2 y = xˆ2 else y = 4 end

Note that in this example we only need one end. Thus, there are three ways of accomplishing the original task: using three separate if-statements, using if-else statements, and using and if-statement with elseif clauses, which is presumably the simplest.

50.3 Loops

We next consider statements that allow other statements to be repeated. The statements that do this are called loop-statements. There are two basic loops in programming: counted loops and conditional loops. A counted loop is a loop that repeats statements a specified number of times. Thus, ahead of execution of the loop, it is known how many times the statement are to be repeated. A conditional loop also repeats statements, but ahead of execution of the loop it is not known how many times the statements will need to be repeated. The statements that are repeated in any loop are called actions of the loop. As noted above, there are two different loop statements in Matlab: the for-loop and the while-loop. In practice, the for-loop is used as a counted loop and the while-loop is usually used as a conditional loop.

For-loops

A for-loop is used when it is necessary to repeat statements in a script and when it is known ahead of time how many times the statements will be repeated. The statements that are repeated are called the action of the loop. For example, it may be known that the action of the loop will be repeated five times. The terminology used is that we iterate through the action of the loop five times. The variable that is used to iterate through values is called a loop variable, iterator variable, or iteration counter. For example, the variable might iterate through the integers 1 to 5 (i.e., 1,2,3,4 and 5). Although, in general, variable names should be mnemonic, it is common in many languages for an iterator variable to be given the name i, and if more than one iterator variable is needed use j, k, and so on. This is historically due to the naming of integer variables√ in FORTRAN. Notably, Matlab has inbuilt functions i and j, which return the complex number unit −1. This may occasionally lead to trouble with using an iterator variable i or j. However, usually, initialization of the iterator variable will override the Matlab in-built function and no problems occur. The general form of a for-loop is for loopvar = range action end where loopvar denotes the loop variable, range is the range of value through which the loop variable iterates, and the action of the loop consists of all statements up to the end. Like for if-statements, the action is indented for readability. The range can be specified using any vector, but normally the easiest way to specify the range of values is to use the colon operator. As an example, consider printing a column of numbers from 1 to 5: for i = 1:5 fprintf('%d\n',i) end

Here, what the for statement accomplished was to print the value of i and then the newline character for every value of i, from 1 through 5 in steps of 1. The first thing that happens is that i is initialized to have the value 1. Then the action of the loop is executed, i.e., fprintf command prints the value of i (i.e., 1) and the newline character to move the cursor down. Then i is incremented to have the value 2. Next, the action of the loop is executed, which prints 2 and the newline. This is repeated for the third to fifth iteration of the loop. The final value of i is 5, this value can be used in further statements once the loop has finished. In the example above, the value of the loop variable was used in the action of the for-loop: it was printed. It is not always necessary to actually use the value of the loop variable, however. Sometimes the variable is simply used to iterate, or repeat, an action a specified number of times. For example, for i = 1:3 fprintf('Programming is fun! \n') end prints the statement Programming is fun! and the newline character three times. The variable i is needed to repeat the action three times, even though the value of i is not used in the action of the loop. For-loops can be used to write values at specific entries into vectors. As a basic example, consider creating a vector containing the squares of the integers 1,2,3,4,5, in this order, using a for-loop. There are two approaches to this: either we initialize the vector as an empty array and concatenate the integers to this iteratively, or we pre-allocated the vector to NaNs and write into the vector directly. The latter method is usually preferred. The first approach takes the following form: % initialize the vector to an empty array v = []; for i = 1:5 % concatenate new squared integer and previous version of vector v = [v; iˆ2]; end

The second approach takes the following form: v = NaN(5,1); % iterate over integers 1,2,3,4,5 for i = 1:5 v(i) = iˆ2; end

Note that in the first case, the size of the resulting vector does not have to be pre-specified. This can sometimes be useful. However, in the second case more code control and safety is maintained by pre-initializing the vector in the correct size. The action of a loop can be any valid set of statements. When the action of a loop is another loop, this is called a nested loop. The general form of a nested for-loop is as follows:

% outer loop for loopvarone = rangeone % action one including the inner loop for loopvartwo = rangetwo actiontwo end end

The first for-loop is called the outer loop, the second for-loop is called the inner loop. The action of the outer loop consists (at least in part, there could be other statements) of the entire inner loop. In other words: on every iteration of the outer loop, all iterations of the inner loop are executed. As an example, a nested for-loop is used below to fill the entries of a 3 × 4 matrix with the integers from 1 to 3 · 4 = 12 in a row-wise fashion:

% define number of rows and columns n row = 3; n col = 4;

% initialize matrix to NaN’s and matrix entries A = NaN(n row, n col); a = 1;

% iterate over rows for i = 1:n row

% for a fixed row, iterate over columns for j = 1:n col

% fill in matrix element A(i,j) = a; % update matrix element for the next location a = a + 1;

end end

Note that the outer loop iterates over the rows of the matrix. It starts with row i = 1 and then carries out the action set of the outer for-loop, which in this case is the inner for-loop. With row index set constantly to i = 1, the inner now executes all its statements. On the first iteration of the inner for loop, the iteration variable j is set to 1, and the matrix element that will be filled in is thus aij = a11. This element is set to a = 1. Next, the matrix entry variable is incremented by +1, i.e. a is set to a = 1+1 =2. Next, the iteration variable of the inner for loop is increased to j = 2, and thus the matrix element in the first row, second column will be filled in next with a = 2. Again, upon this, a is increased by +1 yielding a = 2 + 1 = 3. The inner for loop continues up to j=4. Only then the outer for loop starts with the second iteration and the iteration variable i over rows is set to i=2. Again the inner for loop is executed, and so on. The statements within a nested loop can be any valid statements, including any selection statements such as if, if-else, or switch-statements. As an example, consider the case that in addition to filling in the matrix elements of the matrix above with the integers from 1 to 3 · 4 = 12, the elements on the leading diagonal, i.e., those elements for which the row and column indices are identical, are set to zero. This may accomplished using the following combination of nested for loops and if-statements:

% define number of rows and columns n row = 3; n col = 4;

% initialize matrix to NaN's A = NaN(n row, n col);

% initialize matrix entries a = 1;

% cycle over rows for i = 1:n row

% for a fixed row, cyle over columns for j = 1:n col

% single out the leading diagonal elements if j == i A(i,j) = 0; else

% fill in the off-diagonal matrix elements A(i,j) = a; end % update matrix element a = a + 1; end end

In summary, for-loops are a highly versatile programming tool that are ideal to achieve one of the fundamental goals of : carrying out tedious computing tasks many, many times in a highly controlled and precise manner.

While-loops

The while-statement, the conditional loop offered by Matlab, is used to repeat an action in the case that ahead of execution of the loop it is not known how many times the action will have to be repeated. The general form of the while-statement is while condition action end

The action, which consists of any number of statements, is executed as long as the condition is logically true. Specifically, first the condition is evaluated. If it is logically true, the action is executed. Thus, on the first iteration, the while statement behaves like an if-statement. However, then, the condition is evaluated again. If it is still true, the action is executed again. Then, the condition is evaluated again. If it is still true, the action is executed again. Then, the condition is ... Eventually, something in the action should change something in the condition thus that it becomes false to avoid an infinite loop. In fact, a common bug when using while-loops is that theire execution does not end automatically. In this case Ctrl-C stops execution of the program and thus also the while-loop. As an example of a conditional loop, we consider a program that will find the first factorial that is greater than a pre-specified variable. Recall that the factorial n! of a natural number n ∈ N is defined as the product of all numbers from 1 to n, i.e. n! = 1 · 2 · 3 ··· n. To make the difference to for-loops transparent, we first write a version that evaluates the factorial of a given integer.

% argument of the factorial n = 5;

% initialization of the factorial 0! = 1 fac = 1; for i = 1:n fac = i∗fac end

In the case of the for-loop, we know how many times the action statement has to be executed in order to yield the desired result upon completion of the loop. To find the first factorial that is greater than a pre-specified variable, which we call high, we use the following algorithm with two variables: one variable iterates through the values 1,2,3, and so on, and another variable stores the factorial of the iterator at each step. We start with 1, and 1 factorial, which is 1. We next check the factorial. If it is not greater than the variable of interest high, the iterator variable will increment to 2, and we evaluate its factorial. If this is not greater than high, the iterator will increment to 3, and its factorial is evaluated. This continues until the first factorial that is greater than high is observed:

% define value of interest high = 110; % initialize the iteration variable i = 0;

% initialize the factorial of the iteration variable 0! = 1 fac = 1; while fac <= high

% increase the iteration variable i = i+1;

% evaluate the factorial of the current iteration variable fac = fac∗i; end

% save the final factorial that is greater than high in a new variable facgth = fac;

The iterator variable i is initialized to 0, and the running product variable fac, which will store the factorial of each value of i, is initialized to 1. The first time the while-loop is executed, the condition is true: 1 is less than 110. So the action of the loop is executed, which is to increment i to 1 and fac becomes 1 (1∗1). After the execution of the action loop, the condition is evaluated again. Since it will still be true, the action is executed again: i is incremented to 2, and fac will get the value 2 (1∗2). The value 2 is still smaller than 110, so the action will be executed again: i will be incremented to 3, and fac will get the value 6 (2∗3). This continues until the first value of fac is reached that is greater than 110. As soon as fac gets to this value, the condition will be false and the will end. At that point the factorial is assigned to the new variable facgth. In functional neuroimaging practice, while-loops are often encountered in experimental programs that repeat certain statements until a specific condition is fulfilled, for example, the fMRI scanner starts recording, or a participant enters a required response. An example in this direction is the following. inputnum = input('Enter a positive number: '); while inputnum <= 0 inputnum = input('Invalid! Please enter apositive number:'); end fprintf('Thanks, you entered a %.0f \n', inputnum)

A second example in the functional neuroimaging context are numerical approaches for finding zeros of nonlinear functions, or similar numerical optimization problems. The following code implements the Newton-Raphson method for finding the square root of a positive number a ∈ R by evaluating the zero of a function defined as f(x) := x2 − a:

% variable for which to find the square root a = 2;

% convergence criterion vareps = 1e-3;

% initialize square root approximation sqrt a = 2;

% while loop while (sqrt a - sqrt(a)) > vareps

sqrt a = sqrt a - ((sqrt aˆ2 - a)/(2∗sqrt a)); end

% write final approximation into new variable sqrt a final = sqrt a;