Foundations Revisited

Welcome back! If you're reading this, it may be because you encountered some difficulties in the previous sections and got stuck, or simply desire additional instruction because you found our Syntax cheat sheet to be too brief for your needs. Or maybe you're just curious and want to learn more about the Julia language. Please notice that this chapter is just an extended version of the Syntax cheat sheet, and therefore the content might feel redundant if you carefully read the cheatsheet. However, if you are new to programming or got overwhelmed, some repetition might be helpful to you.

Before diving into this chapter, we want to encourage you to ...

  • Initially embark on the standard workshop route.
  • Connect with our moderators if certain areas prove tough.
  • If you already finished the rest of the Workshop, explore the Julia universe with some suggestions in our Resources section, instead of spending too much time here.

Variable assignment

In Julia, variables are used to store data and represent values in your program. Variables have a name and are assigned a value using the assignment operator =. Julia supports both regular variable names and Unicode variable names.

x = 1
my_variable = "awesome"
φ = 1.618
1.618

Unicode variable names

Unicode variables allow you to use a wider range of characters, including mathematical symbols, Greek letters, and other special characters. To create a Unicode variable, type a backslash \ followed by the Unicode character name, and then press the Tab key to convert it into the corresponding Unicode symbol. For example, type \alpha and then press Tab to create the α variable. If you are interested you can find a table of all available unicode characters and how to type them out in the Julia documentation.

α = 0.05
β₁ = 1.2
Δt = 0.01
0.01

Exercises

  1. Assign the Integer value 42 to a variable named the_answer_to_life.
  2. Assign the String "Julia is fun!" to a variable named yay.
  3. Assign the value of 1 + √2 divided by 2 to the variable φ (phi). Remember to use the Unicode character: type \phi or \sqrt and press Tab to convert it.
Show solution
Solution
    # 1. Assign the integer value 42 to a variable named the_answer_to_life.
    the_answer_to_life = 42

    # 2. Assign the string "Julia is fun!" to a variable named yay.
    yay = "Julia is fun!"

    # 3. Assign the value of 1 + √2 divided by 2 to the variable φ (phi).
    φ = (1 + sqrt(2)) / 2

    # Print out the variables.
    println("The answer to life: ", the_answer_to_life)
    println("Yay: ", yay)
    println("Phi: ", φ)

Functions

Excellent! Now that you have an understanding of variables, let's dive into the topic of functions.

In programming, functions are reusable blocks of code that serve a specific purpose. They play a crucial role in organizing and modularizing code, which in turn enhances its readability and maintainability. For instance, if you've written a chunk of code that performs a specific task or multiple tasks, it would be beneficial to break down the code into individual functions and give each a descriptive name. This approach makes your code more comprehensible and easier to work with. Functions also enable code reuse, minimizing redundancy and simplifying updates, which can save time and effort in the long run.

The basic syntax for defining a function in Julia is as follows:

function function_name(arguments)
    # function body
    return output
end

The return keyword plays a vital role in specifying the value that a function should output. When no return statement is explicitly provided, the function will return the value of the last expression evaluated in the function body.

However, it is generally good practice to include a return statement when defining functions using the function keyword, even if the function returns nothing. This approach helps to eliminate ambiguity and ensures that the function's expected output is clear to anyone who reads it.

For example:

function add(x, y)
    return x + y
end
add (generic function with 1 method)

You can also write:

my_function(arguments) = "function body"

For example:

square(x) = x * x
square (generic function with 1 method)

Exercises

  1. Write a function called divide that takes two arguments and returns the result of dividing the first argument by the second.
  2. Write a function called cube that takes one argument and returns the cube of the input number.
  3. Write a function that checks if a number is even. The function should take one argument and return a Bool.
Show solution
Solution
# 1. Write a function called divide that takes two arguments and returns the result of dividing the first argument by the second.
function divide(a, b)
    return a / b
end

# 2. Write a function called cube that takes one argument and returns the cube of the input number.
cube(x) = x ^ 3

# 3. Write a function that checks if a number is even. The function should take one argument and return a `Bool`.
function is_even(n)
    return n % 2 == 0
end

Vectors

In Julia, vectors are a fundamental data structure used to store and manipulate collections of values. A vector is a one-dimensional array that can store elements of the same type (e.g., integers, floating-point numbers, or strings). Vectors are useful for representing and processing sequences of data, such as time series, feature vectors, or lists of names.

To create a vector in Julia, you can use square brackets [] and separate the elements by commas. For example, to create a vector of integers, you would write:

integer_vector = [1, 2, 3, 4, 5]
5-element Vector{Int64}:
 1
 2
 3
 4
 5

You can access elements of a vector by specifying the index in square brackets. Note that Julia uses 1-based indexing, which means the first element has an index of 1:

first_element = integer_vector[1]
1

You can also assign a new value to an element of a vector by specifying the index in square brackets and using the assignment operator:

integer_vector[1] = 10
10

However, you cannot assign a new value to an element of a vector if the vector is of a fixed size. For example, if you try to assign a value to index 6 you will get an BoundsError. To add a new element to a vector, you can use the push! function:

push!(integer_vector, 6)
6-element Vector{Int64}:
 10
  2
  3
  4
  5
  6

Notice the exclamation mark at the end of the function name. This is a convention in Julia that indicates that the function mutates its arguments. This means that the function modifies the original data passed as an argument instead of creating a new copy of the data.

This will already help you to predict the behavior of the pop! function:

last_element = pop!(integer_vector)
6

Lets create two vectors and see what we can do with them:

vector1 = [1, 2, 3]
vector2 = [4, 5, 6]
3-element Vector{Int64}:
 4
 5
 6

When you use standard arithmetic operators (+, -) on vectors, Julia performs element-wise operations.

added_vectors = vector1 + vector2
subtracted_vectors = vector1 - vector2
3-element Vector{Int64}:
 -3
 -3
 -3

Scalar multiplication and division using the standard arithmetic operators (*, /):

multiplied_vector = 2 * vector1
divided_vector = vector1 / 2
3-element Vector{Float64}:
 0.5
 1.0
 1.5

Vectors can be concatenated with the vcat function or the ; operator:

concatenated_vector = vcat(vector1, vector2)
concatenated_vector = [vector1; vector2]
6-element Vector{Int64}:
 1
 2
 3
 4
 5
 6

However, when we want to multiply two vectors like this:

multiplied_vectors = vector1 * vector2
MethodError: no method matching *(::Vector{Int64}, ::Vector{Int64})

We get a MethodError because the * operator is not defined for Vectors.

Now, it depends on what we mean when we want to multiply two vectors. If we want to perform an element-wise multiplication, we need to use the broadcast . operator:

vector1 .* vector2
3-element Vector{Int64}:
  4
 10
 18

However, maybe you want to perform the dot product, that is related to matrix multiplication. It is also known as the scalar product, because it always returns a single number, i.e., a scalar. We can do this with the dot function or the operator from the LinearAlgebra package:

using LinearAlgebra
dot_product = dot(vector1, vector2)
dot_product = vector1 ⋅ vector2
32

Now, at last we can transpose a vector with the transpose function or the ' operator:

transposed_vector = transpose(vector1)
transposed_vector = vector1'
1×3 adjoint(::Vector{Int64}) with eltype Int64:
 1  2  3

Equipped with the transpose operator ', we can now multiply two vectors:

dot_product = vector1' * vector2
32

Arithmetically, this is the same as the dot product!

Another important concept before we move on is the concept of sequences in Julia.

In programming, sequences are ordered collections of elements, typically used to represent a series of values or data points. Sequences are essential in various applications, such as iterating through data, generating series of numbers, and organizing data in specific orders.

In Julia, sequences can be created using ranges. Ranges represent a series of evenly spaced values and can be created using the colon operator : or the range function.

For example, you can create a range (Start:End) of Integers from 1 to 10:

integer_sequence = 1:10
1:10

To create a range with a specific step size (Start:Step:End), you can use the following syntax:

even_sequence = 2:2:10
2:2:10

To convert a sequence to a Vector, you can use the collect function:

integer_vector = collect(integer_sequence)
10-element Vector{Int64}:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
integer_vector = collect(even_sequence)
5-element Vector{Int64}:
  2
  4
  6
  8
 10

Exercises

  1. Create a Vector of strings and concatenate it with another Vector of Strings.
  2. Create a Vector and perform an element-wise square of it.
  3. Calculate the scalar product of the Vector in the way you like.
  4. Read out an index, set an index, append an element, and pop an element from a vector.
Show solution
Solution
    # 1. Create a vector of strings and concatenate it with another vector of strings.
    somestrings = ["this", "is", "a", "vector"]
    somemore = ["this", "is", "another", "vector"]
    vcat(somestrings, somemore)

    # 2. Create a vector and perform an element-wise square of it.
    avec = [1, 2, 3]
    avec .^2

    # 3. Calculate the scalar product of the vector in the way you like.
    42 * avec

    # 4. Read out an index, set an index, append an element, and pop an element from a vector.
    avec[1]

    avec[2] = 4
    avec

    push!(avec, 5)
    pop!(avec)

Matrices

In Julia, matrices are a fundamental data structure used to store and manipulate two-dimensional arrays of values. A matrix is a rectangular grid of elements, organized into rows and columns, where each element can be accessed by its row and column indices. Matrices are useful for representing and processing structured data, such as images, tables, or linear systems of equations.

To create a Matrix in Julia, you can use square brackets [] and separate the elements within each row by spaces or commas, and separate the rows by semicolons ;. For example, to create a square matrix of integers, you would write:

square_matrix = [1 2 3; 4 5 6; 7 8 9]
3×3 Matrix{Int64}:
 1  2  3
 4  5  6
 7  8  9

You can access elements of a matrix by specifying the row and column indices in square brackets, separated by a comma. Note that Julia uses 1-based indexing, which means the first row and column have an index of 1:

upper_left_element = square_matrix[1, 1]
lower_right_element = square_matrix[3, 3]
9

As you can see, many things are the same as with vectors, which is not surprising, since vectors are just 1-dimensional matrices.

Lets define a 2x3 Matrix ...

non_square_matrix = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

... and transpose it with the transpose function or the quote ' operator:

transposed_matrix = transpose(non_square_matrix)
transposed_matrix = non_square_matrix'
3×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  4
 2  5
 3  6

Let's create two matrices and see what we can do with them:

matrix1 = [1 2; 3 4]
matrix2 = [5 6; 7 8]
2×2 Matrix{Int64}:
 5  6
 7  8

We already know from the chapter about Vectors that we can use the vcat function or semicolon ; for vertical concatenation:

vertically_concatenated_matrix = vcat(matrix1, matrix2)  # Returns [1 2; 3 4; 5 6; 7 8]
vertically_concatenated_matrix = [matrix1; matrix2]      # Also returns [1 2; 3 4; 5 6; 7 8]
4×2 Matrix{Int64}:
 1  2
 3  4
 5  6
 7  8

Concatenate two matrices horizontally using the hcat function or space :

concatenated_matrix = hcat(matrix1, matrix2)
concatenated_matrix = [matrix1 matrix2]
2×4 Matrix{Int64}:
 1  2  5  6
 3  4  7  8

Exercises

  1. Define two square matrices and add and subtract them with each other.
  2. Perform an element-wise multiplication of two matrices and then a matrix multiplication.
  3. Write a vector with spaces, e.g.,: [1 2 3]. Is that really a vector?
  4. Find out what else you can do with matrices by writing methodswith(Matrix) in the REPL.
  5. Look into the documentation of ?something you are interested in, then try it out in the REPL.
Show solution
Solution
    # 1. Define two square matrices and add and subtract them with each other.
    matrixA = [1 2; 3 4]
    matrixB = [5 6; 7 8]
    matrixAddition = matrixA + matrixB
    matrixSubtraction = matrixA - matrixB

    # 2. Perform an element-wise multiplication of two matrices and then a matrix multiplication.
    elementWiseMultiplication = matrixA .* matrixB
    matrixMultiplication = matrixA * matrixB

    # 3. Write a vector with spaces, e.g.,: [1 2 3]. Is that really a vector?
    vec = [1 2 3]
    println("1D Matrix: ")
    println(vec)
    vec = [1, 2, 3]

    # Note: For the 4th and 5th task, you'll have to use Julia's REPL (Read-Eval-Print Loop).
    # It's not possible to run these commands here.

    # 4. methodswith(Matrix) # Uncomment this line in the Julia REPL to get the methods for Matrix.

    # 5. ?rand # Uncomment this line in the Julia REPL to get the documentation for the rand function.

Arrays

Without going too much into the details, it is important to know about the concept of arrays in Julia.

Arrays are a fundamental data structure in Julia that can be used to represent and manipulate multi-dimensional collections of values. Vectors and matrices are special cases of arrays, where vectors are one-dimensional arrays and matrices are two-dimensional arrays. Arrays can have more than two dimensions, allowing you to work with higher-dimensional data structures in a consistent and efficient way.

The concept of arrays generalizes vectors and matrices by extending their properties and operations to multiple dimensions. This means that most of the functions and operations you have learned for vectors and matrices can be applied to arrays with higher dimensions as well.

Control Flow

Control flow refers to the order in which statements or instructions are executed in a program. It is an essential concept in programming, as it allows you to create more dynamic and flexible code. Two fundamental control flow structures are loops and conditional statements. Loops allow you to repeat a block of code multiple times, while conditional statements enable you to execute a block of code only if certain conditions are met.

For Loops

For loops in Julia are used to iterate over a range of values or the elements of a collection, such as an Array. The syntax for a for loop is as follows:

for variable in collection
    # Code to be executed for each value in the collection
end

For example:

for i in 1:5
    println(i^2)
end
1
4
9
16
25

If statements in Julia are used to execute a block of code only if a specific condition is met. The syntax for an if statement is as follows:

if condition
    # Code to be executed if the condition is true
end

You can also use elseif and else to test multiple conditions:

if condition1
    # Code to be executed if condition1 is true
elseif condition2
    # Code to be executed if condition1 is false and condition2 is true
else
    # Code to be executed if both condition1 and condition2 are false
end

For example, to check if a number is positive, negative, or zero:

number = -3

if number > 0
    println("The number is positive")
elseif number < 0
    println("The number is negative")
else
    println("The number is zero")
end
The number is negative

Let's remember what we learned about functions!

function classify_sign(number)
    if number > 0
        println("The number is positive")
    elseif number < 0
        println("The number is negative")
    else
        println("The number is zero")
    end
end
classify_sign (generic function with 1 method)

Now we can use the function to classify any number we want:

classify_sign(3)
classify_sign(-3)
classify_sign(0)
The number is positive
The number is negative
The number is zero

Exercises

  1. Write a for loop that prints the first 10 even numbers.
  2. Create a 2x2 Matrix and print out the value of each index. Tip: use the length function.
  3. Write a nested loop with i and j and add each index if both are equal and prints the result.
  4. Create a 3-dimension and 4-dimensional array and call size and length on them.
Show solution
Solution
    # 1. Write a for loop that prints the first 10 even numbers.
    println("First 10 even numbers: ")
    for i in 1:10
        println(2 * i)
    end

    # 2. Create a 2x2 Matrix and print out the value of each index. Tip: use the `length` function.
    matrix = [1 2; 3 4]
    println("Values in the 2x2 matrix: ")
    for i in 1:length(matrix)
        println(matrix[i])
    end

    # 3. Write a nested loop with `i` and `j` and add each index if both are equal and prints the result.
    println("Results from nested loop: ")
    for i in 1:5
        for j in 1:5
            if i == j
                println(i + j)
            end
        end
    end

    # 4. Create a 3-dimension and 4-dimensional array and call `size` and `length` on them.
    threeDArray = rand(3, 3, 3)
    fourDArray = rand(3, 3, 3, 3)
    println("Size and length of the 3D array: ")
    println(size(threeDArray))
    println(length(threeDArray))
    println("Size and length of the 4D array: ")
    println(size(fourDArray))
    println(length(fourDArray))