Loops, Arrays (Vectors), and Series
Contents
Loops, Arrays (Vectors), and Series¶
Loops¶
A loop is a programming construct that allows the programmer to run the block of code multiple times without having to write the code multiple times.
For loop¶
Example:
println("The copy paste way: (bad)")
println("hello world")
println("hello world")
println("hello world")
println("hello world")
println("hello world")
println("-----------")
println("The loop way:")
for i in 1:5
println("hello world")
end
The copy paste way: (bad)
hello world
hello world
hello world
hello world
hello world
-----------
The loop way:
hello world
hello world
hello world
hello world
hello world
The syntax of a loop:
for ## begins the loop
# block of code that will be run multiple times
end # ends the loop
i : the “index” of the loop 1:5 : a range of numbers from 1-5 inclusive (i.e 1,2,3,4,5) in : says to do the loop for each value in the following. The loop will assign the values 1,2,3,4,5 in order.
Try modifiying the above code to have it print out 10 times.
Here is an example corresponding to the following mathematical summation. It will print the value at each iteration
\(\sum_{i=1}^{10} i\)
summation_value = 0
for i in 1:10
summation_value = summation_value + i
println("value at i=$(i): $(summation_value)")
end
println("final value: $(summation_value)")
value at i=1: 1
value at i=2: 3
value at i=3: 6
value at i=4: 10
value at i=5: 15
value at i=6: 21
value at i=7: 28
value at i=8: 36
value at i=9: 45
value at i=10: 55
final value: 55
Another way to do this would be with the operator += which adds the value on the right side of the operator to the existing value in the value to the left;
number = 1
number += 1 # now number equals 2
summation_value = 0
for i in 1:10
summation_value += i
println("value at i=$(i): $(summation_value)")
end
println("final value: $(summation_value)")
value at i=1: 1
value at i=2: 3
value at i=3: 6
value at i=4: 10
value at i=5: 15
value at i=6: 21
value at i=7: 28
value at i=8: 36
value at i=9: 45
value at i=10: 55
final value: 55
While loop¶
You can accomplish the same concepts with a while loop. A while loop continues to run until the condition after the word while is false. I would read this outloud as “while the index is less than or equal to 10, add the index to our value and then increment the index.”
i = 0
summation_value = 0
while i <= 10
summation_value += i
i += 1 # make sure to do the incrementing of i as the last line of code in the loop or you will get the wrong result
end
println("final value: $(summation_value)")
final value: 55
Try changing this to add a differnt set of numbers? What if you wanted to do the following sum with a while loop and a for loop: \(\sum_{i=5}^{11} i\)
Click here to see solution:
i = 5
summation_value = 0
while i <= 11
summation_value += i
i += 1 # make sure to do the incrementing of i as the last line of code in the loop or you will get the wrong result
end
println("final value: $(summation_value)")
summation_value = 0
for i in 5:11
summation_value += i
end
println("final value: $(summation_value)")
Print out even numbers¶
Let’s print out the first 5 even numbers. Here is how we would think about it. The simplest way is to loop over 1 to two times the number of numbers we want to print out. We would check if the number is even and if it is we would print it out. That is what the below code does. The way to check if a number is even is by using the modulo operator %. It checks the division remainder. If the result of number % 2 is 0, it is an even number, if not it is odd.
Some examples of that first:
A simple way of doing it if statement¶
println(0 % 2)
println(1 % 2)
println(2 % 2)
println(3 % 2)
println(4 % 2)
println(5 % 2)
println(33 % 2)
println(1047 % 2)
0
1
0
1
0
1
1
1
for i in 1:10
if i % 2 == 0 # does i divided by 2 have a remainder equal to zero
println("$(i)")
end
end
2
4
6
8
10
Print out even numbers in a smarter way (2*i)¶
There is however a smarter way of doing this. Instead of using the modulus operator we can just multiply the index by 2 and that will be the next even number.
for i in 1:5
println("$(2*i)")
end
2
4
6
8
10
There is yet another way of doing this. We can use a while loop and count the number of times we found a even number.
number_of_even_printed = 0
i = 1
while number_of_even_printed < 5
if i % 2 == 0
number_of_even_printed += 1
println(i)
end
i+=1
end
2
4
6
8
10
Although it may seem a bit annoying to see so many ways of doing the same thing, all of these techniques are distinct and have their uses. We will see below, there are instances where we may be iterating over a list of items that might not be order. That is where these various techniques may be needed.
Calling a function in a loop¶
You don’t have all of your code for the loop directly in the loop itself. It is best not to have a loop with tons and tons of code inside of it. To avoid this you can call a function inside of your loop. This example is a bit silly as the function only has one line in the function, but imagine if it needed 10 or 50 lines of code.
function print_iteration_message(index, summation_value)
println("value at i=$(index): $(summation_value)")
end
summation_value = 0
for i in 1:10
summation_value += i
print_iteration_message(i, summation_value)
end
println("final value: $(summation_value)")
value at i=1: 1
value at i=2: 3
value at i=3: 6
value at i=4: 10
value at i=5: 15
value at i=6: 21
value at i=7: 28
value at i=8: 36
value at i=9: 45
value at i=10: 55
final value: 55
Arrays¶
An array is similar to the mathematical construct of a vector (julia actually calls them vectors, other languages call them arrays and is more common, As such I am going to refer to them as arrays.). It is a list of values. A common use of vectors is to define a point in space (x,y,z). You can use the normal vector algebra you would expect.
point_int_space_origin = [0,0,0]
display(point_int_space_origin) # display is a fancy print function with more information about the object you pass in
second_point = point_int_space_origin + [1,1,2]
display(second_point)
3-element Vector{Int64}:
0
0
0
3-element Vector{Int64}:
1
1
2
You can do “element-wise” operations using .{operator here} (for multiplication this is called a Hadamard Product)
multiplied_point = [1,2,3] .* [2,2,2]
display(multiplied_point)
#which is the same as
multiplied_point = 2*[1,2,3]
display(multiplied_point)
#but could be used to do
multiplied_point = [1,2,3] .* [3,5,10]
display(multiplied_point)
3-element Vector{Int64}:
2
4
6
3-element Vector{Int64}:
2
4
6
3-element Vector{Int64}:
3
10
30
Of course, we don’t have to limit ourselves to loops with length 3. Julia provides some operations functions for getting attributes of the array.
Getting a value at a specific index¶
To get a specific value use the [] operator after the name of the array variable
values = [10,20,30]
### print the first value in the array of values
println(values[1])
longer_vector = [12.23,1,3,.12,5,6,7,-8.32]
sum_of_first_two_terms = longer_vector[1] + longer_vector[2]
println(sum_of_first_two_terms)
10
13.23
Julia one based indexing vs zero based indexing¶
Note most other programming languages use zero based indexing
i.e. to get the first value in an array you use values[0]. It is intended to be less confusing, but if you are looking at algorithms online/in literature for how to implement something assume zero based indexing.
Iterate over an array using a loop¶
One powerful thing we can do with a loop is iterate over an array and perform an operation. As an example lets do the dot product of two vectors. The dot product of a vector is the sum of elementwise multiplication.
e.g. if our vectors are [1,2,3] and [-1,-2,-3] the dot product is 1*-1 + 2*-2 + 3*-3 = -14
function dot_product(vector1, vector2)
dot_product_value = 0
for i in 1:length(vector1)
v1_i = vector1[i]
v2_i = vector2[i]
new_term = v1_i * v2_i
dot_product_value += new_term
end
return dot_product_value
end
vector_a = [1,2,3]
vector_b = [-1,-2,-3]
value = dot_product(vector_a, vector_b)
println(value)
-14
Try to change the vectors and get the value for that result.
Assert Macro¶
The vectors must have the same length for the dot product to have a valid meaning.
We can check to see if this is the case by using the assert “macro.”
A macro is something provided by the Julia language to do some basic functionality without having to write a special function for it.
The assert macro checks a boolean result, if it is true the code proceeds forward. If it is false an error message is thrown.
length() function¶
There is a built in function provided by Julia length(array) that returns the number of items in the array as an integer.
vector_a = [1,2,3]
vector_b = [-1,-2,-3, 4, 5]
length_of_a = length(vector_a)
length_of_b = length(vector_b)
println(length_of_a)
println(length_of_b)
3
5
The following code will result in an error message. Fix the code so it will run again.
function dot_product(vector1, vector2)
@assert length(vector1) == length(vector2)
dot_product_value = 0
for i in 1:length(vector1)
v1_i = vector1[i]
v2_i = vector2[i]
new_term = v1_i * v2_i
dot_product_value += new_term
end
return dot_product_value
end
vector_a = [1,2,3]
vector_b = [-1,-2,-3] # the problem is here
value = dot_product(vector_a, vector_b)
-14
Using keyword¶
We have talked about “built in” features of the Julia language, the most recent example was “length()” Everything we have used so far is part of the “Base” module of the “standard library” of features.
There are other things provided by the language that are not included by default, you have to “ask for them” to be provided. The way to do this is by adding a “using” statement. “using” statements need to be at the top of the file.
Julia has many libraries providing often used functionality. The first one of these we will use Linear Algebra
using LinearAlgebra
This statement tells Julia to provide us the LinearAlgebra module from the standard library.
We use the dot() function that provides the dot product functionality which will give the same result as the code we wrote.
using LinearAlgebra # built in julia library providing linear algebra functions such as dot(): which computes the dot product
vector_a = [1,2,3]
vector_b = [4,5,6]
julia_computed_value = dot(vector_a,vector_b)
println(julia_computed_value)
32
There are other modules other than the standard library developed by the community that are availible for use in Julia. You can even build your own. They all will use the using keyword. We will cover this at a later date.
Just for fun lets do the dot product of a huge vector, i.e. something you’d never want to have to do by hand. A convenient way to make an “empty” array is to use the zeros(n)
function, which gives you an array with the length you provide.
using LinearAlgebra
powers_of_2 = zeros(100) #array with length 100, all values = 0
powers_of_4 = zeros(100)
for i in 1:100 # a loop adding the values to our array.
powers_of_2[i] = 2^i
powers_of_4[i] = 4^i
end
dot_product_value = dot(powers_of_2, powers_of_4)
println(dot_product_value) # ya that is a big number
1.131830893060919e28
Note on what we just did above¶
Most of the examples above were just that, examples. We were “re-inventing the wheel” because it was useful for us to learn looping and how it relates to math. You would not want to implement your own dot product function, or summation function. This type of functionality is provided for you by the Julia language. Here are a few examples. You can find more information at the Linear Algebra Documentation Page., the Math Documentation Page, and Statistics Documentation Page.
In general you want to use the language provided libraries as they are likely to be much faster, more robustly tested, etc. than what the average programmer working on their own is going to produce. Also it saves you time, because you don’t have to do it yourself.
using LinearAlgebra
using Statistics # statistical functions
our_vector = [1,2,3,4,5,6,7,8,9,10,15, 30]
our_sum = sum(our_vector)
our_average = mean(our_vector)
our_median = median(our_vector)
dot_product_value = dot(our_vector, our_vector)
println(our_sum)
println(our_average)
println(our_median)
println(dot_product_value)
100
8.333333333333334
6.5
1510
Convergence And Divergence of Series¶
Now lets put our knowledge of loops to the test and do some mathematical applications. We will now evaluate the value you get using the series expansion of some important mathematical function.
The series expansion of \( e^x \) is:¶
\( e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!} = 1 + x + \frac{x^2}{2!} +... \)
Let’s break down how we can accomplish this step by step. First we can write a function that will return the value of an individual value for a given n.
julia provides the function factorial(n) for computing \(n!\)
function calculate_exp_series_term(x,n)
return (x^n)/factorial(n)
end
println(calculate_exp_series_term(2,1)) # first term in the series for e^2
println(calculate_exp_series_term(2,2)) # second term in the series e^2
println(calculate_exp_series_term(2,3)) # third term in the series e^2
2.0
2.0
1.3333333333333333
Next we can build a function that loops to do the sum. We will use 20 terms to start.
function calculate_exp_series_term(x, n)
return (x^n)/factorial(n)
end
function calculate_series_exponential(x)
series_expansion_value = 0
n = 0 #iteration index
exp_exact = exp(x)
while n < 20
series_term_n = calculate_exp_series_term(x, n)
series_expansion_value += series_term_n
n+= 1
end
println("number of iterations $(n)")
return series_expansion_value
end
series_expansion_value = calculate_series_exponential(2) # calculate e^2
number of iterations 20
7.3890560989301735
Lastly since we know that after a large enough number of terms, the terms will go to zero. We will check if the new term is sufficiently small. When the new term is less than .01 we stop the loop. We also still exit the loop if we reach more than 20 terms.
function calculate_exp_series_term(x, n)
return (x^n)/factorial(n)
end
function calculate_series_exponential(x)
series_expansion_value = 0
n = 0 #iteration index
exp_exact = exp(x)
series_term_n = 1 #use a starting value that will allow us to go into the loop
while series_term_n >= .01 && n < 20
series_term_n = calculate_exp_series_term(x,n)
series_expansion_value += series_term_n
n+= 1
end
println("number of iterations $(n)")
return series_expansion_value
end
series_expansion_value = calculate_series_exponential(2) # calculate e^2
number of iterations 9
7.387301587301587
Here is a final version.
A couple of functions used here
exp(): gives us the exact value for e^x
big(): creates a number that can be very large, signals to julia to handle the factorial function differently (don’t worry about this for now)
round(): rounds a floating point number to a specified number of digits.
Float64(): converts a number to a Float64, again don’t worry too much about this.
function calculate_exp_series_term(x, n)
return (x^n)/factorial(big(n))
end
function calculate_series_exponential(x)
series_expansion_value = 0
n = 0 #iteration index
# keeps track of the new term
series_term_n = 1 #use a starting value that will allow us to go into the loop
while series_term_n > .01 && n < 1000
series_term_n = calculate_exp_series_term(x, n)
series_expansion_value += series_term_n
n+= 1
end
return series_expansion_value
end
## Some code to run and compare the exact value using the exp() function provided by Julia
## and the one that we calculate with our function
x = 2
exp_exact = exp(x) #e^2
exp_exact = round(exp_exact; digits=10) #don't worry too much about how this rounding works
println("$(exp_exact) exp_exact")
series_expansion_value = calculate_series_exponential(x)
#round off some extra values
rounded_value = Float64(round(series_expansion_value; digits=10)) #don't worry too much about how this rounding works
println("$(rounded_value) series_expansion_value")
7.3890560989 exp_exact
7.3873015873 series_expansion_value
cos(x) and sin(x)¶
Now lets consider cos(x) and sin(x).
The procedure is almost exactly the same. We have to change our function to correctly do the series and that is really it.
The biggest change is that (-1)^n term, which will toggle each term, first positive -1^(0), negative (-1)^1, etc….
One other addition is that we use the abs() to get the absolute value of the term when checking it it is sufficiently small.
\( cos(x) = \sum_{n=0}^{\infty} (-1)^{n}\frac{x^{2n}}{(2n)!} = 1 - x + \frac{x^2}{2!} -... \)
\( sin(x) = \sum_{n=0}^{\infty} (-1)^{n}\frac{x^{2n+1}}{(2n+1)!} = x -\frac{x^3}{3!} + \frac{x^5}{5!} -... \)
function calculate_cos_series_term(n, x)
return (-1)^n*(x^(2*n))/factorial(big(2*n))
end
function calculate_series_cos(x)
series_expansion_value = 0
n = 0 #iteration index
series_term_n = 1 #use a starting value that will allow us to go into the loop
while abs(series_term_n) > .01 && n < 10000
series_term_n = calculate_cos_series_term(n, x)
series_expansion_value += series_term_n
n += 1
end
println("number of iterations $(n)")
return series_expansion_value
end
x = 2*3.14159 # 2*pi
cos_exact = cos(x)
cos_exact = round(cos_exact; digits=10)
println("$(cos_exact) cos_exact")
series_expansion_value = calculate_series_cos(x)
#round off some extra values
rounded_value = Float64(round(series_expansion_value; digits=10))
println("$(rounded_value) series_expansion_value")
1.0 cos_exact
number of iterations 11
1.0003012185 series_expansion_value
You will do sin(x) as a exercise problem. The only thing you will need to change is the calculation function.
Exercise¶
REMINDER!!!
When working in Binder/Jupyter book, your files and changes are not persisted unless you are consistently using it. (If it goes idle you may lose your work!) Save your files to your computer early and often!!!
Going forward¶
Going forward all of the end of module exercises will be done in separate julia files(extension .jl). You can use the jupyter notebook to run them (this will be set up for you in the notebook). There will be two files of interest for the exercises:
Submitting¶
Please save your exercise julia files in a folder named LastName_FirstName_Module_N_Exercise and save it to a zip file. Do not change the file names Upload the zip to canvas.
Exercise File¶
There will be a file called m-module-e-exercise.jl (where m is the module number, e is the problem number). This file will have a scaffolding of functions that you will need to complete.
The include function is how to include the code from a different file. It should be at the top of the file.
Here is an example of how to use a julia file in jupyter notebook:
include("hello.jl")
hello_text = hello()
println(hello_text)
hello
Test Runner¶
There will be a file called m-module-e-exercise-test-runner.jl for each module. This file runs test that will call the functions in your exercise file. DO NOT MODIFY THIS FILE. These test files will let you know if you have done the exercise correctly.
Inside this file are tests called “Unit Tests.” These types of tests are very common in software engineering. In a good codebase, unit tests should be written to account for every scenario your code is expected to fulfil.
To receive credit for the module your code must pass all of the tests.
An example of a unit test in julia:
using Test
expected_value = 5
our_value = sqrt(25)
@test expected_value == our_value
Test Passed
using Test
expected_value = 5
wrong_value = 25 / 3
@testset "hello tests" begin
@test our_value == wrong_value # a test that will fail
end
hello tests: Test Failed at In[26]:5
Expression: our_value == wrong_value
Evaluated: 5.0 == 8.333333333333334
Stacktrace:
[1] macro expansion
@ In[26]:5 [inlined]
[2] macro expansion
@ /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/Test/src/Test.jl:1151 [inlined]
[3] top-level scope
@ In[26]:5
Test Summary: |
Fail Total
hello tests | 1 1
Some tests did not pass: 0 passed, 1 failed, 0 errored, 0 broken.
Stacktrace:
[1] finish(ts::Test.DefaultTestSet)
@ Test /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/Test/src/Test.jl:913
[2] macro expansion
@ /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/Test/src/Test.jl:1161 [inlined]
[3] top-level scope
@ In[26]:5
[4] eval
@ ./boot.jl:360 [inlined]
[5] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1116
Example Test Runner¶
Here is how to use one, the tests will be grouped and named to help you figure out which pass and which fail:
include("hello-test-runner.jl") #including the file runs the tests
The code snippets below is commented out so it doesn’t run until you are ready.
Remove the # when you are ready to use it.
Problem 1 - Build an Array¶
a) Write a function that takes in a number n (named number_of_values) as a parameter and returns an array containing the first n odd numbers. (Hint: Starting from 1 going to n)
b) Write a function that takes in a number n (number_of_values) as a parameter and returns an array containing the first n powers of 3. (Hint: starting from 0 to n-1, remember 1 based indexing!)
Implement these in the provided file: module-2-exercise-1.jl
# include("module-2-exercise-1.jl")
# odd_numbers = get_odd_numbers(7)
# display(odd_numbers)
# include("module-2-exercise-1.jl")
# powers_of_there = get_powers_of_three(4)
# display(powers_of_there)
You can run the code below to run the unit tests against your implementation of the exercises. It will give you error messages if something is wrong, and will print out the tests as successful if it succeeds.
# include("module-2-exercise-1a-test-runner.jl") #including the file runs the tests
# include("module-2-exercise-1b-test-runner.jl") #including the file runs the tests
Problem 2 - Implement sin(x) as a series expansion¶
Using similar functions to the cos(x) example above, implement a series expansion of sin(x). You do not need to do any of the checking for the size of terms. We will just use the first 100 terms.
Complete this exercise in module-2-exercise-2.jl
# include("module-2-exercise-2-test-runner.jl") #including the file runs the tests
Problem 3 - Count the even numbers in an array of numbers¶
Using a loop, iterate over an array passed into your function and return an integer corresponding to the number of even numbers in the array.
Complete this exercise in module-2-exercise-3.jl
# include("module-2-exercise-3-test-runner.jl") #including the file runs the tests
Some other thoughts before you go:¶
Complex Numbers in Julia¶
By the way, I forgot to mention this in the last module, but Julia natively can handle complex numbers;
See Documentation for more information and operations availible with complex numbers
complex_1 = (1 + 2im)
print("A complex number: ")
println(complex_1)
print("A complex number addition operation: ")
complex_3 = complex_1 + (1 + 2im)
println(complex_3 )
print("real part of complex 1: ")
println(real(complex_1))
VSCode IDE¶
I will be posting an optional video on installing and using VSCode soon.
virtues of using an ide¶
shortcuts
snippets
short cuts that will scaffold out common blocks of code
visual debugger (this one is especially helpful for new programmers)
syntax highlighting
intellisense
the text editor gives you suggestions based on what you type which can autocomplete. (e.g. function names, variable names, types)
and so much more!
Download links and installation instructions¶
I will post a video of me showing how to do this later but here are two links if you want to try to figure it out yourself
Installing Julia (The language we are using):¶
Installing Visual Studio Code (IDE)¶
https://code.visualstudio.com/download
after installing VS code install the VS code extension: https://marketplace.visualstudio.com/items?itemName=julialang.language-julia
Helpful tutorial: https://code.visualstudio.com/docs/languages/julia