Skip to main content

Chapter 6 Vectors and other Collections

Vectors are a fundamental data structure for nearly every computer language and it is a crucial for scientific computing as that it is an efficient way of handling large amount of the same datatype. Vectors are collections with a single dimension, and Julia has a Matrix type as well for 2 dimensional collections. These all fit into a general array type.
In short a vector is a collection of data of (typically) the same type under the same name. We have seen arrays before. For example, if we say
arr = [1,2,3]
then we get a vector of length 3. Note that when returned, Julia says 3-element Vector{Int64}: which means
  • it is length 3 (since it is a 3-element)
  • The internal type is Int64.
  • The Vector indicates it is 1-dimensional.
There are some basic functions that tell us some information:
  • length(arr) returns 3, the length.
  • eltype(arr) returns Int64, the type of the elements in the array.

Section 6.1 Constructing arrays

We can create arrays in many ways. As seen above, the line
arr=[1,2,3]
creates a vector (1-D array) of length 3. A 2D array can be made like:
arr2=[1 2 3; 4 5 6]
Note that each row of the array is separated by a semicolon and the individual elements in a row are separated by spaces. The result of this is
2×3 Matrix{Int64}:
1  2  3
4  5  6
and the size is 2 by 3 (2 rows and 3 columns). A Matrix is a 2 dimensional array. One can make an array of more than 2 dimensions.
The following are useful for creating arrays of 1 or more dimensions.
  • zeros(type,dims...) makes an array of all zeros with datatype type and given dimensions. For example,
    zeros(Int, 4, 6)
    
    returns an array filled with zeros (of integer type) with 4 rows and 6 columns.
  • ones(type,dims...) is similar to zeros except it is filled with 1s.
  • rand(dims...) makes a random array of floating points (uniformly distributed between 0 and 1).
  • collect(range) takes a Range object (the pair or triples of numbers separated by colons) and creates an 1D array with the numbers from the range. For example, collect(1:10) returns
    10-element Vector{Int64}:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

Section 6.2 Comprehensions

If the elements of an array form a functional pattern, we can use what is called a comprehension to construct it in a compact manner. For example, a 1D array [1, -1, 1, -1, 1, -1, 1, -1] can be made:
 [(-1)^n for n=0:7] 
and the following:
 [m+n for n=1:8,m=1:8] 
makes an 8 by 8 matrix where each element is the sum of the row and column number or
8×8 Matrix{Int64}:
2   3   4   5   6   7   8   9
3   4   5   6   7   8   9  10
4   5   6   7   8   9  10  11
5   6   7   8   9  10  11  12
6   7   8   9  10  11  12  13
7   8   9  10  11  12  13  14
8   9  10  11  12  13  14  15
9  10  11  12  13  14  15  16

Check Your Understanding 6.1.

Using the techniques in this section, create the following arrays in Julia:
  1. \begin{equation*} \begin{bmatrix} 3 \\ 6 \\ 9 \\ 12 \\ 15 \\ 18 \\ 21 \\ 24 \end{bmatrix} \end{equation*}
  2. \begin{equation*} \begin{bmatrix} 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 \\ \end{bmatrix} \end{equation*}
  3. \begin{equation*} \begin{bmatrix} 1 & 1 \\ 1 & 1 \\ 1 & 1 \\ 1 & 1 \end{bmatrix} \end{equation*}
  4. \begin{equation*} \begin{bmatrix} 1 & -3 & 4 & 5 \\ 3 & 2 & 11 & -13 \\ 17 & 0 & 4 & 2 \end{bmatrix} \end{equation*}
  5. \begin{equation*} \begin{bmatrix} 4 & 3 & 2 & 1 & 0 \\ 3 & 2 & 1 & 0 & 1 \\ 2 & 1 & 0 & 1 & 2 \\ 1 & 0 & 1 & 2 & 3 \\ 0 & 1 & 2 & 3 & 4 \end{bmatrix} \end{equation*}
Hint.
Hint: there is no pattern for the array in #4, so just enter the numbers.

Section 6.3 Accessing elements of an array

Let x=collect(1:2:13) which generates:
7-element Vector{Int64}:
 1
 3
 5
 7
 9
11
13
To access the 2nd element, type x[2]. To get a vector consisting of the elements 3, 4 and 5, type
 x[3:5] 
or if we want the last 3 elements, we can use:
 x[end-2:end] 
where end is an alias for the last element, so this returns an array of the last 3 elements of x.
Let
 A=[i+j for i=1:4,j=1:5] 
to access the 1st row, 3rd column, type A[1,3]. If you want the subarray of the first and 3rd and 5th columns and all rows type:
 A[:,1:2:5] 
returns the matrix
4×3 Matrix{Int64}:
2  4  6
3  5  7
4  6  8
5  7  9
where the : in the first slot means all rows and 1:2:5 are the columns 1, 3 and 5.
If we want the first 2 rows and then shuffle the 2,3 and 5th columns in the order 5,3,2 type
A[1:2,[5,3,2]]
returns
2×3 Matrix{Int64}:
6  4  3
7  5  4
There are a number of different ways to return subarrays. See the Julia matrices documentation for more information.

Section 6.4 Common Operations on Arrays

There are a number of operations on array. For each + and - adds and subtracts two arrays of the same size in an element by element manner. For example if
 A=[1 2 3; 4 5 6] 
and
 B=[1 3 5; 2 4 6] 
then A+B returns the array:
2×3 Matrix{Int64}:
2  5   8
6  9  12
and B-A returns
2×3 Matrix{Int64}:
 0   1   2
-2  -1  0

Section 6.5 Element by Element Operations (Broadcasting)

Many methods exist to simplify a vectorized method that returns the operation applied element by element to the matrix. To do this, most operations have a . variety and is also called broadcasting. For example, if we want multiply A and B in an element by element manner then
 A.*B 
returns
2×3 Matrix{Int64}:
  1   6  15
  8  20  36
Note that this is not matrix multiplication, which is A*B and we will discuss this in Chapter 40. Many other operations can be done in similar way. For example, to take the square root of every number in the matrix A, we can enter sqrt.(A) which returns:
2×3 Matrix{Float64}:
  1.0  1.41421  1.73205
  2.0  2.23607  2.44949

Check Your Understanding 6.2.

Broadcasting can be applied to other array-like structures like tuples, but also scalars and other collections. For example,
(1,2,3) .+ (4,5,6)
returns (5, 7, 9). And if we want to add 10 to every element of a matrix, we can use this syntax as well. [1, 2, 3, 4] .+ 10 returns [11, 12, 13, 14]. Note that having broadcasting simplifies a lot of operations with arrays. Without this, typically a for loop would be needed to be written, which is not difficult, but more more complicated than a single operation.
There is also a macro that can apply broadcasting. Let’s say that we are applying the function \(f(x) = x \sin(x) + e^{x^2}\) on an array x = collect(-3:3). To write this operation element by element: one must do x .* sin.(x)+exp.(x.^2) and also that isn’t too difficult, there is a bit of an easier way using the @. macro. If we add this in front of a non-broadcasting expression, it will apply all functions an operations in the right way. You should notice that @. x*sin(x) + exp(x^2) returns the same array.
We will use both forms of broadcasting in the rest of this book. Often, it broadcasting is relatively simple, we’ll use the non-macro version, but for more complicated ones, we’ll use the macro.

Section 6.6 Other Operations on Arrays

There are also other operations on arrays. For example, summing all elements in an array is just the sum functions. For example:
sum([1 2 3 4 5 6 7 8 9 10])
returns 55. There are also the min, max and prod functions, with the last one, the product of elements.
prod([1,3,5,7])
returns 105.

Section 6.7 Sorting Vectors and matrices

Julia can sort an array using the sort function. If A=[3,2,1,4,8,6,5], then
sort(A)
results in [1 2 3 4 5 6 8]. The sort function returns a new array that is the sorted version of the original. If you want a sorted version of A in place, use the sort! function instead.
Also, you may want to sort in a descending manner. If this is true use the keyword argument rev=true in the sort or sort! function. For example:
sort(A,rev=true)
results in [8 6 5 4 3 2 1].
If an array element type is a String, then it is sorted lexicographically (similar to alphabetical). If
B = ["This","is","a","string","array"]
sort(B)
which returns ["This", "a", "array", "is", "string"], where capitalized words come before lower case.
We can also specify the function that is applied before sorting is done. For example,
C = collect(1:9)
sort(C, by = x -> x % 3)
where C is the array from 1 to 9 and before sorting, the mod is taken. The by is a keyword argument that takes a function. in this case, we have used an anonymous function, which is a function without a name and is often used inside of other functions. We will explore these in more detail in Chapter 7. The result of the sort function is [3,6,9,1,4,7,2,5,8], where the mod of the first 3 are 0, the next three are 1 and the last three are 2.
Check out the Julia docs on sorting and sorting algorithms for more details on how Julia does sorting.

Section 6.8 Push and Pop; Array as a Stack

There is a computer science data structure called a stack which acts like a stack of things (paper, dishes, Pokémon cards) and there are two operations on it: 1) put something on top of the stack or 2) take something off the top of the stack. Many languages including Julia don’t have a separate data structure but use an array like one with two operations, push and pop. In Julia these have ! at the end to indicate that the operations change the array. If A=collect(1:5) then
push!(A,7)
returns the array [1 2 3 4 5 7]. And multiple values can be pushed.
push!(A,8,9,10,100)
results in the array [1 2 3 4 5 7 8 9 10 100]. To get an element off of the end of the array, we can use pop!. If A=collect(1:5), then
pop!(A)
returns 5 and now the array A is [1 2 3 4].

Section 6.9 Other Nice Array Functions

Julia has a ton of other nice functions that act on arrays.

Subsection 6.9.1 Append

If we want to concatenate two arrays, we should use the append! command. For example:
A=[1,2,3]
append!(A,[4,5,6])
and now the array stored in A is [1,2,3,4,5,6]

Subsection 6.9.2 Adding and Removing Elements in the Middle of an Array

The functions push! and pop! add and remove an element from the end of a Vector or 1D array. If we want to add or remove elements in the middle there is are the functions insert! and deleteat!. They both act on a position in the array and you can see how they work with the following examples. If A=collect(1:2:11), then
insert!(A,3,15)
results in the array A being [1 3 15 5 7 9 11] and then resetting the array with A=collect(1:2:11) then
deleteat!(A,2)
results in the array A being [1 5 7 9 11].

Subsection 6.9.3 It Splices, It Dices!! --- okay, it doesn’t dice

Although you can get a long ways with push!, pop!, insert! and deleteat!, the splice! function is a Swiss Army knife for arrays. It can take an array, pull elements out and put elements in. We will walk through the options:
Removing elements from an array
We saw using the deleteat! function above how to remove a single element, but if
 A=collect(1:2:13) 
then
 splice!(A, 3:4) 
removes and returns the 3rd and 4th elements which are [5, 7]. Also, the array A is the original array without these elements or [1, 3, 9, 11, 13]. If we let A=collect(1:2:11), then
 x = splice!(A,2) 
returns the element in the 2nd position vector or 3. The array A is now [1, 5, 7, 9, 11]. This is the same as deleteat!(A,2) we saw above.
Inserting elements from an array
We saw the function insert! above, which will insert a single element in a 1D array. The splice! function will insert multiple elements. For example, if we have
 A = collect(1:2:11) 
then to insert elements in the 3rd position (and shifting everything else to the right), we can enter
 B=splice!(A,3:2,[11,12,13,14]) 
then the function returns Int64[]. This is an empty array of type Int64. The result is empty because nothing was removed in the process. The array A is now [1, 3, 11, 12, 13, 14, 5, 7, 9, 11]. A few things to note about this. The second argument is a bit strange in that you insert 3:2 which is a range in which the last element is smaller than the first. This will let Julia know that you don’t want to remove any elements, unlike either a single number or a range with the first element smaller than the last.
Replacing elements from an array
We say splice! is the Swiss army knife, because it does even more!! (Now, how much would you pay?). This last section shows that we can both remove and insert elements into an array at the same time.
If A=collect(1:2:11), then
 x = splice!(A,3,4) 
returns 5 and replaces the 3rd position with the number 4. The result is [1, 3, 4, 7, 9, 11]. If A=collect(1:2:11), then replacing the 3rd element with [-1,-2,-3] by
splice!(A,3,[-1,-2,-3])
returns 5 (the element in position 3) and now the array A is [1, 3, -1, -2, -3, 7, 9, 11].

Subsection 6.9.4 Filtering Arrays

A very handle array method, is filter which takes an array and returns only the elements that satisfy some condition.
If A=collect(1:20), then
 filter(n -> n%2==0, A) 
returns all elements that satisfy that the mod 2 is 0 (or even numbers) or [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]. The first argument is a function and we use an anonymous function for this.
In general, the filter command has the form:
 filter(cond,array) 
where the result is the array consisting of elements in array in which the function cond is true.
In addition, if you want to filter on an array with the results in the place of the original array use filter! instead. For small arrays, one can use either, however, for a large array, to use filter would require a new array be made and we will see in Chapter 9 that creating arrays is a slow down and often a bottleneck for code. In such a case filter! would be more efficient.

Subsection 6.9.5 Removing Duplicate Elements

Another handy function is the unique function that takes an array and returns only the unique elements.
If A=[1,2,3,2,3,4,3,4,5,4,3,2,1] then
 unique(A) 
returns [1,2,3,4,5]. And if you want to update the original array, use the unique! function instead.

Section 6.10 Joining Arrays and Splitting Strings

There are a couple of related functions that involve arrays, that of joining an array to get a string and splitting a string to get an array. We show a few examples here.
Let’s say that we have the vector formed by x=collect(1:6). If we want to create a string in which all of the elements are joined, consider
join(x, ", ")
which outputs "1, 2, 3, 4, 5, 6", which is the 6 elements of the vector stringified and concatenated separated by ", ". The join function has another option to separate the last one differently. For example if
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
then join(days, ", ", " and ") returns
"Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday"
notice that the first 6 elements are separated by ", " and the last one with " and ".
Additionally, Julia has the ability to split a string into an array of substrings. For example if
str = "1; 2; 3; 4; 5; 6"
then split(str, "; ") returns the array
6-element Vector{SubString{String}}:
	"1"
	"2"
	"3"
	"4"
	"5"
	"6"
and notice that this is an array elements of type Substring{String}. This type is used as an efficiency to show where in the original string the substring occurs. If it is desirable to extract the integer representations of the strings, then use the parse function explained in Section 3.7. The integers can be extracted with
map(s-> parse(Int,s), split(str, "; "))
where the map function from Chapter 7 is used. This returns the integer array [1, 2, 3, 4, 5, 6]. See the Julia documentation on split for more information and options.

Section 6.11 Concatenating Arrays

It is also desirable to join two or more arrays. We saw we can append one onto another with the append! function, but recall that this method updates the first array, often an underisable result. We can combine the two arrays
x = [1, 2, 3, 4]
y = [5, 6, 7, 8]
in a way to make a vector of length 8 with vcat(x,y) which returns the vector [1, 2, 3, 4, 5, 6, 7, 8]. Note: vcat is short for vertical concatenation and when applied to vectors (which are understood to be column vectors), one gets a new vector.
Alternatively, if we horizontally concatenate these vectors, with hcat(x,y), we get the result:
4×2 Matrix{Int64}:
  1  5
  2  6
  3  7
  4  8
which is the 4 by 2 matrix. That is it creates a matrix with the given vectors.

Check Your Understanding 6.3.

The methods vcat and hcat also can work with other arrays (like matrices). If we define:
A = [i*j for i=1:4,j=1:4]
B = [(-1)^(i+j) for i=1:4,j=1:2]
(a)
Find hcat(A,B) and vcat(A,B) and determine if the results are as expected.
(c)
Try vcat(A,transpose(x)), where transpose(x) performs a transpose (think Linear Algebra) of the vector x which makes it a row vector.

Section 6.12 Creating patterned arrays with repeat

Although array comprehensions are incredibly powerful, often if you want to repeat elements of an array to generate another array, the task can be tricky.
For example, to create a array [1, 2, 1, 2, 1, 2, 1, 2, 1, 2] can be done with a comprehension like:
[1+(i+1)%2 for i=1:8]
and we can use [1+ (i-1) ÷ 4 for i=1:8] to produce the array [1, 1, 1, 1, 2, 2, 2, 2] and also, [1+i % 4 for i=0:7] to generate [1, 2, 3, 4, 1, 2, 3, 4].
However, if other numbers are repeated it may not be easy to generate these. Instead, we will use the repeat function which in short repeats the numbers in an array some number of times. First, if we execute repeat(1:2,4), then the results are the same as the first array above or [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]. To generate the second one above, we can use the inner option as in repeat(1:2, inner = 4). The 3rd example above can be generated with repeat(1:4,2).
The method repeat can also be used to generate an array. repeat([1, 2, 3], 2, 3) produces:
6×3 Matrix{Int64}:
	1  1  1
	2  2  2
	3  3  3
	1  1  1
	2  2  2
	3  3  3
which repeats the vector 2 times in the vertical direction and 3 times in the horizontal one.

Section 6.13 Arrays as a Collection

An array is one example of a Collection in Julia. We will cover these in more details in Chapter 22 and it is helpful to have knowledge of the more abstract idea when handling arrays.

Section 6.14 Tuples

Another type that allows one to combine two or more elements of data is called a tuple. For example, if we want two integers that are combined, we can make:
(1,2)
For example, this could be a way to store a geometric point in the plane. Determining the data type with typeof((1,2)) returns Tuple{Int64, Int64}, which is another example of a composite type. The type is a Tuple but there are Int64 types for the components of the tuple.
Tuples can consists of different types for different parts. For example, t = (5,7.8, "Hello") is a valid tuple and its type is Tuple{Int64, Float64, String}.
You can access the elements of the tuple using bracket notation. Using the value of t above, we can get the first element of t with t[1]. Note, this is similar to array notation, which we will see in Chapter 6. Note: the first element of things like tuples and array is 1, not 0.

Subsection 6.14.1 Named Tuples

Sometimes it’s easier to associated the individual elements of a tuple with a name instead of an index. We can generate a named tuple as
pt=(x=1,y=3.2,z=9)
which would be like a point in three dimensions. We can access the elements using dot notation, such as
pt.x
returns 1.
Named tuples can be helpful to build complex datatypes, where there are multiple fields are associated with the same piece of data. Another example might be if you have data associated with a person, such as
p = (first_name = "Homer", last_name = "Simpson", age=45) 
where there are a mix of strings and integers in the same tuple. This may be all that you need for an example like this, however, we will see more complex data types in Chapter 12, which creates a new data type or struct. Such a type will allow more flexibility. For example if we define a point using a tuple, it is difficult to do operations on the point.

Subsection 6.14.2 Tuples are Immutable

However, if we try to change one of the elements of a tuple, then an error will occur.
p.age=46
results in
setfield!: immutable struct of type NamedTuple cannot be changed
This is because tuples are immutable, meaning once created, any part of the tuple cannot be changed.