1. Intro 2: Basic Concepts in Julia¶
ISE 754, Fall 2024
1. The Julia Environment¶
After launching Julia, execute using IJulia followed by jupyterlab() at the command prompt to launch Jupyter in your default browser. Julia has an extensive set of built-in functions as well as additional packages that consist of functions related to more specialized topics. It can be used in two different ways: as a traditional programming environment and as an interactive calculator. In calculator mode (running Julia either in Jupyter or at the command prompt), the built-in and package functions provide a convenient means of performing one-off calculations and graphical plotting; in programming mode, running Julia in an IDE like Visual Studio Code (VS Code) with Julia extensions, provides a programming environment (editor, debugger, and profiler) that enables the user to write their own functions and scripts.
2. Creating Arrays¶
In Julia, integer and real-valued scalars are distinguished, and vectors and matrices are special 1- and 2-dimensional cases of an n-dimensional array:
- Scalar $ n = 1 $ is an
Int64integer variable - Scalar $ x = 1. $ is a
Float64real-valued variable - Vector $ {\bf{a}} = \left[ {\begin{array}{c} 1\\2\\3 \end{array}} \right] $ is a 3-element
Array{Int64,1}1-dimensional integer array - Matrix $ {\bf{A}} = \left[ {\begin{array}{c}1&2&3&4\\5&6&7&8\end{array}} \right] $ is a
2×4 Array{Int64,2}2-dimensional integer array.
Scalar variables and arrays can be created as follows (# is used for comments in code, Markdown is used for this comment since it is in a separate non-code cell):
n = 1 # Integer number
1
x = 1. # Real (or floating point) number
1.0
Int(x) # Convert real to integer
1
Float64(n) # Convert integer to real
1.0
a = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
A = [1 2 3 4; 5 6 7 8]
2×4 Matrix{Int64}:
1 2 3 4
5 6 7 8
In Julia, the case of a variable matters; e.g., the arrays a and A are different variables. The last expression in a cell is displayed. To suppress the output, end the expression with a semicolon (;):
A = [1 2 3 4; 5 6 7 8];
An empty array is considered a vector of type Any (note: since only the last expression in a cell is displayed, the macro @show can be used to display an expression that is not the last):
@show a = []
@show typeof(a)
a
a = [] = Any[]
typeof(a) = Vector{Any}
Any[]
The following operators and functions can be used to automatically create basic structured arrays:
a = collect(1:5)
a = [1:5;] # Note: the ';' is used to indicate that the 5-element array should be generated
5-element Vector{Int64}:
1
2
3
4
5
a = [1:5]
1-element Vector{UnitRange{Int64}}:
1:5
[1:5] only creates a 1-element UnitRange variable. It is used for iteration and is efficient since [1:50000000000000] would also be a 1-element variable, while [1:50000000000000;] or collect(1:50000000000000) would attempt to create a vector that would likely exceed the RAM on your computer.
@show typeof([1:5;])
@show [1:5]
typeof(1:5)
typeof([1:5;]) = Vector{Int64}
[1:5] = UnitRange{Int64}[1:5]
UnitRange{Int64}
a = [1:2:5;]
3-element Vector{Int64}:
1
3
5
a = [10:-2:1;]
5-element Vector{Int64}:
10
8
6
4
2
a = ones(5) # Default is floating point
5-element Vector{Float64}:
1.0
1.0
1.0
1.0
1.0
a = ones(Int, 5) # Integer ones
5-element Vector{Int64}:
1
1
1
1
1
a = ones(5, 1) # 5x1 is different from a 5-element array (2-D vs. 1-D)
5×1 Matrix{Float64}:
1.0
1.0
1.0
1.0
1.0
a = zeros(5)
5-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
Array comprehensions can be used to create custom-valued arrays.
a = [i^2 + i + 1 for i in 0:5]
6-element Vector{Int64}:
1
3
7
13
21
31
The rand function generates random numbers between 0 and 1. (Each time it is run, it generates different numbers.)
a = rand(3)
3-element Vector{Float64}:
0.0029016732080523466
0.939772531950368
0.16140671757896563
b = rand(3)
3-element Vector{Float64}:
0.2739916496461332
0.12646758295412197
0.3486956748317406
B = rand(2, 3)
2×3 Matrix{Float64}:
0.978608 0.643051 0.524561
0.0569324 0.378742 0.58227
a = rand(1:100, 5) # Generate 5 random integers between 1 and 100
5-element Vector{Int64}:
11
78
84
56
56
A random permutation of the integers 1 to n can be generated using the randperm(n) function, which is in the Random package and using Random is used to load it the first time it is called:
using Random
randperm(5)
5-element Vector{Int64}:
3
5
1
2
4
List the variables currently in the workspace:¶
varinfo()
| name | size | summary |
|---|---|---|
| A | 104 bytes | 2×4 Matrix{Int64} |
| B | 88 bytes | 2×3 Matrix{Float64} |
| Base | Module | |
| Core | Module | |
| Main | Module | |
| a | 80 bytes | 5-element Vector{Int64} |
| b | 64 bytes | 3-element Vector{Float64} |
| n | 8 bytes | Int64 |
| x | 8 bytes | Float64 |
Variables cannot be removed from the worspace and, instead, can be set equal to nothing to that the memory they were using is freed:
A = nothing
a = nothing
varinfo()
| name | size | summary |
|---|---|---|
| A | 0 bytes | Nothing |
| B | 88 bytes | 2×3 Matrix{Float64} |
| Base | Module | |
| Core | Module | |
| Main | Module | |
| a | 0 bytes | Nothing |
| b | 64 bytes | 3-element Vector{Float64} |
| n | 8 bytes | Int64 |
| x | 8 bytes | Float64 |
a = [10:15;]
a[3] # Select single element
12
a[[2, 4]] # Select multiple elements
2-element Vector{Int64}:
11
13
idx = [2, 4] # Use index array
a[idx]
2-element Vector{Int64}:
11
13
using Random
Random.seed!(1234) # Set seed to allow replication
x = rand(3) # Array of random values
3-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
0.21858665481883066
x[1]
0.32597672886359486
Random.seed!(1234)
x = rand(1) # Want single random value, but returned as 1-element array
1-element Vector{Float64}:
0.32597672886359486
Random.seed!(1234)
x = rand(1)[] # Returned as single value
0.32597672886359486
2-D arrays¶
The colon operator : is used to select an entire row or column:
A = [1 2 3 4; 5 6 7 8]
A[:, :]
2×4 Matrix{Int64}:
1 2 3 4
5 6 7 8
A[1, 2] # Select single element
2
A[1, :] # Select single row
4-element Vector{Int64}:
1
2
3
4
A[:, 1] # Select single column
2-element Vector{Int64}:
1
5
A[:, [1,3]] # Select two columns
2×2 Matrix{Int64}:
1 3
5 7
The vector $ \left[ {\begin{array}{c} 1,3 \end{array}} \right] $ is an index array, where each element corresponds to a column index number of the original matrix A. The keyword end can be used to indicate the last row or column:
A[:, end]
2-element Vector{Int64}:
4
8
A[:, end-1]
2-element Vector{Int64}:
3
7
The selected portion of the one array can be assigned to a new array:
B = A[:, 3:end]
2×2 Matrix{Int64}:
3 4
7 8
a = [1:5;]
5-element Vector{Int64}:
1
2
3
4
5
a[2] = 6
6
Note: Just the modified element of a is displayed. Type a on a second line in the cell to display the modified a in full:
a[2] = 6
a
5-element Vector{Int64}:
1
6
3
4
5
Assign a value to multiple locations:
a[[1, 3]] = 0 # Error: need to use dot so that assignment is made to multiple elements in array
ArgumentError: indexed assignment with a single value to possibly many locations is not supported; perhaps use broadcasting `.=` instead?
Stacktrace:
[1] setindex_shape_check(::Int64, ::Int64)
@ Base .\indices.jl:261
[2] _unsafe_setindex!(::IndexLinear, A::Vector{Int64}, x::Int64, I::Vector{Int64})
@ Base .\multidimensional.jl:953
[3] _setindex!
@ .\multidimensional.jl:944 [inlined]
[4] setindex!(A::Vector{Int64}, v::Int64, I::Vector{Int64})
@ Base .\abstractarray.jl:1396
[5] top-level scope
@ In[45]:1
Why all the dots?¶
a[[1, 3]] .= 0 # Note: '.=' is used instead of '=' to assign to mulitple locations
a
5-element Vector{Int64}:
0
6
0
4
5
a[[1, 3]] .= [7,8]
a
5-element Vector{Int64}:
7
6
8
4
5
A[1, 2] = 100
A
2×4 Matrix{Int64}:
1 100 3 4
5 6 7 8
Delete selected array elements:¶
a = [1:5;]
deleteat!(a, 3) # Note: the "!" at end of function name is used to signify that the values of input
a # variables are changed by the function (don't need to assign output)
4-element Vector{Int64}:
1
2
4
5
deleteat!(a,[1,4])
a
2-element Vector{Int64}:
2
4
Delete and return last element:
a = [1:5;]
@show b = pop!(a)
a
b = pop!(a) = 5
4-element Vector{Int64}:
1
2
3
4
@show b = popfirst!(a) # Delete and return first element
a
b = popfirst!(a) = 1
3-element Vector{Int64}:
2
3
4
Insert selected array elements:¶
@show a = [3:5;]
insert!(a, 2, 8) # Insert scalar value 8 into array at location 2
a = [3:5;] = [3, 4, 5]
4-element Vector{Int64}:
3
8
4
5
push!(a, 6) # Insert 6 at end of array
5-element Vector{Int64}:
3
8
4
5
6
pushfirst!(a, 7) # Insert 7 at beginning of array
6-element Vector{Int64}:
7
3
8
4
5
6
@show b = [10:12;]
append!(a, b) # Append another array to end of array
b = [10:12;] = [10, 11, 12]
9-element Vector{Int64}:
7
3
8
4
5
6
10
11
12
c = ones(3)
append!(a,c) # Append another array to end of array
12-element Vector{Int64}:
7
3
8
4
5
6
10
11
12
1
1
1
a = [1:4;]
2 + a # Error: '+' used instead of '.+'
MethodError: no method matching +(::Int64, ::Vector{Int64})
For element-wise addition, use broadcasting with dot syntax: scalar .+ array
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...)
@ Base operators.jl:587
+(::Real, ::Complex{Bool})
@ Base complex.jl:319
+(::Array, ::Array...)
@ Base arraymath.jl:12
...
Stacktrace:
[1] top-level scope
@ In[58]:2
2 .+ a # Note: '.+' is used instead of '+'
4-element Vector{Int64}:
3
4
5
6
Broadcasting: Automatically expanding a value to have a compatible size another; e.g.,
2 .+ a == [2, 2, 2, 2] + a
[2, 2, 2, 2] + a
4-element Vector{Int64}:
3
4
5
6
b = 2 * a # Note: '*' OK for multiplication; reason?, see next cell
4-element Vector{Int64}:
2
4
6
8
b = 2a # Don't need '*' if multipying a number and variable
4-element Vector{Int64}:
2
4
6
8
Question 1.2.1¶
What is the value of b?
a = [1:3;]
3 .+ a
b = 4a
(a) b = [4, 8, 12]
(b) b = [16, 20, 24]
(c) b = [12, 24, 36]
(d) b = [7, 11, 15]
Summation¶
The elements of a single array can be added together using
the sum and cumsum functions.
@show a = [1:5;]
sum(a) # (Array summation)
a = [1:5;] = [1, 2, 3, 4, 5]
15
cumsum(a) # (Cumulative summation)
5-element Vector{Int64}:
1
3
6
10
15
By default, Julia sums all the elements in a matrix. To sum each row or column of a matrix, the dimension must be specified:
A = [1 3 4; 5 7 8]
sum(A) # (Sum entire matrix)
28
sum(A, dims = 1) # (Sum along columns)
1×3 Matrix{Int64}:
6 10 12
sum(A, dims = 2) # (Sum along rows)
2×1 Matrix{Int64}:
8
20
6. Tuples¶
Arrays gain much of their utility because they are mutable, allowing elements to be easily added or deleted. Tuples are similar to arrays except that they are immutable: once defined, they can not be changed. This can be useful because it allows Julia to more efficiently process tuples because they're guaranteed not to change.
t = (6, 1, 4) # 3-tuple
(6, 1, 4)
typeof(t)
Tuple{Int64, Int64, Int64}
t[2] # Access second element
1
t[2] = 3 # Can't reassign tuple element (immutable)
MethodError: no method matching setindex!(::Tuple{Int64, Int64, Int64}, ::Int64, ::Int64)
Stacktrace:
[1] top-level scope
@ In[71]:1
pop!(t, 2) # Can't remove last element (immutable)
MethodError: no method matching pop!(::Tuple{Int64, Int64, Int64}, ::Int64)
Closest candidates are:
pop!(::BitSet, ::Integer, ::Any)
@ Base bitset.jl:263
pop!(::BitSet, ::Integer)
@ Base bitset.jl:254
pop!(::Base.IdSet, ::Any)
@ Base idset.jl:22
...
Stacktrace:
[1] top-level scope
@ In[72]:1
v = collect(t) # Convert tuple to vector
3-element Vector{Int64}:
6
1
4
v[2] = 3 # Can reassign vector element (mutable)
v
3-element Vector{Int64}:
6
3
4
t = (5) # Want a 1-tuple but got a scalar
typeof(t)
Int64
t = (5,) # Got a 1-tuple containing an integer
typeof(t)
Tuple{Int64}
t = (5.0,) # Got a 1-tuple containing a real (floating point) number)
typeof(t)
Tuple{Float64}
function fun1(a)
b = 3a + 1
println("b = ", b)
if b % 2 == 0 # Check if c is even
c = b/2
else
c = (b - 1)/2
end
return c
end
fun1(5) # Output returned as Float64
b = 16
8.0
fun1(8.)
b = 25.0
12.0
Multiple outputs returned as a tuple¶
function fun2(a)
b = 3a + 1
println("b = ", b)
if b % 2 == 0 # Check if c is even
c = b/2
else
c = (b - 1)/2
end
return b, c
end
fun2(5) # Output returned as 2-tuple
b = 16
(16, 8.0)
fun2(5)[1] # Get only the first value
b = 16
16
fun2(5)[2] # Get only the second value
b = 16
8.0
One-line (named) functions¶
fun3(a) = 3a + 1
fun3(8)
25
Anonymous (unnamed) functions¶
x -> 3x + 1
#3 (generic function with 1 method)
map(x -> 3x + 1, 8) # `map` applies a function (x -> 3x + 1) to a value (8)
25
map(x -> 3x + 1, [8, 3, 7]) # Can also apply to a collection [8, 3, 7]
3-element Vector{Int64}:
25
10
22
[3x + 1 for x in [8, 3, 7]] # Array comprehension gives same result
3-element Vector{Int64}:
25
10
22
afun = x -> 3x + 1 # Can assign to a variable `afun`
#11 (generic function with 1 method)
afun(8) # Can now be used like a named function
25
varinfo() # Still not a named function
| name | size | summary |
|---|---|---|
| A | 88 bytes | 2×3 Matrix{Int64} |
| B | 72 bytes | 2×2 Matrix{Int64} |
| Base | Module | |
| Core | Module | |
| Main | Module | |
| a | 80 bytes | 5-element Vector{Int64} |
| afun | 0 bytes | #11 (generic function with 1 method) |
| b | 72 bytes | 4-element Vector{Int64} |
| c | 64 bytes | 3-element Vector{Float64} |
| fun1 | 0 bytes | fun1 (generic function with 1 method) |
| fun2 | 0 bytes | fun2 (generic function with 1 method) |
| fun3 | 0 bytes | fun3 (generic function with 1 method) |
| idx | 56 bytes | 2-element Vector{Int64} |
| n | 8 bytes | Int64 |
| t | 8 bytes | Tuple{Float64} |
| v | 64 bytes | 3-element Vector{Int64} |
| x | 8 bytes | Float64 |
x1 = (3, 1) # Using 2-tuple for each 2-D point, could also use 2-vector [3, 1]
x2 = (6, 5)
x1 - x2
MethodError: no method matching -(::Tuple{Int64, Int64}, ::Tuple{Int64, Int64})
Stacktrace:
[1] top-level scope
@ In[91]:3
x1 .- x2
(-3, -4)
(x1 .- x2).^2
(9, 16)
sum((x1 .- x2).^2)
25
sqrt(sum((x1 .- x2).^2))
5.0
d2(x1, x2) = sqrt(sum((x1 .- x2).^2))
d2(x1, x2)
5.0
d2((3, 1, 7), (6, 5, 2)) # Get 3-D for free
7.0710678118654755
d2(3, 6) # Also get 1-D (for free)
3.0
Calculate distances from x to other points stored as an array/vector pt of 2-tuples
pt = [(1, 1), (6, 1), (6, 5)]
x = (3, 1)
[d2(x, i) for i in pt] # Array comprehension
3-element Vector{Float64}:
2.0
3.0
5.0
[d2(x, i) for i ∈ pt] # type "\in" followed by TAB to get element of symbol
3-element Vector{Float64}:
2.0
3.0
5.0
Broadcast d2:
d2.(x, pt) # x is a tuple of integers and pt an array of tuples
DimensionMismatch: arrays could not be broadcast to a common size; got a dimension with lengths 2 and 3
Stacktrace:
[1] _bcs1
@ .\broadcast.jl:555 [inlined]
[2] _bcs
@ .\broadcast.jl:549 [inlined]
[3] broadcast_shape
@ .\broadcast.jl:543 [inlined]
[4] combine_axes
@ .\broadcast.jl:524 [inlined]
[5] instantiate
@ .\broadcast.jl:306 [inlined]
[6] materialize(bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(d2), Tuple{Tuple{Int64, Int64}, Vector{Tuple{Int64, Int64}}}})
@ Base.Broadcast .\broadcast.jl:903
[7] top-level scope
@ In[101]:1
d2.([x], pt) # Make x an array of tuples and broadcast function d2
3-element Vector{Float64}:
2.0
3.0
5.0
1-D points:
pt = [1, 4, 6]
x = 3
d2(x, pt) # Why is this "working" in 1-D, put gave error in 2-D?
3.7416573867739413
d2((x, x, x), pt) # `x` is braodcasted to be a 3-D point
3.7416573867739413
d2.(x, pt) # Incorrect result, forgot the `.`
3-element Vector{Float64}:
2.0
1.0
3.0
How can this be prevented?
a, b = 4, 1
a < b ? b - a : a - b # Ternary operator: condition ? true : false
3
d2(x1, x2) = length(x1) == length(x2) ? sqrt(sum((x1 .- x2).^2)) : error("Inputs not same length.")
d2(x, pt)
Inputs not same length.
Stacktrace:
[1] error(s::String)
@ Base .\error.jl:35
[2] d2(x1::Int64, x2::Vector{Int64})
@ Main .\In[107]:1
[3] top-level scope
@ In[107]:2
d2.(x, pt)
3-element Vector{Float64}:
2.0
1.0
3.0
Question 1.2.2¶
Why is it usually better to represent a two-dimensional point as a 2-tuple, (x, y), than a 2-vector, [x, y]?
(a) Tuples are dynamically sized, which makes them more efficient than vectors for small datasets.
(b) Since points typically change frequently during processing, a tuple can adapt to these changes more efficiently than a mutable vector.
(c) Since points do not typically change, they can be immutable, allowing better memory usage since the storage location size for each point will not change.
(d) Tuples are mutable, allowing for easier modification of the point coordinates.