Skip to main content

Chapter 5 Boolean Statements, Loops and Branching

The basic computer science structures of if statements, while and for loops are crucial in scientific computing. In this chapter, we cover the basics of these structures in Julia. We will see all of these in more context in later chapters, but here’s the syntax and basics.
Lastly, because of the syntax of a for loop, we show details about ranges in Julia, which is a compact way to write a set of numbers that are either sequential or sequential with skips in it.

Section 5.1 Boolean values and if statements

A boolean value is something that is either true or false. These are built-in constants in Julia. Sometimes we will want to know if a statement is true or false, but generally, we will use them in other structures.
We often use boolean to test various conditions. For each, testing equality using ==, or comparison of numbers we use <, <=, >, >= for less than, less than or equal, greater than and greater than or equal respectively.
If we set x=3 and then can just type x==3, x < 3 , x > 3 to test a variety of comparisons.
In addition, we can test if a boolean value does not have some value using the != and ! operators. For example using the same example x=3 as above, then we can test that x is not 4 with x != 4. If a variable is a boolean, like b = true, the operator that changes true to false or vice versa with !b.

Subsection 5.1.1 Compound boolean statements

We often want to test multiple boolean statements and can build up compound ones with either the and (using the code &&) or or (using the code ||) operators. Recall the following table for && and ||
Table 5.1. Truth Table
AND T F OR T F
T T F T T T
F F F F T F
If we have x=3 and y=10, if we want to test that x is greater than 0 and y is 5, by
x >= 0 && y==5
which will return false, since only the first is true and both must be true for this compound statement to be true. However,
x >= 0 || y==5
returns true, because the first is true.
In both of these examples, it is important to note the order of operations or operator precedence. This was mentioned in Section 2.5, however as the number of operators grows, it’s important to know the precedence. In these cases the tests ==,<=,<,>=,> have precedence over && and ||.
Also, && has precedence over || in that if we evaluate
x >=0 && y > 7 || y == 5
results in true. You can think of this resulting in true && true || false and because of precedence the first pair is tested (to be true) then the result true || false results in true.
Often when precedence is unclear, adding parentheses can be helpful. Instead, perhaps write the above as:
(x >=0 && y > 7) || y == 5

Subsection 5.1.2 Range Boolean Operator

There are often situations in mathematics where we write a range of values like \(1 \leq x \leq 8.5\text{.}\) We could write in using a compound operator like 1 <= x && x <= 8.5, but Julia has a nice operator that handles this as one like 1 <= x <= 8.5.
For example if x=5, y= -1, z= 10 then

Section 5.2 If Statements

An if statement is used to do different things depending on the value of a variable. A standard example of this is the piecewise version of the absolute value. Mathematically, we write:
\begin{equation*} |x|=\begin{cases} x & x \geq 0 \\ -x & x < 0 \end{cases} \end{equation*}
We could write this as a function as
function absValue(x::Real)
  if x >= 0
     return x
  end
  return -1*x
end
Evaluate absValue(3) and absValue(-7) to ensure this is returning expected results.
Notice that we basically had two situations here, either \(x\) was greater than or equal to 0 or else not. We can rewrite this use an if-else statement.
function absValue(x::Real)
  if x >= 0
    return x
  else
    return -1*x
  end
end
Try entering this and seeing if the results are the same.
The absolute value function is quite important in mathematics and thus is built-in to Julia as abs.

Subsection 5.2.1 Further choices with if statements

You may need more than 2 choices on an if statement. Recall the quadrants of the \(xy\)-plane start at I for the upper right and increase as you traverse counterclockwise. A function could be written:
function quadrant(x::Real,y::Real)
  if x > 0 && y > 0
    return "I"
  elseif x < 0 && y > 0
    return "II"
  elseif x < 0 && y < 0
    return "III"
  elseif x > 0 && y < 0
    return "IV"
  else
    return "NONE"
  end
end
and technically if you are on an axis, then you are not in a quadrant, so that is the reason for the last option.
Check Your Understanding 5.2.
Evaluate the function quadrant at the following points and see if the results are as expected.

Subsection 5.2.2 Indentation in if statements

We mentioned in Chapter 4 that spacing in functions doesn’t matter and also doesn’t in if statement either. Note this is different in other languages such as Python. Throughout this text, statement within other blocks (like functions or if statements or we will see loops below) are indented 2 spaces. The number of spaces doesn’t matter and many others prefer 4 spaces, but it is helpful to understand which statements are within the if block or the else block.

Subsection 5.2.3 Using Short-Circuiting for simple branches

Sometimes an if statement has one element in its block. In cases like this, we can shorten it. Here’s a for loop (that we will see that below)
for n=1:8
	@show n
	if n%2 == 0
		println("$n is even")
	end
end
We are going to replace the if statement with n%2 == 0 && println("$n is even") to the following:
for n=1:8
  @show n
  n%2 == 0 && println("$n is even")
end
The reason this works is that the boolean and (&&) evaluates using short-circuiting. If the left side of && evaluates to true, then the right side is then evaluated. However if the left side is false, then the statement short-circuits and skips the right side evaluation.
An alternative using the or (||) short-circuit. Consider the isOdd method we wrote in Chapter 4 in which we check if an integer is odd. We may want to restrict the input to nonnegative numbers and can with
function isOdd(n::Integer)
  n >=0 || error("The input $n must be non negative")
  n%2 == 1
end
Line #2 translates to n is greater than or equal to 0 or print an error. The left side of the || is always evaluated. If it is true then the right side is not evaluated. This is similar to
function isOdd(n::Integer)
  if !(n >=0)
    error("The input $n must be non negative")
  end
  n%2 == 1
end
This example using the || short-circuit to check an argument. We will show this more extensively in Chapter 11. However, it can be used anywhere. In short, if you have a simple if statement and you want to check the condition is true, you can replace it with the cond && statement . Alternatively, if you want to check that a condition is not true, then cond || statement can be used.
One can also replace an if-else statement with a short version of it. This is discussed in the next section.

Section 5.3 Ternary if-then-else

A common use of an if-then-else statement is to assign a value to a variable (or return from a function) depending on some condition. There is a compact way to do this with a ternary if-then-else statement that has the form:
condition ? value_if_condition_is_true : value_if_condition_is_false
which returns value_if_condition_is_true if condition is true otherwise value_if_condition_is_false is returned. The absolute value example above can be written as a single line:
absVal2(x::Real) = x >= 0 ? x : -1*x
and once you practice with this, it will be easy to read and much shorter (1 line versus 7). Be careful with the syntax of this. It is required that the expressions around the ? be padded with spaces to parse correctly. The error is reasonably clear if you don’t write it correctly.
Alternatively, you can write this as
absVal3(x::Real)=ifelse(x >=0, x, -1*x)
The ifelse is not used in this book, preference goes to the ? : format instead--which is available in almost all modern languages as well.
The ternary if-then-else is useful if you want to store a value that has a branching condition and that branch is relatively simple. If you have more complicated logic or need to call a function, this is where a if-then or if-then-else statement would be used.

Check Your Understanding 5.3.

Write the recursive factorial function above using the ternary if-then-else.
You can nest the ternary if-then-else. For example, the following will perform the same as the quadrant function above.
function quadrant2(x::Real, y::Real)
  x > 0 && y > 0 ? "I" :
    x < 0 && y > 0 ? "II" :
    x < 0 && y < 0 ? "III" :
    x > 0 && y < 0 ? "IV" : "NONE"
end
Note that although this can be written on a single line (it is a single nested statement), it has been split on lines for clarity. Also, the precedence order is that : is higher that ? in order for this to evaluate as expected without parentheses. Also, since this is one line, the return statement is not added since the result of this will be the element returned.

Section 5.4 Loops

A loop is a series of statements that are repeated either a fixed number of times or until a condition occurs. They can be very helpful if a large number of operations need to be done in a predictable manner.

Subsection 5.4.1 While Loops

Another very common construction for programming is called a while loop. Basically, we want to run a few statements while some boolean statement is true. Here’s a simple, but uninteresting example:
let
  local n=1
  while n < 10
    @show n
    n+=1
  end
end
and note that the expression n+=1 is shorthand for n=n+1. We are using the @show macro which dumps the expression and its value to the screen. Also, we are using a let block to just encapsulate the variables.
A more practical example of a for loop will be the Bisection Method for finding a root.
function bisection(f::Function, a::Real, b::Real)
	local c
	while (b-a) > 1e-6
		c = 0.5*(a+b)  # find the midpoint
		# test if f(a) and f(c) have opposite signs to determine the new interval
		(a,b) = f(a)*f(c) < 0 ? (a,c) : (c,b)
	end
	c
end
In short, this method takes a function \(f\) and an interval \([a,b]\) and continually bisects it ensuring there is a root in the resulting interval. It continues while the length of the interval is greater than 1e-6 or \(10^{-6}\text{.}\) We use a tuple to handle the interval and on line 6, we have used a ternary if-then-else to first test which interval to use and then return either the left or right hand subintervals.
To test it, consider
f(x) = x^2-2
which has a root of \(\sqrt{2}\text{.}\) The function call
bisection(f,1,2)
returns 1.4142141342163086, which is approximately \(\sqrt{2}\text{.}\)
Note that we used a ternary if-then-else in the bisection function in conjunction with a tuple to update both points in the interval. This makes the function reasonably simple. A standard if-then-else statement can be used instead if desired.
This was used as a practical example and we will explore this and other rootfinding techniques in Chapter 10.

Subsection 5.4.2 Bailing out of a While loop

Generally the way to stop a while loop is in the test condition. However, another nice way to do this is using the break statement. Consider the following.
let
	n = 1
	while n < 10
		@show n
		if n > 5
			break
		end
		n += 1
	end
end
and the result will be the integers 1 to 6 shown. Note that if the if n > 5 block is not present then you will get the integers up to 10. (try this by commenting out lines 5-7) and rerunning. With this statement as soon as n gets larger than 5, then the execution is jumped out of the loop.
We can often shorten this using the shortcut if statement shown above, replacing the if n > 5 block with n > 5 && break as in
let
  n = 1
  while n < 10
    @show n
    n > 5 && break
    n += 1
  end
end
and recall that if n > 5on line 5 is executed and when false then the statement is short-circuited and the second part (break) is not executed, however when n > 5 is true, then the second part is executed and the while loop is broken out of. Consider
let
n = 1
while true
@show n
n > 5 && break
n += 1
end
end

Subsection 5.4.3 A while true loop

Notice that with this whileloop that the condition of the loop n < 10 is never false and therefore we don’t need that condition, however we do need something and that is often to replace the condition with true, which seems like a bad idea, but consider:
let
  n = 1
  while true
    @show n
    n > 5 && break
    n += 1
  end
end
For while loops like this, the while true basically runs the loop indefinitely. To ensure that the loop stops, make sure that there is something like line 5 that breaks out of the loop.
Check Your Understanding 5.4.
Rewrite the bisection method above using a while true and use the short-circuit with the break statement to ensure there is an exit from the loop.
Answer.
function bisection(f::Function, a::Real, b::Real)
  local c
  while true
    c = 0.5*(a+b)  # find the midpoint
    # test if f(a) and f(c) have opposite signs to determine the new interval
    (a,b) = f(a)*f(c) < 0 ? (a,c) : (c,b)
    b-a && break
    n += 1
  end
  c
end

Subsection 5.4.4 Skipping statements in a loop

In the previous section, we used the break statement to break out of a loop. An alternative is if there is some lines of code that we wish to skip in the loop we could put them in an if statement, however another nice way to handle this is to use the continue statement. We’ll show this with an example
The following will produce the numbers 1 to 6:
let
  n = 1
  while true
    n > 6 && break
    @show n
    n += 1
  end
end
Let’s say we want to not print the odd numbers. Although there are many ways to do this, we will skip line 6 in the following way.
let
  n = 0
  while true
    n > 6 && break
    n += 1
    n % 2 == 1 && continue
    @show n
  end
end
When line 6 is reached and n % 2 == 1 then the right side of the && is evaluated, then continue means we go to the top of the loop.
As with doing anything with a loop, careful about using continue. It will skip all lines below this. If you wrote this loop with the n += 1 below the continue line then it will not be evaluated and will thus be an infinite loop, which we discuss more so below.

Subsection 5.4.5 Infinite Loops

It is common in a while loop to keep running it forever. This occurs if there is some bug or you haven’t considered all cases. For example, in the bisection method above, if the function doesn’t have a root (like \(f(x)=x^2+2\)), then this will never stop.
Here’s a few things that can help prevent or debug the code:
  • Make sure something is changing in your loop. If you intend to stop the loop on an index, make sure the index is updating.
  • Look at your code and see if you have something that you think will stop the loop. What ever is in the boolean statement needs to eventually switch.
  • Consider an additional stopping condition. You may need to add a variable to count the number of times you’ve gone through the loop and stop if it hits some maximum, which is greater than what you would expect.
  • Stop the code if you need to. You may need to interrupt the kernel. In the REPL, CTRL-C will stop and in a notebook, selecting the Kernel menu then Interrupt or there is often a stop button which should stop it. The square in the toolbar should work too. See Section A.7 for more information.
  • If you can’t figure out why it is in an infinite loop, use @show to print out values of variables.

Subsection 5.4.6 Debugging While Loops

As we mentioned above, if we use a function that does not have a root, like \(f(x)=x^2+2\text{,}\) the bisection function will not stop. Go ahead and try it. Here we will add some code to that method that will prevent it from running forever.
  1. we will introduce a variable n which will keep track of the number of steps we have done. We will start this variable with the value 0.
  2. Then, inside the loop update the value of n with the statement n += 1.
  3. Lastly, add another condition to the while statement that will only continue if n is less than some value, say 10.
The following update to the bisection method will now stop if we go too many times:
Listing 5.5. Bisection Method
function bisection(f::Function, a::Real, b::Real)
  local c
  local n = 0
  while (b-a) > 1e-6 && n < 10
    c = 0.5*(a+b)  # find the midpoint
    # test if f(a) and f(c) have opposite signs to determine the new interval
    (a,b) = f(a)*f(c) < 0 ? (a,c) : (c,b)
    n += 1
  end
  c
end

Section 5.5 For loops

A for loop executes some code a fixed number of times and has a variable (called an index) that updates. The next few examples are not practical, but are designed to illustrate the syntax and what is possible. The following is a simple for loop that prints out the numbers 1 to 10
for i=1:10
  @show i
end
If you want to skip numbers or count backwards, a range in the form start:skip:stop is used. The following starts at 1 and skips by 2 up to 21:
for i=1:2:21
  @show i
end
and the following starts at 10, counting down to 1:
for i=10:-1:0
  @show i
end
We can use the for var in list form to go through elements in a list or array (or tuple). The following adds all numbers in the list (array) [1,5,7,11,20]:
let
  local sum=0
  for i in [1,5,7,11,20]
    sum += i
  end
  sum
end
Recall that in Section 4.6 we used this in the formation to find the mean:
function theMean(x::Number...)
  local s = 0
  for val in x
    s += val
  end
  s/length(x)
end

Subsection 5.5.1 While Loops Versus For Loops

No this, isn’t a smackdown between while and for loops. A big question often is when I have a problem that I want to solve and I know a loop is needed, when should I use a while loop and when should I use a for loop. The general rule of thumb is:
  • If you know that you need to run code for a fixed number of times, use a for loop
  • If you don’t use a while loop. Generally, the doing something in the loop will affect how many times the loop is run.
Notice in the examples above, the bisection method used a while loop because it is unclear when you start how many times you want to go through the loop. Instead we stopped in while the interval was small enough.
In the summing of the terms for the mean, a for loop was used because of the fixed number of values in the tuple x.

Subsection 5.5.2 Adapting While loops as For Loops

As just mentioned above, generally if we don’t know how many times we go through a loop that a while loop should be used. Let’s say we have a game that we roll a die. If we roll the die 10 times without rolling a 1, we win and if a 1 appears fewer than 10 times, we lose. Here’s a function that we can use to play this game.
function playDieGame()
  local roll = rand(1:6)
  @show roll
  local n = 1
  local win = roll != 1
  while roll != 1 && n <=10
    roll = rand(1:6)
    @show roll
    win = false
    n += 1
  end
  win
end
This starts with a roll of the die with rand(1:6). We also have the variable n that will store the number of rolls and win is whether or not we win the game. At this point win is assumed true unless roll == 1. The condition on the while loop is that the roll is not 1 and that the number of rolls is less than or equal to 10. A possible output is
roll = 6
roll = 5
roll = 1
false
We will discuss random numbers in detail in Chapter 18 and if you run this function, you will get different results, but should see rolls until a 1 is reached. If it 1, the result is false that you win and true if not. Try running this function a number of times.
If this seems complicated, it is more complicated than it needs to be. If we take the premise that since we don’t know how many times this is run, we need to use a while then that complicates things then we need to have conditions on both the roll and the number of rolls (this is how the game is determined) and these both need to be set before the while loop.
Instead, we will use a for loop and break out (or actually just return) during the winning and losing conditions. Here’s an update of the code:
function playDieGame()
  for n=1:10
    local roll = rand(1:6)
    @show roll
    roll == 1 && return false
    n == 10 && return true
  end
end
Try running this code and you should see that it behaves the same way as above.
Notice that first we didn’t need to define roll before the loop and n is now the index of the for loop. Also, we didn’t define a win variable. Instead, notice that if roll == 1 we just return false and if n == 10 we return true.
If also, we don’t care about seeing the rolls, we can simplify this further with
function playDieGame()
  for n = 1:10
    rand(1:6) == 1 && return false
    n == 10 && return true
  end
end
We simplify further in that there is no reason to check that n == 10 each time through the loop. If we make it through the loop, then we have gone 10 times and thus we win. Here’s an even shorter version of the game:
function playDieGame()
  for n = 1:10
    rand(1:6) == 1 && return false
  end
  true
end
And if you compare this to the original function above, it’s much shorter. There is only one other feature to note. The variable n is never used, it is simply there as the index of the for loop. If this is true, in Julia style, we replace the variable with the _ character (which is technically a variable name) so our final version is
function playDieGame()
  for _ = 1:10
    rand(1:6) == 1 && return false
  end
  true
end

Subsection 5.5.3 Using break and continue with for loops

Although we introduced both break and continue statements in while loops above, they can also be used in for loops. Although these are not very practical examples, nevertheless this shows how they are used.
In this example, we will defined a loop from 1 to 10 and then break out of it after going through 5 times.
for n=1:10
  @show n
  n == 5 && break
end
And in this example, we will print out only odd numbers
for n=1:10
  n % 2 == 0 && continue
  @show n
end
Note 5.6.
Although most of the examples we have used with break and continue involve the short-circuit if statements, they can be used anywhere, however are typically using this or inside a standard if statement.

Section 5.6 Ranges

In a for loop with the syntax var=x:y:z, such as
for i=1:4
  @show i
end
The syntax 1:4 is called a Range and is shorthand for all integers between 1 and 4 inclusively. Technically, this is called a UnitRange because the skip size between numbers is 1.
If you type typeof(1:4), it will return UnitRange{Int64}, a composite type which is a UnitRange with integers. Note the difference using 1.0:4.0. This is of type StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, and the details of this are skipped for now. In short, it is Floating Point version of a UnitRange.
The other ranges we say included 1:2:11 and entering typeof(1:2:11) results in StepRange{Int64, Int64}.
We will see in Chapter 6 that there is a handy way of expanding this shorthand way of writing these sets of numbers to that of an entire list (or technically array) of numbers.

Subsection 5.6.1 Linear Ranges

As we see above, it easy to make ranges if we know the step between each pair. Another use would be to know the starting and ending value and the number of values. For example, if we want to start at 3 and end at 11 with 5 values, it’s not too hard to compute this, but Julia has a built-in way to do it with the range command. For example,
range(3,11,length=5)
returns 3.0:2.0:11.0 and note that even though all of the arguments were integers that the result is a floating point range.
Another nice function that does a similar thing is the LinRange function.
LinRange(3,11,5)
which returns 3.0, 5.0, 7.0, 9.0, 11.0 and this is an expanded version from above.