Tricky Stuff

This file highlights some tricky aspects of Julia (from the perspective of a Matlab user).


In [1]:
using Dates, LinearAlgebra

include("printmat.jl")   #function for prettier matrix printing


Out[1]:
printyellow (generic function with 1 method)

An Nx1 Array is not a Vector

and it sometimes matters.

Julia has both vectors and Nx1 arrays (the latter being a special case of NxM arrays). They can often be used interchangeably, but not always (see below for an example).

In particular, you typically use a vector when you want to pull out particular rows from a larger array.


In [2]:
v  = ones(Int,2)                     #a vector with two elements
v2 = ones(Int,2,1)                   #a 2x1 matrix (Array)

println("v and v2 look similar, but they have different sizes: ")
printmat(v)
printmat(v2)
println("size of v and v2: ",size(v)," ",size(v2))

x = [11 12;21 22;31 32]
println("\nx: ")
printmat(x)

println("x[v,:] is")
printmat(x[v,:])                    #instead, x[v2,:] gives an 2x1x2 array


v and v2 look similar, but they have different sizes: 
         1
         1

         1
         1

size of v and v2: (2,) (2, 1)

x: 
        11        12
        21        22
        31        32

x[v,:] is
        11        12
        11        12

Array .+ scalar Requires the dot (.)


In [3]:
y = [1;2] .+ 1              #do not forget the dot
printmat(y)


         2
         3

Creating Variables in a Loop


In [4]:
for i = 1:5
    global Tor         #without this, Tor is not seen outside the loop  
    Tor = cos(i)
end
println(Tor)


Oden = Inf              
for i = 1:5
    #global Oden        #only needed in REPL/scripts
    Oden = cos(i)       #will overwrite an existing value 
end
println("Oden: ",Oden)


0.28366218546322625
Oden: 0.28366218546322625

Threads.@threads and Variable Scope

Code like this

v = 1:2
Threads.@threads for i = 1:N
    v = something 
    x = SomeFunction(v)
end

can create unexpected results since the threads are sharing v. This is solved by declaring v inside the loop to be local.


In [5]:
function f2(N)
  v = falses(N+1)
  x = zeros(Int,N,N)
  Threads.@threads for i = 1:N
    #local v                   #comment out to see the problem    
    v    = falses(N)
    v[i] = true
    x[v,i] .= i               
  end
  return x
end

println("this should always be zero. Run a few times.")
M = 100
dev = zeros(M)
for i = 1:M
  dev[i] = maximum(abs,f2(i) - diagm(1:i))
end
println(dev)


this should always be zero. Run a few times.
[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, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 24.0, 0.0, 0.0, 0.0, 0.0, 18.0, 0.0, 0.0, 0.0, 0.0, 11.0, 0.0, 27.0, 0.0, 16.0, 2.0, 0.0, 0.0, 0.0, 2.0, 24.0, 29.0, 0.0, 19.0, 38.0, 0.0, 0.0, 0.0, 28.0, 32.0, 28.0, 30.0, 0.0, 47.0, 0.0, 0.0, 17.0, 0.0, 43.0, 40.0, 0.0, 48.0, 0.0, 2.0, 0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 40.0, 45.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 59.0, 47.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 28.0, 16.0, 0.0, 0.0, 0.0, 63.0, 0.0, 92.0]

A Heterogeneous Array

To create a 'cell array' (a heterogeneous Array), use [x1,x2,...]

Alternatively, you can preallocate as in B = Array{Any}(undef,3) and then fill by, for instance, B[1] = [11 12]


In [6]:
A = [[11 12;21 22],"A nice dog",27]

println("\nThe array A: ")
foreach(i->printmat(A[i]),1:length(A))     #print each element of A

B = Array{Any}(undef,3)
B[1] = [11 12]
B[2] = "A bad cat"
B[3] = pi

println("\nThe array B: ")
foreach(i->printmat(A[i]),1:length(A))


The array A: 
        11        12
        21        22

A nice dog

        27


The array B: 
        11        12
        21        22

A nice dog

        27

An Array of Arrays

can be initialized by comprehension (see below). (Do not use fill. See "A Reshaped Array" for why.)


In [7]:
x = [zeros(2,2) for i=1:2]        #a vector of two matrices
x[1][1,1] = -99

println("x[1]")
printmat(x[1])

println("x[2]")
printmat(x[2])


x[1]
   -99.000     0.000
     0.000     0.000

x[2]
     0.000     0.000
     0.000     0.000

Arrays are Different...

Vectors and matrices (arrays) can take lots of memory space, so Julia is designed to avoid unnecessary copies of arrays.

Issue 1. B = A Creates Two Names of the Same Array

If A is an array, then

B = A

creates two names of the same matrix. If you later change A, then B is changed automatically. (Similarly, if you change B, then A is changed automatically.)


In [8]:
A = [1,2]
B = A                                 #A and B are the same
C = A .+ 0                            #A and C are not the same
println("old A,B,C (each is a column): ")
printmat([A B C])

A[2] = -999
println("after changing element A[2] to -999, A,B,C are:")
printmat([A B C])

printblue("\nNotice that B changed, but C did not")


old A,B,C (each is a column): 
         1         1         1
         2         2         2

after changing element A[2] to -999, A,B,C are:
         1         1         1
      -999      -999         2


Notice that B changed, but C did not

Issue 2. A Reshaped Array still Refers to the Original Array

If you create a reshaped array by either

B = reshape(A,n,m)
C = vec(A)
D = A'
E = fill(A,2)

then A, B, C, D and E contain the same values. Changing one changes the others automatically.


In [9]:
A = [1 2]
println("original A: ")
printmat(A)

B = reshape(A,2,1)
C = vec(A)
D = A'
E = fill(A,2)

println("old B, C and D (each is a column): ")
printmat([B C D])

A[2] = -999
println("B, C and D after changing element A[2] to -999")
printmat([B C D])
println("E[1] and E[2] after changing element A[2] to -999")
printmat(E[1])
printmat(E[2])

printblue("\nNotice that B, C, D and E also changed")


original A: 
         1         2

old B, C and D (each is a column): 
         1         1         1
         2         2         2

B, C and D after changing element A[2] to -999
         1         1         1
      -999      -999      -999

E[1] and E[2] after changing element A[2] to -999
         1      -999

         1      -999


Notice that B, C, D and E also changed

Issue 3. Changing an Array Inside a Function Can Have Effects Outside the Function

When you use an array as a function argument, then that is passed as a reference to the function.

This means that if you change some elements of the array (A[1] = A[1]/2, say) inside the function, then it will also affect the array outside the function (even if they have different names).

In contrast, if you change the entire array (A/2, say) inside the function, then that does not affect the array outside the function.

This applies to arrays, but not to scalars or strings.

If you really need an independent copy of an array, create it by

B = copy(A)


In [10]:
function f1(A)
    A[1] = A[1]/2          #changes ELEMENTS of A, affects outside value
  return A
end
function f2(A)
    A = A/2                #changes all of A, does not affect outside value
  return A
end

x  = [1.0 2.0]
printlnPs("original x: ",x)

y1 = f1(x)
printlnPs("x (outside function) after calling f1(x): ",x)

x  = [1.0 2.0]
printlnPs("\noriginal x: ",x)

y2 = f2(x)
printlnPs("x (outside function) after calling f2(x): ",x)

printblue("\nNotice that f1() changed x also outside the function, but f2() did not")


original x:      1.000     2.000
x (outside function) after calling f1(x):      0.500     2.000

original x:      1.000     2.000
x (outside function) after calling f2(x):      1.000     2.000

Notice that f1() changed x also outside the function, but f2() did not

Issue 4. Arrays in Heterogeneous Arrays Still Refer to the Underlying Arrays

An array a inside another array Ais really just a referece to the existing a. Changing elements of a will change A.


In [11]:
a = [11 12;21 22]
A = [a,"A nice dog",27]               #a heterogeneous array
println("A:")
foreach(i->printmat(A[i]),1:length(A))

printblue("\nChange a[1,1] to -999 and notice that also A changes")
a[1,1] = -999
println("A:")
foreach(i->printmat(A[i]),1:length(A))

B = pushfirst!(A,a)
printblue("\nChange a[1,1] to 123 and notice that also A changes")
a[1,1] = 123
foreach(i->printmat(A[i]),1:length(A))


A:
        11        12
        21        22

A nice dog

        27


Change a[1,1] to -999 and notice that also A changes
A:
      -999        12
        21        22

A nice dog

        27


Change a[1,1] to 123 and notice that also A changes
       123        12
        21        22

       123        12
        21        22

A nice dog

        27


In [ ]: