First steps#

Comments#

Comments are non-coding part 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

Also a (64-bit) floating-point

y = 1.
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: “\alpha

α = 3.74
3.74

Strings (Text) are surrounded by double quotes. NOT SINGLE 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
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
@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

Or this convenience function

typeof(float(x))
Float64

Compound expressions#

  • 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.

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

Math expressions#

Julia supports basic arithmatic 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 / 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#

\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

Natual log

log(10)
2.302585092994046

Common log

log10(10)
1.0

Natural exponant

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 neede) 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 * and is most recommended.

"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."

Use string interpolation

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

concat string using *

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

Control flow#

Julia programs are able to run nonlinearly by controlling its execution 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.

ifelse function#

All the arguments are evaluated first in ifelse(cond, tvalue, fvalue).

short cicuit evaluation#

&& (logical and) and || (logical or) operators support [short circuit evaluation]( Short-circuit evaluation - Wikipedia https://en.wikipedia.org › wiki › Short-circuit_evaluation).

  • In the expression a && b, the subexpression b is only evaluated if a evaluates to true.

  • In the expression a || b, the subexpression b is only evaluated if a evaluates to false.

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

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

&& otherwise returns false

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

if block has 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#

Loops are repeated evaluations of a code block.

  • while loops are often related to a predicate.

  • for loops are often related to a sequence.

  • break: exit the loop immediately.

  • continue: move on to the next item / evaluation 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

For loop

for x in 1:9
    if x == 5
        continue ## jump to line #2
    elseif x >=8
        break    ## jump to line #9
    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

Use enumerate(seq) to get a pair of idx and element

for (i, x) in enumerate(10:-1:1)
    println("xs[", i, "] = ", x)
end
xs[1] = 10
xs[2] = 9
xs[3] = 8
xs[4] = 7
xs[5] = 6
xs[6] = 5
xs[7] = 4
xs[8] = 3
xs[9] = 2
xs[10] = 1

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 facilitate:

  • Code reuse and encapsulation.

  • Specializations (Methods)

  • Functions are first-class objects and can be passed into a higher-order function.

  • The arguments are “passed-by-sharing”. Modifications to mutable argument values (such as Arrays) will be reflected to the caller. (Similar to Python)

  • Functions that will update the arguments are named with a bang ! by convention. (e.g. sort(arr) vs sort!(arr))

  • Often only the scalar version of a function is required; for vector element-wise operations, there are broadcast (dot) syntax.

  • You can write multiple function with the same name provided they have different parameter lists. Julia will choose the most apporpriate one for you.

Standard form#

"Mechaelis-Menton function"  ## Function documentations
function mm(x, k)            ## function name and parameter list
  result = x / (x +k)        ## Doing calculations
  return result              ## return statement is optional
end
Main.var"##231".mm
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()

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

Use 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}:
 -3
 -1
  4
  5
  4
 -3
  6
  6
  0
 -5

Optional arguments#

Optional (positional) arguments are listed after mandatory ones.

function func(a, b, c=1)

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")
    ...
end

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

args_kwargs(args...; kwargs...) = (args, kwargs)  ## Note the semicolon

args_kwargs(1, 2, 3; a=4, b=5.0, c="Hello")
((1, 2, 3), Base.Pairs{Symbol, Any, Tuple{Symbol, Symbol, Symbol}, @NamedTuple{a::Int64, b::Float64, c::String}}(:a => 4, :b => 5.0, :c => "Hello"))

See also#

Collections, broadcasting, and Methods#

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

  • Immuatable

  • Sequencial

  • Eheap

  • Evenly-spaced numerical sequences

A simple range

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

linspace() equivalent

LinRange(1, 10, 10)
10-element LinRange{Float64, Int64}:
 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0

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)

Usually written as

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

Accessing elements

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)

Arryas 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 cuase 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}:
 139687807699272
   2379411882717
 139687807176512

np.zeros_like()

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

Array of random numbers

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

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}:
 6
 4
 3
 4

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) space is a shorthand for hcat() semicolone 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 arrya 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.(LinRange(-3.0, 3.0, 50))
50-element Vector{Float64}:
    0.001
    0.0013257113655901094
    0.001757510624854793
    0.002329951810515372
    0.0030888435964774785
    0.004094915062380427
    0.005428675439323859
    0.007196856730011514
    0.009540954763499934
    0.012648552168552958
    ⋮
  104.81131341546875
  138.9495494373136
  184.20699693267164
  244.205309454865
  323.7457542817647
  429.1934260128778
  568.9866029018293
  754.3120063354608
 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#

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"##231".Point
p1 = Point(1.0, 2.0)
p2 = Point(-3.0, 2.0)
Main.var"##231".Point(-3.0, 2.0)

Define a method for our cutsom 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"##231".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 and compiles down to optimal code")
"true, it just works and compiles down to optimal code"

This notebook was generated using Literate.jl.