First steps

Comments

Comments are non-coding parts in the source code. Although the compiler does not read comments, comments are important notes for humans, making the code more readable (hopefully).

# One line comments

# Multiline comments

Variables and expressions

The assignment operator = binds a name to a piece of data.

To see the data content:

  • @show expression to show the expression and its result
  • println(data): good old print function.
  • @printf: C-like formatted output.
  • Inline display: content of the last expression. A semicolon ; at the end will suppress inline output.

An integer (64 bit)

x = 1
1

A (64-bit) floating-point number

y = 1.0
1.0

A 32-bit floating-point number. less accurate but calculates faster. Often used in GPU computing.

y = 1.0f0
1.0f0

Complex number

z = 3 + 5im
3 + 5im

Unicode names are supported. For example, \alpha<tab>

α = 3.74
3.74

Strings (Text) are surrounded by double quotes.

s = "Julia"
"Julia"

Characters are surrounded by single quotes.

c = ' '
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

Fractions (rational numbers)

station = 9 + 3//4
39//4

Constants will emit a warning if you try to change it after its creation

const theUltimateAnswer = 42
42

Print content to the terminal

println("Hello World")
Hello World
println("Hello ", s)
Hello Julia
println(1, 2, 3)
123

@show will print x = val

@show x 1+1 2-2 3*3;
x = 1
1 + 1 = 2
2 - 2 = 0
3 * 3 = 9

Types of the data

@show typeof(x) typeof(y) typeof(c)  typeof(s) typeof(z) typeof(1//2);
typeof(x) = Int64
typeof(y) = Float32
typeof(c) = Char
typeof(s) = String
typeof(z) = Complex{Int64}
typeof(1 // 2) = Rational{Int64}

convert(T,x) converts x to type T

typeof(convert(Float64, x))
Float64

There is also Type(x)

typeof(Float64(x))
Float64

Converts numbers to floating numbers

typeof(float(x))
Float64

Math expressions

Julia supports basic arithmetic operations and essential math functions by default.

Basic arithmetic

Multiple assignment

a, b = 2, 3
(2, 3)

Addition

a + b
5

Subtraction

a - b
-1

Multiplication

a * b
6

Division

b / a
1.5

Fraction

b // a
3//2

integer division: \div<tab>, the same as div(a, b)

a ÷ b
0

Modulus

b % a
1

Power

a^b
8

Comparison

Returns a boolean value (true or false)

a, b = 2, 3
(2, 3)
a < 1
false
b > 2
true
a <= b
true
a != b
true
a == b + 1
false

Chained comparisons are supported

1 < a <= b
true

Approximation operator \approx<TAB> for floating point number equivalence

1e10 + 1.0  1e10
true

The same as

isapprox(1e10 + 1.0, 1e10)
true

Math functions

Math functions in Julia

How to type π : \pi<TAB>

sin(0.5*π)
1.0

More precise

sinpi(1//2)
1.0
cos(0.5*π)
6.123233995736766e-17

More precise

cospi(1//2)
0.0

\sqrt<TAB>

sqrt(π)  √π
true

Natural log

log(10)
2.302585092994046

Common log

log10(10)
1.0

Natural exponent

exp(-5)
0.006737946999085467

expm1(x) is more accurate that exp(x) - 1 when x is very close to zero.

exp(1e-12) - 1, expm1(1e-12)
(1.000088900582341e-12, 1.0000000000005e-12)

Strings

A string is a sequence of characters.

  • " ... " for one line strings.
  • Three double quotes surround multiline string.
  • str1*str2*... to concatenate strings
  • string(str1, str2, ...) to convert the data (if needed) and make a string.
  • ^ to repeat a string: str^3 to repeat str three times.
  • [idx] to access an individual character.
  • $ to insert (or interpolate) a value into a string.

Although string(x, y) looks less streamlined, it is generally faster than interpolation $ or concatenation *.

"I am a string."
"I am a string."
"""
I am a multiline
string.

Hello Julia!
"""
"I am a multiline\nstring.\n\nHello Julia!\n"

A character is different from a string

'a' == "a"
false

How to insert contents into a string

str1 = "BEBI"
str2 = "5009"
string("The class is ", str1, '-', str2, ".")
"The class is BEBI-5009."

String interpolation $

"The class is $(str1)-$(str2)."
"The class is BEBI-5009."

String concatenation *

str1*"-"*str2
"BEBI-5009"

Compound expressions (Code blocks)

  • A begin block beginend squashes multiple expressions into one.
  • A let block letend is similar to a begin block but variables inside will be discarded outside the block.

a1 and a2 are available after begin block ends

begin
    a1 = 2
    a2 = 35
    a1 = a1 * a2
end
70

x, y, z are NOT available after let block ends

let
    x = 1
    y = 2
    z = 3

    x + y * z
end
7

Control flow

Conditional statements

  • The elseif and else blocks are optional. if and end are mandatory.
  • if blocks return a value. Capturing the value is optional.
  • if blocks are “leaky”, i.e. they do not introduce a local scope. (The same as Python)
  • Only boolean (true / false) could be evaluated in if blocks. Using other types (e.g. Int) will generate an error.

Ternary operator

cond ? T:F

ifelse function

A no-branching alternative. All the arguments are evaluated first in ifelse(cond, tvalue, fvalue).

short circuit evaluation

&& (logical and) and || (logical or) operators support short circuit evaluation.

  • In the expression a && b, the expression b would be evaluated only if a evaluates to true.
  • In the expression a || b, the expression b would be evaluated only if a evaluates to false.

Short circuit and && evaluates and returns the second argument if the first is true

(2 > 1) && println("Hi")
Hi

otherwise it returns false

(2 < 1) && println("Hi")
false

A if block can return value(s)

let score = 10
    response = if 80 < score <= 100
        "Good"
    elseif 60 < score <= 80
        "Okay"
    else
        "Uh-Oh"
    end
    response
end
"Uh-Oh"

Ternary operator

1 > 2 ? "True" : "False"
"False"

Loops

Repeated evaluations in a code block.

While loop (we will use it to solve stochastic simulations)

while cond
    expr
end

For loop (we will use it to solve ODEs)

for i in seq
    expr
end

Loop controls:

  • break: exit the loop immediately.
  • continue: move on to the predicate immediately.

Hailstone sequence (3n+1 problem) in a while loop

let n = 1025489
    step = 0
    while n > 1
        if iseven(n)
            n ÷= 2
        else
            n = 3n + 1
        end
        step += 1
    end
    step
end
77

Summation

let n = 100
    s = 0
    for i in 1:n
        s += i
    end
    s
end
5050

How continue and break work

for x in 1:9
    if x == 5
        continue ## jump to `x in 1:9`
    elseif x >=8
        break    ## jump to the end of loop
    end
    println(x, "^2 = ", x^2)
end
1^2 = 1
2^2 = 4
3^2 = 9
4^2 = 16
6^2 = 36
7^2 = 49

You can use enumerate(seq) to get a pair of index number and the element.

for (i, x) in enumerate([2, 3, 5, 7, 9, 11, 13])
    println("xs[", i, "] = ", x)
end
xs[1] = 2
xs[2] = 3
xs[3] = 5
xs[4] = 7
xs[5] = 9
xs[6] = 11
xs[7] = 13

Multiple nested for loops can be combined into a single outer loop, forming the cartesian product of its iterables.

for i = 'x':'z', j = '1':'3'
    println(i, j)
end
x1
x2
x3
y1
y2
y3
z1
z2
z3

Functions

In Julia, a function is an object that maps a tuple of argument values to a return value. Julia docs

Functions could do: - Code reuse and encapsulation. - Specializations of Methods for different data types.

Notes: - Functions are first-class objects and can be passed into a higher-order function. - The arguments are “passed-by-sharing” (Similar to Python). Modifications to mutable argument values (such as Arrays) will be reflected to the caller. - By convention, functions that will update the arguments are named with a bang !. (e.g. sort(arr) vs sort!(arr)) - For element-wise operations, use the broadcast (dot) syntax. e.g. sqrt.(arr) - You can write multiple functions with the same name provided they have distinct parameters. Julia will choose the most appropriate one according to the input.

Standard form

"Michaelis-Menton function"  ## Function documentations
function mm(x, k)            ## function name and parameter list
    result = x / (x +k)      ## Doing stuff
    return result            ## Return statement
end                          ## End of function
Main.var"##277".mm

Call the function

mm(1, 0.5)
0.6666666666666666

One-liner form

f(x, y) = x + y
f(1, 2)
3

And you can also reuse previously-defined functions

mm(x) = mm(x, one(x))
mm(1)
0.5

Anonymous functions

Anonymous functions are often used with other functions that take in another function. e.g. map(func, seq)

g = x->x^2
g(3)
9
map(x->x^2, 1:3)
3-element Vector{Int64}:
 1
 4
 9

Use the do block for long anonymous functions.

val = rand(-6:6, 10)

map(val) do x
    res = if x < 0 && iseven(x)
        zero(x)
    elseif x == 0
        one(x)
    else
        x
    end
    res
end
10-element Vector{Int64}:
 2
 0
 1
 0
 6
 1
 0
 3
 1
 2

The same as

map(x -> begin
    res = if x < 0 && iseven(x)
        zero(x)
    elseif x == 0
        one(x)
    else
        x
    end
    res
end, val)

Optional arguments

Optional (positional) arguments are listed after mandatory ones.

function func(a, b, c=1)
    ## do_stuff
end

And they are called with func(a, b) or func(a, b, 3)

Keyword arguments

Keyword arguments are listed after ;. They are called by name rather than order.

function plot(x, y; style="solid", width=1, color="black")
    ## do_stuff
end

And they are called with plot(x, y, width=2) or plot(x, y; width=2)

See also

Collections

Using built-in collections is the simplest way to group and organize data.

The values in a immutable collection cannot be updated after its creation, while in a mutable collection can.

The elements in sequential collections are accessed by integer indices, while those in associative collection are accessed by keys.

Sequential collections

General rules for sequential collections:

  • 1-based indexing, as in R, MATLAB, and Fortran.
  • Elements are accessed by an integer index seq[i] or an integer range seq[1:2:end-1].
  • length(seq) returns the total size
  • Splat operator ... passes the inner contents in the collection as positional function arguments.
  • Dot syntax (e.g. a .+ b) performs element-wise / broadcasting operations.

Ranges

start[:step]:end

  • Immutable
  • Sequential
  • Cheap
  • Evenly-spaced numerical sequences

A simple range starts from one, and stops at ten, with a step size of two,.

1:2:10
1:2:9

Length of a sequence

length(1:2:10)
5

Show its content

dump(1:2:10)
StepRange{Int64, Int64}
  start: Int64 1
  step: Int64 2
  stop: Int64 9

Explicit range function

range(1, 10, length=10)
1.0:1.0:10.0

Pick an element

(1:10)[3]
3

Pick elements by a range of indices

(1:10)[3:end]
3:10

Tuples

  • immutable
  • sequential collections
  • efficient for heterogenous data of various types
  • stack-allocated
tuple(1, 'a', 3.14)
(1, 'a', 3.14)

Tuples are usually written as

(1, 'a', 3.14)
(1, 'a', 3.14)

Pick elements You cannot change the elements once its created. (immutable)

t1 = (1, 2, 3)
t1[1]
1
t2 = (1, 'a', 3.14)
dump(t2)
Tuple{Int64, Char, Float64}
  1: Int64 1
  2: Char 'a'
  3: Float64 3.14

Merging multiple tuples using the splat (…) operator

tuple(t1..., t2...)

# Tuples could be used to swap elements
let x = 1, y = 2, z = 3
    x, y, z = z, x, y
    @show x, y, z
end;
(x, y, z) = (3, 1, 2)

Tuple can return multiple values from a function

neighbors(x) = x+1, x-1
neighbors(0)
(1, -1)
extrema([1, 5, 6, 7, -1, -3, 0])
(-3, 7)
sincospi(1//2)
(1.0, 0.0)

Arrays

Arrays in Julia docs

[seq...] / collect(seq)

Arrays are the bread and butter for scientific computing, similar to numpy’s ndarrays.

  • Column-major (Fortran style) rather than row-major (C and numpy style)
  • Assignments and updating may cause unwanted editing due to memory sharing.

Some useful functions for arrays:

  • length(A) the number of elements in A
  • ndims(A) the number of dimensions of A
  • size(A) a tuple containing the dimensions of A
  • size(A,n) the size of A along dimension n
  • eachindex(A) an efficient iterator for visiting each position in A

1D array (column vector)

x = [5, 6, 7]
3-element Vector{Int64}:
 5
 6
 7

np.arange() equivalent

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

Array with all zeroes

zeros(2, 5, 2)
2×5×2 Array{Float64, 3}:
[:, :, 1] =
 0.0  0.0  0.0  0.0  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  0.0  0.0  0.0  0.0

Array with all ones

ones(2, 5)
2×5 Matrix{Float64}:
 1.0  1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0  1.0

Uninitialized array with the same data type and dims as x

similar(x)
3-element Vector{Int64}:
 139987819608464
 139987819608912
 139987819429896

np.zeros_like()

zero(x)
3-element Vector{Int64}:
 0
 0
 0

Array of random numbers

rand(1:6, 10)
10-element Vector{Int64}:
 3
 4
 3
 1
 2
 3
 3
 4
 2
 3
rand(1:6, 2, 2)
2×2 Matrix{Int64}:
 5  3
 4  2

Reshape an array

reshape(1:12, 3, 4)
3×4 reshape(::UnitRange{Int64}, 3, 4) with eltype Int64:
 1  4  7  10
 2  5  8  11
 3  6  9  12

Reshape A to an (1D) vector

vec(rand(1:6, 2, 2))
4-element Vector{Int64}:
 3
 5
 6
 2

repeat the array 3x2 times

repeat([1 2; 3 4], 3, 2)
6×4 Matrix{Int64}:
 1  2  1  2
 3  4  3  4
 1  2  1  2
 3  4  3  4
 1  2  1  2
 3  4  3  4

comprehension for 1D array

[i^2 for i in 1:10 if i >= 5]
6-element Vector{Int64}:
  25
  36
  49
  64
  81
 100

2D comprehension for a 2x3 array

[x * y for x in 1:2, y in 1:3]
2×3 Matrix{Int64}:
 1  2  3
 2  4  6

casting comprehension result element type to Float64

Float64[x^2 for x in 1:4]
4-element Vector{Float64}:
  1.0
  4.0
  9.0
 16.0

This is a 1-element tuple containing a vector

tuple([1,2,3])
([1, 2, 3],)

How to convert vector to tuple

Tuple([1,2,3])
(1, 2, 3)

2D array (matrix) A space is a shorthand for hcat() A semicolon is a shorthand for vcat()

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

Accessing elements

A[1, 2]
2

Accessing a range of elements

A[1:2, 2:3]
2×2 Matrix{Int64}:
 2  3
 5  6

Array total length

length(A)
6
axes(A)
(Base.OneTo(2), Base.OneTo(3))
size(A)
(2, 3)
ndims(A)
2
transpose(A)
3×2 transpose(::Matrix{Int64}) with eltype Int64:
 1  4
 2  5
 3  6

(Conjugate transpose) Adjoint

A'
3×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  4
 2  5
 3  6

Matrix-vector multiplication

b = A * x
2-element Vector{Int64}:
 38
 92

Find x for Ax = b, using left division operator /

A\b  x
true

Flatten A to an (1D) vector

vec(A)
6-element Vector{Int64}:
 1
 4
 2
 5
 3
 6

Arrays are mutable (i.e. you can update the contents) objects You should make a copy if you want the original one intact

A[1, 1] = 0
A
2×3 Matrix{Int64}:
 0  2  3
 4  5  6

Associative collections

  • d[key] accesses values by keys
  • d[key] = value sets a key-value pair for a mutable dictionary.
  • delete!(d, key) deletes the kay (and its partner) from a mutable dictionary.
  • keys(d) returns a series of keys
  • values(d) returns a series of values
  • pairs(d) returns a series of (key => value) pairs
  • merge(d1, d2, ...) return combinations of several dicts. merge!(d1, d2, ...) combine several dicts and update the first one.
  • get(d, key, default) returns the value stored for the given key, or the given default value if no mapping for the key is present.

Named tuples

Namedtuples are tuples with key-value pairs.

nt = (a=1, b=2, c=4)
(a = 1, b = 2, c = 4)
nt[1]
1
nt.a == nt[:a] == nt[1]
true

How to fill a named tuple elegantly

a = 1
b = 2
c = 3

nt = (; a, b, c)
(a = 1, b = 2, c = 3)

Dictionaries

Dictionaries are mutable mappings of key => value.

eng2sp = Dict("one" => "uno", "two" => "dos", "three" => "tres")
Dict{String, String} with 3 entries:
  "two"   => "dos"
  "one"   => "uno"
  "three" => "tres"
eng2sp["two"]
"dos"
eng2sp["five"] = "cinco"
"cinco"
keys(eng2sp)
KeySet for a Dict{String, String} with 4 entries. Keys:
  "two"
  "one"
  "three"
  "five"
values(eng2sp)
ValueIterator for a Dict{String, String} with 4 entries. Values:
  "dos"
  "uno"
  "tres"
  "cinco"
get(eng2sp, "one", "N/A")
"uno"
get(eng2sp, "four", "N/A")
"N/A"
haskey(eng2sp, "one")
true

Elements are not ordered

for (k ,v) in eng2sp
    println(k, " => ", v)
end
two => dos
one => uno
three => tres
five => cinco

Creating a dict from an array of tuples

Dict([('a', 1), ('c', 3), ('b', 2)])
Dict{Char, Int64} with 3 entries:
  'a' => 1
  'c' => 3
  'b' => 2

Creating a Dict via a generator (similar to comprehensions)

Dict(i => i^2 for i = 1:10)
Dict{Int64, Int64} with 10 entries:
  5  => 25
  4  => 16
  6  => 36
  7  => 49
  2  => 4
  10 => 100
  9  => 81
  8  => 64
  3  => 9
  1  => 1
Dict(zip("abc", 1:3))
Dict{Char, Int64} with 3 entries:
  'a' => 1
  'c' => 3
  'b' => 2

Broadcasting (Dot) syntax

Broadcasting turns scalar operations into vector ones.

[1, 2, 3] .+ [4, 5, 6]
3-element Vector{Int64}:
 5
 7
 9
[1, 2, 3] .+ 4
3-element Vector{Int64}:
 5
 6
 7

Element-wise operation

sinpi.([0.5, 1.0, 1.5, 2.0])
4-element Vector{Float64}:
  1.0
  0.0
 -1.0
  0.0

Create a dictionary with a list of keys and a list of values

ks = (:a, :b, :c)
vs = (1, 2, 3)
Dict(ks .=> vs)
Dict{Symbol, Int64} with 3 entries:
  :a => 1
  :b => 2
  :c => 3

How to do logspace() in Julia

exp10.(range(-3.0, 3.0, 50))
50-element Vector{Float64}:
    0.001
    0.0013257113655901094
    0.0017575106248547913
    0.002329951810515372
    0.0030888435964774815
    0.004094915062380423
    0.005428675439323859
    0.0071968567300115215
    0.009540954763499945
    0.012648552168552958
    ⋮
  104.81131341546852
  138.94954943731375
  184.20699693267164
  244.20530945486522
  323.74575428176433
  429.1934260128778
  568.9866029018299
  754.3120063354615
 1000.0

Make a 9*9 multiplication table

collect(1:9) .* transpose(collect(1:9))
9×9 Matrix{Int64}:
 1   2   3   4   5   6   7   8   9
 2   4   6   8  10  12  14  16  18
 3   6   9  12  15  18  21  24  27
 4   8  12  16  20  24  28  32  36
 5  10  15  20  25  30  35  40  45
 6  12  18  24  30  36  42  48  54
 7  14  21  28  35  42  49  56  63
 8  16  24  32  40  48  56  64  72
 9  18  27  36  45  54  63  72  81

Custom data structures and Methods

https://docs.julialang.org/en/v1/manual/types/#Composite-Types

struct or mutable struct

struct Point
    x
    y
end

Define a default constructor

Point() = Point(0.0, 0.0)
Main.var"##277".Point
p1 = Point(1.0, 2.0)
p2 = Point(-3.0, 2.0)
Main.var"##277".Point(-3.0, 2.0)

Define a method for our custom type

add(a::Point, b::Point) = Point(a.x + b.x, a.y + b.y)
add (generic function with 1 method)
add(p1, p2)
Main.var"##277".Point(-2.0, 4.0)

Methods

Methods in Julia docs

You can overload the same function with different argument types/numbers. Julia will try to find the right function for the argument type(s).

func(a::Int) = a + 2
func(a::AbstractFloat) = a/2
func(a::Rational) = a//11
func(a::Complex) = sqrt(a)
func(a, b::String) = "$a, $b"
func (generic function with 5 methods)
func(1)
3
func(3.0)
1.5
func(33//4)
3//4
func(-2 + 0im)
0.0 + 1.4142135623730951im
func(true, "it just works")
"true, it just works"

This notebook was generated using Literate.jl.

Back to top