First steps#
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 resultprintln(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
begin
…end
squashes multiple expressions into one.A let block
let
…end
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 stringsstring(str1, str2, ...)
to convert the data (if neede) and make a string.^
to repeat a string:str^3
to repeatstr
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
andelse
blocks are optional.if
andend
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 subexpressionb
is only evaluated ifa
evaluates to true.In the expression
a || b
, the subexpressionb
is only evaluated ifa
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 rangeseq[1:2:end-1]
.length(seq)
returns the total sizeSplat 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#
[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 Andims(A)
the number of dimensions of Asize(A)
a tuple containing the dimensions of Asize(A,n)
the size of A along dimension neachindex(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 keysd[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 keysvalues(d)
returns a series of valuespairs(d)
returns a series of (key => value) pairsmerge(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#
Namedtuple
s 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#
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.
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).