So far, what we have learned might be called coding, the way your write commands that do a fixed set of instructions. To do true programming we need to know how to write scripts in which the exact sequence of commands is determined dynamically. This is called control flow.
Loops: for and while
Loops are very useful in programming, because we often want to do a set of commands multiple times. For example:
for ice in1:10 iceCubed = ice^3println("The cube of ", ice, " is ", iceCubed)end
The cube of 1 is 1
The cube of 2 is 8
The cube of 3 is 27
The cube of 4 is 64
The cube of 5 is 125
The cube of 6 is 216
The cube of 7 is 343
The cube of 8 is 512
The cube of 9 is 729
The cube of 10 is 1000
Here we’ll use a random integer to determine how many times to do a loop:
rand_times =rand(1:10) # This returns a random integer in the range 1 to 10for i in1:rand_timesprintln(i)endprintln("I counted to $rand_times")
1
2
3
4
5
6
7
8
9
10
I counted to 10
Note that the $ in the last line above causes string interpolation of the expression to the right of it.
Teach Julia to Sing
Write a script that produces the song “99 bottles of beer on the wall.” This song goes like “99 bottles of beer on the wall, 99 bottle of beer, you take one down and pass it around, 98 bottles of beer on the wall; 98 bottles of beer on the wall, 98 bottles of …” and so forth that all the way down. (I don’t think anyone has ever completed the song . . . Can your program succeed in doing so?)
An interesting thing about for loops is that the iterator variable is internal to the loop (not “known” outside of the loop):
i =27println("Before the loop, i = $i")for i in1:3println("Inside the loop, i = $i")endprintln("After the loop, i = $i")
Before the loop, i = 27
Inside the loop, i = 1
Inside the loop, i = 2
Inside the loop, i = 3
After the loop, i = 27
for loops can iterate across many kinds of collections
You can iterate across all sorts of things, for example a matrix of strings:
speciesGrid = ["warblers""flamebacks""sticklebacks"; "sparrows""guppies""digital organisms"] for i in speciesGridprintln("We study $i.")end
We study warblers.
We study sparrows.
We study flamebacks.
We study guppies.
We study sticklebacks.
We study digital organisms.
Instead of the in in the for line, you can use the unicode character ∈ (produced by typing \in tab), which is a mathematical symbol meaning “is an element of”. :
mySet = ["banana", 5.3, '🐬'] # get the dolphin with \:dolphin: tabfor thing ∈ mySetprintln(thing)end
banana
5.3
🐬
Nested loops
Often it is useful to have nested loops. For instance, to produce all dinucleotide combinations:
bases = ['A', 'C', 'G', 'T']for i in basesfor j in basesprint(i, j, ", ")endend
AA, AC, AG, AT, CA, CC, CG, CT, GA, GC, GG, GT, TA, TC, TG, TT,
You can produce the above more concisely by putting both iterator statements on the same line:
bases = ['A', 'C', 'G', 'T']for i in bases, j in basesprint(i, j, ", ")end
AA, AC, AG, AT, CA, CC, CG, CT, GA, GC, GG, GT, TA, TC, TG, TT,
Produce all trinucleotides
Can you modify the above to produce all possible trinucleotides (i.e., 3-base nucleotides)?
while loops
Let’s say you don’t know in advance how many times you want to go through a loop. Rather, you want to stop the loop when some condition is met. The while loop is perfect for that:
mySum =0while mySum <=21# inspired by a card game myRand =rand(1:10) # this variable internal to the loop, not known outside mySum = mySum + myRandprintln(mySum)end
4
11
20
26
Simulate dispersal of an organism
You are writing a simulation in which individuals move from their birth site to their breeding site, along a continuous range of length 1. The dispersal distance is drawn from a standard normal distribution, given by randn(), with the constraint that they are not allowed to move outside of the range. If an individual is born at location 0.7, write a script that determines its breeding location within the range (lowest possible value = 0; highest possible value = 1).
Compound expressions
A compound expression is a single large expression that is made up of multiple sub-expressions that are evaluated in order. When the compoud expression is evaluated, only the value generated by the last sub-expression is returned. There are basically two ways to generate compound expressions:
Use a semicolon (;) chain
x =4; y =3; x + y
7
The above returns the value of the last subexpression, but it does remember values assigned in the earlier subexpressions. (To prove this to yourself, enter x to see its value.)
You can use compound expressions as part of even larger expressions, e.g.:
myVar = (x =4; y =3; x + y)
7
There, the compound expression in parentheses is evaluated and the resulting value is assigned to myVar.
Use a begin block
The above compound expression is equivalent to the one below:
myVar2 =begin x =4; y =3; x + y end
7
The use of begin blocks is particularly useful when dividing expressions between multiple lines:
begin string1 ="Phylloscopus" string2 ="trochiloides" string1 *" "* string2end
"Phylloscopus trochiloides"
Conditional evaluation: if and ? :
Often in our programs we want to execute some commands only if a condition is met. For this, we can use an if - elseif - else block:
coinflip =rand(["heads", "tails"])if coinflip =="heads"println("Heads--You win!")elseif coinflip =="tails"println("Tails--You lose :(")elseprintln("The coin has a strange value")end
Heads--You win!
In an if block, the elseif and else parts are optional, but the end is essential. The indentation is customary and makes the code easier to read, but is not required. The value of the conditional expressions (e.g., just to the right of if) must be of type Bool, meaning either true or false.
Determine homozygote or heterozygote
Write a script in which you assign a diploid genotype to a variable, with the genotype encoded as a 2-element vector containing strings (e.g., ["A", "C"] or ["A", "A"]), and then the script tells you whether the genotype is homozygous (the two elements are the same) or heterozygous (the two elements are different). (Hint: Remember how to access one element of a vector, e.g. x[1] gives just the first element of x; Remember how to ask if two things are equal: ==)
In Julia, if blocks return a value. You can make use if that to do something like this:
The ternary operator, consisting of a ? and : separating three expressions (hence “ternary”), can be thought of as a consise form of an if - else block. For instance, this is equivalent to the above:
homozygote ? "TT":"TG"
"TT"
This can be thought of as “True or false” ? “Do this if true” : “Do this if false”
Determine whether a number is positive or negative
Assign a non-zero number to a variable, and then use a ternary operater to report whether the number is positive or negative.
Short-circuit evaluation
Another way of constructing conditional evaluations is to use the && and || operators (which mean logical “and” and logical “or”). These operators come between two sub-expressions which are evaluated in order from left to right. Julia is efficient and only evaluates the rightmost sub-expression if it needs to (this is why it is called short-circuit evaluation: the evaluation doesn’t always extend to the right sub-expression). Hence:
In a && b, b is evaluated only if a is true (because otherwise the whole statement is definitely false)
In a || b, b is evaluated only if a is false (because otherwise the whole statement is definitely true)
The interesting thing is that the b subexpression does not have to produce true or false. Rather, it can do anything you want, and is only executed depending on whether a is true. For example:
x =5x >0&&println("x is positive")
x is positive
Try changing x above to a negative value and run the above again.
I think of the above construction as “Check if true AND (only if true) do this thing.”
y =3.2y <=0||println("x is positive")
x is positive
Again, try changing the value of y to a negative value and run again.
I think of the above as “Check if true OR (only if not true) do this thing.”
The above expressions may take a while to get used to. You don’t have to use them, as you might find the if block a lot more intuitive. But you may see these expressions in code that others have written, so it is good to understand them.
Array comprehensions
This is a concept that involves understanding of arrays, for loops, and (in some cases) if statements. Let’s say we want to generate a vector containing all the cubes of the integers from 1 to 5. From what we’ve learned above, we can write a for loop to do that:
cubes = [] # initializes an empty vectorfor i in1:5push!(cubes, i^3)endcubes
5-element Vector{Any}:
1
8
27
64
125
That works fine. A more concise way to do this using an array comprehension:
cubes2 = [x^3 for x in1:5]
5-element Vector{Int64}:
1
8
27
64
125
The code in the brackets can be thought of as a loop in which a variable (x) takes on the values from 1 through 5, each time calculating x^3 and storing that as a value in the vector. We can even add an if statement to filter the resulting values in the vector:
cubes3 = [x^3 for x in1:10 if x^3%3==0]
3-element Vector{Int64}:
27
216
729
This produces only the cubes that are divisible by 3.
Multi-dimensional array comprehension
We can use comprehensions to generate arrays of any dimension. For example:
The above had two implied loops (one for x and one for y) and the comma between them indicates they represent two dimensions.
Make a 4×4 array containing all possible dinucleotides
Using array comprehension, write code that will generate Matrix containing all 16 possible 2-letter combinations of A, C, G, and T.
If you succeed: Can you now generate a 4×4×4 Array that contains all possible trinucleotides?
Array comprehensions might take a while to get used to, as they contain a lot of logic in concise code. I mention them here because you may see them in Julia programs written by others. When you are starting out though, the logic may be easier to perceive if you write out your array-filling loops as full for blocks containing a series of expressions and an end keyword.
Next steps
Now that we have have developed an understanding of loops and conditional evaluation, we are ready to learn more about functions, types, and multiple dispatch in Julia. Together, these concepts can be thought of as methods of control flow. They enable powerful and fascinating approaches to programming.