Julia Fundamentals

18 Sep 2021

Julia is an advanced programming language which claims to have the performance of “C” and the ease of “Python”. This guide summarizes my impressions of Julia.

Variables

The programming is loosely typed, meaning that variables vary in the type of information they store, and therefore are late interpreted. This is powerful but also less efficient. As a result, it is recommended to maintain efficient Julia code, that when a variable is defined and declared, that the programmer manually attempt to ensure that a variable’s data type not change.

a=1
b="some string"
c=3.1415

Note: Avoid the following

a=1
b="asda"
a=b

Data Types

typeof(true)          # Bool
typeof(1)             # Int64
typeof("some string") # String
typeof(3.1415)        # Float64
typeof(3//2)          # Rational{Int64}
typeof(Complex(4,2))  # Complex{Int64}

Comparison

a=1
b=2
a==1 # true  - Equality
a==a # true
a==b # false
a!=b # true  - Inequality
a<b  # true  - Less than
a>b  # false - Greater than
a<=b # true  - Less than or equal
a>=b # false - Greater than or equal

Type Conversion

convert(Float64, 4)    # Ok. 4.0
convert(Int64, 3.1415) # ERROR: InexactError: Int64(3.1415)
string("a",1,true)     # "a1true"

Numerical Variables

a=3
b=2

addition=a+b        # 5
subtraction=a-b     # 1
division=a/b        # 1.5
multiplication=a*b  # 6
power=a^b           # 9
modulus=a%b         # 1
rational=a//b       # 3//2 I.e. stored as numerator and denominator for precision

String Variables

a="some string"

String Comparison

"4" > "5"            # false
"exact" == "exact"   # true
"exact" == "inexact" # false

String Interpolation

value = 3.1415          # 3.1415
"$value"                # "3.1415"
"1 + 2 = $(1 + 2)"      # "1 + 2 = 3"

String functions

# Case
uppercase("abc DEF gHI JkL") # "ABC DEF GHI JKL"
lowercase("abc DEF gHI JkL") # "abc def ghi jkl"
titlecase("abc DEF gHI JkL") # "Abc Def Ghi Jkl"
# Search
occursin("world", "hello world") # true
occursin("earth", "hello world") # false

findfirst("an","banana") # 2:3
findlast("an","banana")  # 4:5
findfirst('a',"banana")  # 2
findlast('a',"banana")   # 6

repeat

repeat("rubarb",3)       # "rubarbrubarbrubarb"
"rubarb"^3               # "rubarbrubarbrubarb"

Arrays

persons = ["Alice", "Bob", "Carla", "Daniel"]

Array Slicing

a = [1,2,3,4,5,6]
b = a[2:5]                      # 2,3,4,5

Array Memory Access

The keyword copy is used to clone an array, safetly protecting an array from modification when used as an argument

# Memory Access
a=[1,2,3]
b=a       # Unsafe array assignment
b[2] = 42
print(a)                # [1, 42, 3]

a=[1,2,3]
b=copy(a) # Safe clone of array
b[2] = 42
print(a)                # [1, 2, 3]

Array Comprehension

[i for i in 1:10]                # Standard comprehension
[i+j for i in 1:10 for j in 1:5] # Nested comprehension

Multi Dimensional Arrays

# Operators with special names
[1 2 3]                 #hcat   - ie. a Row of data
[1;2;3]                 #vcat   - ie. a Column of data
A = [1 2;3 4;5 6]       #hvcat    - ie. A Matrix,  an array of rows and columns
A'                      #adjoint - the Adjoint (inverted) matrix of A 
A[1]                    # getindex
A[1] = 7                # setindex
A.n                     # getproperty

# Multi-Dimension Arrays
table = zeros(2,3,4)
for k in 1:4
    for j in 1:3
        for i in 1:2
            table[i,j,k] = i*j*k
        end
    end
end
table

Matrix reshaping

mat1 = reshape([i for i in 1:16],4,4)
mat2 = mat1[2:3, 2:3] # Submatrix
mat1' # Adjunct

mat3 = reshape(mat1, 2,8)

Vectors

Vectors are just another name used for Arrays, typically where they have mixed data types

c=[1,2,3.6,"Sam"]
c[1]                    # 1
c[3:4]                  # 3.6, "Sam"
c[3:-1]                 # Any

Dictionaries

Dict("A"=>1,"B"=>2,"C"=>3)      # Dict{String, Int64} with 3 entries:
                                #  "B" => 2
                                #  "A" => 1
                                #  "C" => 3
Dict("A"=>1,"B"=>2,"C"=>3)["A"] # 1 - Fetch
Dict("A"=>1,"B"=>2,"C"=>3)["B"] # 2 - Fetch
Dict("A"=>1,"B"=>2,"C"=>3)["D"] # ERROR: KeyError: key "D" not found

Add key value pair

a = Dict("A"=>1,"B"=>2,"C"=>3)
a["D"] = 5                      # Adds the values 5 with key "D"
a                               # Dict{String, Int64} with 4 entries:
                                #   "B" => 2
                                #   "A" => 1
                                #   "C" => 3
                                #   "D" => 5

Nesting Dictionaries

person1 = Dict("Name" => "Aurelio", "Phone" => 123456789, "Shoe-size" => 40)
person2 = Dict("Name" => "Elena", "Phone" => 123456789, "Shoe-size" => 36)
addressBook = Dict("Aurelio" => person1, "Elena" => person2)
addressBook["Elena"]["Phone"]

Tuples

Tuples a flexible type, often used to return multiple values from a function.

d = (1,2,3)             # (1, 2, 3)
e = 1, 2, 3             # (1, 2, 3)
d[1]                    # 1

Tuple Packing

Packs the values 1, 2 and 3 into a tuple

a=(1,2,3)

Tuple Unpacking

Unpacks the values 1 into a, 2 into b, 3 into c

a, b, c = (1,2,3)
a                       # 1
b                       # 2
c                       # 3

Auto unpacking

function auto_unpack(a,b,c)
    print("$a,$b,$c")
end


# Functions unpack automatically by appending ...
auto_unpack(tuple1...)

# For example
auto_unpack(("a","b","c")...)   # a,b,c

Named Tuples

# Named tuples
a = (smith = 4, jones=5)
a[:jones]                   # 5

Regex

re = r"^\s*(?:#|$)"     # Declaration
typeof(re)              # Regex

Regex Searching

occursin returns a Bool value, if pattern is found or not

occursin(r"^\s*(?:#|$)", "not a comment")   # false
occursin(r"^\s*(?:#|$)", "# a comment")     # true

match returns a RegexMatch object, if a match is found

match(r"^\s*(?:#|$)", "not a comment")      # <no value>
match(r"^\s*(?:#|$)", "# a comment")        # RegexMatch("#")

Using a match

m = match(r"^\s*(?:#|$)", line)
if m === nothing
    println("not a comment")
else
    println("blank or comment")
end

Control Flow

If Else

if a > b
    print('yes')
else
    print('no')
end

For Loop

for i in 1:10
    print(i)
end

Use of break keyword, is similar to C#, terminating the loop

for i in 1:100
    if i>10
        break
    else
   	    println(i^2)
    end
end

Use of continue keyword is similar to C#, skipping to next loop iteration

for i in 1:30
    if i % 3 == 0
        continue
    else
        println(i)
    end
end

While Loop

i=0
while(i<30)
    println(i)
    i += 1
end

Functions

Standard Functions

If keyword return is not provided, the last calculation is assumed to be returned.

function simple(a,b)
    a+b
end

simple(1,2)         # 3

Inline Functions

g(x,y) = x+ y
g(1,2)              # 3

Function as a Variable

g(x,y) = x+ y
g(1,2)              # 3

h = g 
h(4,5)              # 9

Return Nothing

Returning keyword nothing ensures that the function returns no value

function n(x,y)
    print("Sum = $(x + y)")
    return nothing
end

n(1,2)              # Sum = 3

Operators are functions

1 + 2 + 3           # 6
+(1, 2, 3)          # 6

p = +               # + (generic function with 190 methods)
p(1,2,3)            # 6

Broadcasting

Array Broadcasting

a = [1,2,3]         # is a column vector
b = [4,5,6]         # is a column vector
a * b               # ERROR: MethodError: no method matching *(::Vector{Int64}, ::Vector{Int64})
c = [4 5 6]         # is a row vector
a * c               # 3-element Vector{Int64}:
                    #  3
                    #  6
                    #  9
c * a               # 3-element Vector{Int64}:
                    #  3
                    #  6
                    #  9

d = reshape([1,2,3,4,5,6,7,8,9],3,3)
d * a               # 3-element Vector{Int64}:
                    #  30
                    #  36
                    #  42
c * d               # 3×3 Matrix{Int64}:
                    #  3  12  21
                    #  6  15  24
                    #  9  18  27
a * d               # Fails

Special matrix operator .* ensures that rows and columns are correctly transposed before multiplication

a .* c
a * c
c .* a
a .* d              # Does not fail due to broadcasting

Function Broadcasting

# Broadcasts the single value function Sin over the array
sin.([1,2,3])       # 3-element Vector{Float64}:
                    #  0.8414709848078965
                    #  0.9092974268256817
                    #  0.1411200080598672

Any method which takes a single argument can be used to broadcast over an array

function my_function(x)
    collect(1:x)
end

my_function(6)          # 6-element Vector{Int64}:
                        #  1
                        #  2
                        #  3
                        #  4
                        #  5
                        #  6

my_function(6)'         # 1  2  3  4  5  6

# Broadcast function
my_function.([6 7 8])   # [1, 2, 3, 4, 5, 6]  [1, 2, 3, 4, 5, 6, 7]  [1, 2, 3, 4, 5, 6, 7, 8]

# Adjunct of broadcast function
my_function.([6 7 8])'  # 3×1 adjoint(::Matrix{Vector{Int64}}) with eltype LinearAlgebra.Adjoint{Int64, Vector{Int64}}:
                        #  [1 2 … 5 6]
                        #  [1 2 … 6 7]
                        #  [1 2 … 7 8]

File IO

Reading a File

open("war_and_peace.txt") do f
 
    # line_number
    line = 0  
   
    # read till end of file
    while ! eof(f) 
   
       # read a new / next line for every iteration          
       s = readline(f)         
       line += 1
       println("$line . $s")
    end
   
  end

Packages

Julia uses Packages to encapsulate a collection of capabilities as a deployable and reusable asset.

The following example uses the Plots package to draw a chart, of 10 random numbers.

Before the package can be used however, the local Package collection needs to be updated to ensure that the Plots package can be accessed. The Pkg package is a standard package which provides runtime access and management of packages on a system.

using Pkg

Pkg.add("Plots")

using Plots

x = 1:10; y = rand(10); # These are the plotting data 
plot(x,y, label="my label")

Unit Testing

An example of very basic unit testing

using Test

@test true  # <silently passes>
@test false # Test Failed at c:\...\unitTesting.jl:4

Here’s an example of a test which fails (intentionally)

using Test

function sum(a,b)
   a - b # obvious bug here
end

@test sum(0,0) == 0 # Passes
@test sum(1,0) == 1 # Passes
@test sum(0,1) == 1 # Fails here
@test sum(1,1) == 2 # Not tested

# Test Failed at c:\...\unitTesting.jl:9
#   Expression: sum(0, 1) == 1
#    Evaluated: -1 == 1       
# ERROR: LoadError: There was an error during testing
# in expression starting at c:\...\unitTesting.jl:9

Testing with a test set, results in all internal tests being run, regardless of previous results

using Test

function sum(a,b)
    @debug "Testing sum($a,$b)"
    a - b # obvious bug here
end

@testset "sum" begin
    @test sum(0,0) == 0 # Passes
    @test sum(1,0) == 1 # Passes
    @test sum(0,1) == 1 # Fails here
    @test sum(1,1) == 2 # Not tested

    @test_skip sum(1,2) == 5 # Not executed
    @test_broken sum(1,3) == 5 # Marked as a pass, but known to fail

    @test_logs (:info,"Testing sum(0,0)") sum(0,0) == 0 # Passes, checking the logging info messages
end

# [ Info: Testing sum(0,0)
# [ Info: Testing sum(1,0)
# [ Info: Testing sum(0,1)
# sum: Test Failed at c:\...\unitTesting.jl:11
#   Expression: sum(0, 1) == 1
#    Evaluated: -1 == 1       
# Stacktrace:
#  [1] macro expansion
#    @ c:\...\unitTesting.jl:11 [inlined]
#  [2] macro expansion
#    @ C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\Test\src\Test.jl:1151 [inlined]
#  [3] top-level scope
#    @ c:\...\unitTesting.jl:9
# [ Info: Testing sum(1,1)
# sum: Test Failed at c:\...\unitTesting.jl:12
#   Expression: sum(1, 1) == 2
#    Evaluated: 0 == 2
# Stacktrace:
#  [1] macro expansion
#    @ c:\...\unitTesting.jl:12 [inlined]
#  [2] macro expansion
#    @ C:\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.6\Test\src\Test.jl:1151 [inlined]
#  [3] top-level scope
#    @ c:\...\unitTesting.jl:9
# [ Info: Testing sum(1,3)
# Test Summary: | Pass  Fail  Broken  Total
# sum           |    3     2       2      7
# ERROR: LoadError: Some tests did not pass: 3 passed, 2 failed, 0 errored, 2 broken.
# in expression starting at c:\...\unitTesting.jl:8