function plus_two(x)
return x+2
end
plus_two (generic function with 1 method)
This section of the tutorials introduces programming basics, including the art of simple functions, positional arguments, keyword arguments, loops, if-else-break usage and continue-while usage.
It is important to note that if you have experience programming R, there is a major difference in Julia - the use of loops is very much advocated in Julia where as vectorising loops is advocated in R.
Basically, we write loops in Julia. We try to avoid them in R, if we want speed.
Functions work exactly like they do in R, however, there are three fundamental differences:
function
and end with the word end
. -to store something that is calculated in a function, you use the return
command.Let’s begin with a simple function - adding 2 to any number
function plus_two(x)
return x+2
end
plus_two (generic function with 1 method)
Let’s use it now by providing an defining and x value, and asking for the function to return the new value.
= 33
x_in = plus_two(x_in) x_out
35
Because we’ve defined x_out
, we can request it…
x_out
35
As in R, input variables for functions have a specified and fixed order unless they have a default value which is explicitly specified. For instance, we can build a function that measures body weight on different planets, but defaults to estimating weight on earth with a gravitational force of 9.81:
function bodyweight(BW_earth, g = 9.81)
# bw should be in kg.
return BW_earth*g/9.81
end
bodyweight (generic function with 2 methods)
Note that the function is called bodyweight, it requires in the first position a weight in kg on earth and then defaults to estimating weight on earth by using g = 9.81
bodyweight(75)
75.0
Now, if we want to estimate they same bodyweight on Mars, where gravity is 3.72, you can specify the g-value.
bodyweight(75, 3.72)
28.44036697247706
# function with keyword arguments:
# here, b and d are fixed = 2
# a is positional
# c is a keyword argument
# the addition of ; before c means that c is an keyword argument and can be specified in any order, but must be named
function key_word(a, b=2; c, d=2)
return a + b + c + d
end
key_word (generic function with 2 methods)
Here we specify position 1 (a) and that c = 3
key_word(1, c = 3)
8
Here we specify c = 3, and then position 1
key_word(c=3, 1)
8
Here we specify position 1 (a), redefine position 2 (b = 6) and declare c = 7.
key_word(1, 6, c=7)
16
Note that this DOES NOT work, because we’ve failed to define c. (and or b)
key_word(1, 8, d=4)
LoadError: UndefKeywordError: keyword argument `c` not assigned
UndefKeywordError: keyword argument `c` not assigned
Stacktrace:
[1] top-level scope
@ In[13]:1
To redefine d, you’d need to define c and d.
key_word(1, c = 8, d = 4)
15
For loops work by iterating over a specified range (e.g. 1-10) at specified intervals (e.g. 1,2,3…). For instance, we might use a for loop to fill an array:
To fill an array, we first define an object as an array using []
.
= [] I_array
Any[]
Like with function, all loops start with for
and end with end
. Here we iteratively fill I_array
with 1000 random selections of 1 or 2.
# for loop to fill an array:
for i in 1:1000
# pick from the number 1 or 2 at random
# for each i'th step
= rand((1,2))
for_test # push! and store for_test in I_array2
# Julia is smart enough to do this iteratively
# you don't necessarily have to index by `[i]` like you might do in R
push!(I_array, for_test)
end
Let’s look at I_array now
I_array
1000-element Vector{Any}:
2
1
2
2
1
1
2
2
2
2
2
1
1
⋮
1
1
1
1
1
1
2
2
2
1
1
2
Let’s try something more complex, iterating over multiple indices
A new storage container:
= [] tab
Any[]
Now, we fill the storage container with values of i, j and k. Can you tell which in which order this will happen? The first entry will be [1,1,1]
. The second will be [2,1,1]
. Do you understand why? Mess around to check.
# nested for loop to fill an array:
for k in 1:4
for j in 1:3
for i in 1:2
append!(tab,[[i,j,k]]) # here we've use append! to allocate iteratively to the array as opposed to using push! - both work.
end
end
end
Let’s look…
tab
24-element Vector{Any}:
[1, 1, 1]
[2, 1, 1]
[1, 2, 1]
[2, 2, 1]
[1, 3, 1]
[2, 3, 1]
[1, 1, 2]
[2, 1, 2]
[1, 2, 2]
[2, 2, 2]
[1, 3, 2]
[2, 3, 2]
[1, 1, 3]
[2, 1, 3]
[1, 2, 3]
[2, 2, 3]
[1, 3, 3]
[2, 3, 3]
[1, 1, 4]
[2, 1, 4]
[1, 2, 4]
[2, 2, 4]
[1, 3, 4]
[2, 3, 4]
We can also allocate to a multiple dimensional matrix. When working with matrices, we can build them out of zeros and the replace the values.
Here we start with a three dimensional array with 4 two x three matrices.
= zeros(2,3,4) threeDmatrix
2×3×4 Array{Float64, 3}:
[:, :, 1] =
0.0 0.0 0.0
0.0 0.0 0.0
[:, :, 2] =
0.0 0.0 0.0
0.0 0.0 0.0
[:, :, 3] =
0.0 0.0 0.0
0.0 0.0 0.0
[:, :, 4] =
0.0 0.0 0.0
0.0 0.0 0.0
Now, let’s do a nested loop again, but this time into the matrices. The element we are adding each iteration is the sum of i+j+k.
Can you guess how this works?
for k in 1:4
for j in 1:3
for i in 1:2
# note default is by column....
# first element allocated is 1+1+1, then 2+1+1 and this is first col
# then 1+2+1 and 2+2+1 into the second col
# then 1+3+1 and 2+3+1 into the third col
= i+j+k
threeDmatrix[i,j,k] end
end
end
threeDmatrix
2×3×4 Array{Float64, 3}:
[:, :, 1] =
3.0 4.0 5.0
4.0 5.0 6.0
[:, :, 2] =
4.0 5.0 6.0
5.0 6.0 7.0
[:, :, 3] =
5.0 6.0 7.0
6.0 7.0 8.0
[:, :, 4] =
6.0 7.0 8.0
7.0 8.0 9.0
Finally, note that we can use println
to provide a basic marker what what is happening: we show two ways to do this in the code.
for k in 1:4
for j in 1:3
for i in 1:2
#println(i,"-",j,"-",k) # multiple quotes
println("$i-$j-$k") # one quote, $ to grab variables
# note default is by column....
# first element allocated is 1+1+1, then 2+1+1 and this is first col
# then 1+2+1 and 2+2+1 into the second col
# then 1+3+1 and 2+3+1 into the third col
= i+j+k
threeDmatrix[i,j,k] end
end
end
And just for fun… this println
trick can be handy for verbose tracking. Note how person in unique(persons)
iterates and how you can embed a variable’s value in a text string.
= ["Alice", "Alice", "Bob", "Bob2", "Carl", "Dan"]
persons
for person in unique(persons)
println("Hello $person")
end
Hello Alice
Hello Bob
Hello Bob2
Hello Carl
Hello Dan
There are tons of different functions that can be helpful when building loops. Take a few minutes to look into the help files for eachindex
, eachcol
, eachrow
and enumerate
. They all provide slightly different ways of telling Julia how you want to loop over a problem. Also, remember that loops aren’t just for allocation, they can also be very useful when doing calculations.
When building a loop, it is often meaningful to stop or modify the looping process when a certain condition is met. For example, we can use the break
, if
and else
statements to stop a for loop when i exceeds a given value (e.g. 10):
# if and break:
for i in 1:100
println(i) # print i
if i >10
break # stop the loop with i >10
end
end
1
2
3
4
5
6
7
8
9
10
11
# this loop can be modified using an if-else statement:
# even though we are iterating to 100, it stops at 10.
for j in 1:100
if j >10
break # stop the loop with i >10
else
= j^3
crj println("J is = $j") # print i
println("The Cube of $j is $crj")
end
end
J is = 1
The Cube of 1 is 1
J is = 2
The Cube of 2 is 8
J is = 3
The Cube of 3 is 27
J is = 4
The Cube of 4 is 64
J is = 5
The Cube of 5 is 125
J is = 6
The Cube of 6 is 216
J is = 7
The Cube of 7 is 343
J is = 8
The Cube of 8 is 512
J is = 9
The Cube of 9 is 729
J is = 10
The Cube of 10 is 1000
You’ll notice that every statement requires it’s own set of for
and end
points, and is indented as per Julia’s requirements. if
and else
statements can be very useful when building experiments: for example we might want to stop simulating a network’s dynamics if more than 50% of the species have gone extinct.
The continue
command is the opposite to break
and can be useful when you want to skip an iteration but not stop the loop:
for i in 1:30
# this reads: is it false that i is a multiple of 3?
if i % 3 == false
continue # makes the loop skip iterations that are a multiple of 3
else println("$i is not a multiple of 3")
end
end
1 is not a multiple of 3
2 is not a multiple of 3
4 is not a multiple of 3
5 is not a multiple of 3
7 is not a multiple of 3
8 is not a multiple of 3
10 is not a multiple of 3
11 is not a multiple of 3
13 is not a multiple of 3
14 is not a multiple of 3
16 is not a multiple of 3
17 is not a multiple of 3
19 is not a multiple of 3
20 is not a multiple of 3
22 is not a multiple of 3
23 is not a multiple of 3
25 is not a multiple of 3
26 is not a multiple of 3
28 is not a multiple of 3
29 is not a multiple of 3
Can you figure out what the code would be for keeping even numbers only? Note the change of logic from false above to true here.
for i in 1:10
# where is it true that i is a multiple of 2?
if i % 2 == true
continue # makes the loop skip iterations that are odd
else println("$i is even")
end
end
2 is even
4 is even
6 is even
8 is even
10 is even
while
loops provide an alternative to for
loops and allow you to iterate until a certain condition is met:
# counter that is globally scoped (see next section)
# testval -- try changing this to see how this global variable can be used in
# the local process below
global j=0
global testval = 17
# note that we started with j = 0!!!
# justify a condition
while(j<testval)
println("$j is definitely less than $testval") # prints j until j < 17
# step forward
+= 1 # count
j end
0 is definitely less than 17
1 is definitely less than 17
2 is definitely less than 17
3 is definitely less than 17
4 is definitely less than 17
5 is definitely less than 17
6 is definitely less than 17
7 is definitely less than 17
8 is definitely less than 17
9 is definitely less than 17
10 is definitely less than 17
11 is definitely less than 17
12 is definitely less than 17
13 is definitely less than 17
14 is definitely less than 17
15 is definitely less than 17
16 is definitely less than 17
while
loops don’t require you to specify a looping sequence (e.g. i in 1:100
). But you do specify the starting value. The while
loop can be very useful because sometimes you simply don’t know how many iterations you might need.
In the above code, you might have spotted the word global
. Variables can exist in the local
or global
scope. If a variable exists inside a loop or function it is local
and if you want to save it beyond the loop (i.e., in your workspace) you have to make it global
- more on this below.
Let’s get a bit more complicated. Above, you created a function that added 2 to any number. Let’s embed that in a loop and introduce enumerate
. Quite often, there are functions you may want to apply to multiple things, and this is the example of how to do that!
# make a vector - these are input values to our function
= [1,2,3,7,9,11]
vv
# enumerate takes a special two variable starter: "(index, value)"
# note how we print the index, then the output and then a line break with \n
for (i, v) in enumerate(vv)
= plus_two(v)
out println("this is element $i of vv")
println("$v plus 2 is equal to $out\n")
end
this is element 1 of vv
1 plus 2 is equal to 3
this is element 2 of vv
2 plus 2 is equal to 4
this is element 3 of vv
3 plus 2 is equal to 5
this is element 4 of vv
7 plus 2 is equal to 9
this is element 5 of vv
9 plus 2 is equal to 11
this is element 6 of vv
11 plus 2 is equal to 13
Scoping refers to the accessibility of a variable within your project. The scope of a variable is defined as the region of code where a variable is known and accessible. A variable can be in the global or local scope.
A variable in the global
scope is accessible everywhere and can be modified by any part of your code. When you create (or allocate to) a variable in your script outside of a function or loop you’re creating something that is global
:
# global allocation to A
= 7
A = zeros(1:10) B
10-element OffsetArray(::Vector{Float64}, 1:10) with eltype Float64 with indices 1:10:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
Of course you can be super literate and force a variable to be global
global(c = 7)
7
A variable in the local
scope is only accessible in that scope or in scopes eventually defined inside it. When you define a variable within a function or loop that isn’t returned then you create something that is local
:
# global
= zeros(10) C2
10-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
# local:
for i in 1:10
= 2 # local_varb is defined inside the loop and is therefore local (only accessible within the loop)
local_varb = local_varb*i # in comparison, C is defined outside of the loop and is therefore global
C2[i] end
Now, let’s see what we can see.
C2 is global
and it had numbers assigned to it, and we can see it.
C2
10-element Vector{Float64}:
2.0
4.0
6.0
8.0
10.0
12.0
14.0
16.0
18.0
20.0
However, local_varb
is local, and we can’t ask for anything about it. If we wanted to know about it, we’d have to ask for it to be println
-ed to monitor it, or written (as it was to C2)
local_varb
LoadError: UndefVarError: `local_varb` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
UndefVarError: `local_varb` not defined in `Main`
Suggestion: check for spelling errors or missing imports.