August 15, 2024
Julia

# Arrays in Julia

In general, unlike many other technical computing languages, Julia does not expect programs to be written in a vectorized style for performance. Julia’s compiler uses type inference and generates optimized code for scalar array indexing, allowing programs to be written in a style that is convenient and readable, without sacrificing performance, and using less memory at times.[1]

The above quote, taken directly from Julia documentation is the feature that got me into Julia in the first place. It might seem trivial at first, but this is the same level of paradigm-shift as PyTorch caused after Tensorflow. When the compiler isn’t responsible for vectorization, everyone else is! Let’s see how many people that is. The user must keep track of the best-practices, which must be maintained by the community. Moreover, packages need to be written to allow idiomatic vectorization. All this takes a lot of time and effort by way too many people. When the compiler is responsible instead, vectorization is a concern only for core developers.

### Definition

Manually defining an array in Julia takes much less effort and keystrokes than most other languages.

Defining vectors is particularly pleasant, you can specify ranges (or elements) separated by a semicolon to concatenate all of them into a column vector:

``````julia> [1:3;4:6]
6-element Array{Int64,1}:
1
2
3
4
5
6``````

If instead of the semicolon, you use spaces, the concatenation is horizontal:

``````julia> [1:3 4:6]
3×2 Array{Int64,2}:
1  4
2  5
3  6``````

You can then combine the two types of concatenation to define matrices:

``````julia> A=[[1,2,3] [4,5,6] [7,8,9]]
3×3 Array{Int64,2}:
1  4  7
2  5  8
3  6  9

julia> A=[[1 2 3];[4 5 6];[7 8 9]]
3×3 Array{Int64,2}:
1  2  3
4  5  6
7  8  9``````

You can force the ‘type’ of an array by prefixing with it:

``````julia> Real[[1 2 3];[4 5 6];[7 8 9]]
3×3 Array{Real,2}:
1  2  3
4  5  6
7  8  9``````

As must be familiar to you, arrays have many helper methods:

``````julia> eltype(A) #Element type of A
Int64

julia> ndims(A) #Number of dimensions of A
2

julia> size(A) #Shape of A
(3, 3)``````

### Comprehension

Syntax for comprehension is as natural as could be, let’s see how you’d populate a matrix with the values of a polynomial in two variables at various points of the Argand plane:

``````julia> poly=[42x^3+3y^2+9x*y+1 for x=-3:3,y=3:9] #That's it! One line only!!
7×7 Array{Int64,2}:
-1187  -1193  -1193  -1187  -1175  -1157  -1133
-362   -359   -350   -335   -314   -287   -254
-41    -29    -11     13     43     79    121
28     49     76    109    148    193    244
97    127    163    205    253    307    367
418    457    502    553    610    673    742
1243   1291   1345   1405   1471   1543   1621``````

In general, the syntax is `[ expression for var1=rangeVar1,var2=rangeVar2,...]`. Now, we showcase `reshape` to define a higher dimensional matrix:

``````julia> A=reshape(1:64,(4,4,4))
4×4×4 reshape(::UnitRange{Int64}, 4, 4, 4) with eltype Int64:
[:, :, 1] =
1  5   9  13
2  6  10  14
3  7  11  15
4  8  12  16

[:, :, 2] =
17  21  25  29
18  22  26  30
19  23  27  31
20  24  28  32

[:, :, 3] =
33  37  41  45
34  38  42  46
35  39  43  47
36  40  44  48

[:, :, 4] =
49  53  57  61
50  54  58  62
51  55  59  63
52  56  60  64``````

Then, you’d index the matrix as per usual:

``````julia> poly[3,5]
43

julia> A[3,4,4]
63``````

You can also use this indexing to change a matrix:

``````julia> poly[end,end]=345678909876543 #change the bottom-right value
345678909876543

julia> poly
7×7 Array{Int64,2}:
-1187  -1193  -1193  -1187  -1175  -1157            -1133
-362   -359   -350   -335   -314   -287             -254
-41    -29    -11     13     43     79              121
28     49     76    109    148    193              244
97    127    163    205    253    307              367
418    457    502    553    610    673              742
1243   1291   1345   1405   1471   1543  345678909876543``````

You can also use ranges as indices:

``````julia> poly[1:2,:]
2×7 Array{Int64,2}:
-1187  -1193  -1193  -1187  -1175  -1157  -1133
-362   -359   -350   -335   -314   -287   -254``````

Remember that in Julia, ranges are inclusive, and indices start at 1, always.

Now, broadcasting, in Julia can be done explicitly:

``````julia> poly[1:2,:]=broadcast(+,1000000,poly[1:2,:])
2×7 Array{Int64,2}:
998813  998807  998807  998813  998825  998843  998867
999638  999641  999650  999665  999686  999713  999746

julia> poly
7×7 Array{Int64,2}:
998813  998807  998807  998813  998825  998843           998867
999638  999641  999650  999665  999686  999713           999746
-41     -29     -11      13      43      79              121
28      49      76     109     148     193              244
97     127     163     205     253     307              367
418     457     502     553     610     673              742
1243    1291    1345    1405    1471    1543  345678909876543``````

Or it can be done implicitly:

``````julia> poly[3:4,:] .+ -10000 #This doesn't change the matrix poly
2×7 Array{Int64,2}:
-10041  -10029  -10011  -9987  -9957  -9921  -9879
-9972   -9951   -9924  -9891  -9852  -9807  -9756``````

As homework, I suggest you play with the `.*` operator on matrices with at least 4 dimensions. In the next article, we’ll be looking at Tuples in Julia.

1. Julia Docs