pic
Personal
Website

5d. Initializing and Creating Vectors

PhD in Economics

Introduction

We continue introducing preliminary concepts for mutations. The previous section distinguished between = for assignments and mutations. Now, we'll deal with approaches to creating vectors.

Our presentation starts by outlining the process of initializing vectors, where memory is reserved without assigning initial values. We'll then discuss how to create vectors with predefined values, such as zeros or ones. We conclude by showing how to concatenate multiple vectors into a new one.

Initializing Vectors

Creating an array involves two steps: allocating memory space and assigning initial values to its elements. If, nonetheless, you intend to eventually populate the array with specific values, it's more efficient to only initialize the array. This involves reserving memory space, but without setting any initial values.

The approach relies on the so-called undef values, which represent arbitrary content held in memory at the moment of reserving space. This means that, while undef provides concrete numbers, these values are meaningless and vary every time you initialize an array.

There are two methods for initializing arrays with undef values. The first one requires specifying the type and length of the array, using the syntax Vector{<elements' type>}(undef, <length>). The second approach is based on the function similar(x), which creates a vector with the same type and dimensions as x.

x_length = 3

x = Vector{Int64}(undef, x_length)  # `x` can hold `Int64` values, and is initialized with 3 undefined elements
Output in REPL
julia>
x
3-element Vector{Int64}:
 140724480121488
   2497084710592
   2497285012816
y = [3,4,5]

x = similar(y)                      # `x` has the same type as `y`, which is Vector{Int64}(undef, 3)
Output in REPL
julia>
x
3-element Vector{Int64}:
 2497063587648
 2497063587664
 2497355825296

The example starkly shows that undef values don't follow any particular logic. Moreover, these values vary in each execution, as they capture any content held in RAM at the moment of allocation. In fact, a more descriptive way to call undef values would be uninitialized values.

Creating Vectors

In the following, we present several approaches to creating arrays with predefined values.

Vectors With Range

Sequences of values can be created by specifying ranges, which have been extensively applied on the website. Ranges can be materialized into vectors by the function collect(<range>).

A Vector Filled With a Sequence
some_range = 2:5

x          = collect(some_range)
Output in REPL
julia>
x
4-element Vector{Int64}:
 2
 3
 4
 5

The syntax of range is <start>: <steps>: <stop>, where <steps> implicitly dictates the number of elements that to be created. Alternatively, you could indicate the number of elements to store, letting <steps> be implicitly determined. This is implemented through the function range.

range creates a linearly spaced sequence, where the user specifies the number of elements to be created between two endpoints. Formally, it's equivalent to <start> : 1 / <number elements> : <end>. Its syntax is range(<start>, <end>, <number elements>), where you could alternatively set these values through keyword arguments. The following snippet demonstrates its use through a range from 0 to 1 that holds 5 elements.

x = range(0, 1, 5)
Output in REPL
julia>
x
0.0:0.25:1.0
x = range(start=0, stop=1, length=5)
Output in REPL
julia>
x
0.0:0.25:1.0
x = range(start=0, length=5, stop=1)    # any order for keyword arguments
Output in REPL
julia>
x
0.0:0.25:1.0

Vectors With Specific Values

We can also create vectors with predefined values. For vectors filled with zeros and ones, Julia provides the functions zeros and ones. By default, these functions define Float64 elements, although you also have the option of defining their type. The following examples demonstrate their use.

length_vector = 3

x             = zeros(length_vector)
Output in REPL
julia>
x
3-element Vector{Float64}:
 0.0
 0.0
 0.0
length_vector = 3

x             = zeros(Int, length_vector)
Output in REPL
julia>
x
3-element Vector{Int64}:
 0
 0
 0
length_vector = 3

x             = ones(length_vector)
Output in REPL
julia>
x
3-element Vector{Float64}:
 1.0
 1.0
 1.0
length_vector = 3

x             = ones(Int, length_vector)
Output in REPL
julia>
x
3-element Vector{Int64}:
 1
 1
 1

For creating Boolean vectors, you can conveniently use the functions trues and falses. They're optimized for Boolean vectors, by creating a BitVector rather than Vector{Bool}.

length_vector = 3

x             = trues(length_vector)
Output in REPL
julia>
x
3-element BitVector:
 1
 1
 1
length_vector = 3

x             = falses(length_vector)
Output in REPL
julia>
x
3-element BitVector:
 0
 0
 0

Vectors Filled With a Repeated Object

To define vectors filled with elements different from zeros or ones, you can apply the fill function. This accepts any object as the element to be repeated.

length_vector    = 3
filling_object   = 1

x                = fill(filling_object, length_vector)
Output in REPL
julia>
x
3-element Vector{Int64}:
 1
 1
 1
length_vector    = 3
filling_object   = [1,2]

x                = fill(filling_object, length_vector)
Output in REPL
julia>
x
3-element Vector{Vector{Int64}}:
 [1, 2]
 [1, 2]
 [1, 2]
length_vector    = 3
filling_object   = [1]

x                = fill(filling_object, length_vector)
Output in REPL
julia>
x
3-element Vector{Vector{Int64}}:
 [1]
 [1]
 [1]

Concatenating Vectors

Finally, we can create a vector z that combines all the elements of two vectors x and y. Based on material we covered, one approach for doing this is through z = [x ; y]. This method is suitable if only a few vectors need to be concatenated. However, the method becomes impractical when working programmatically or when the number of vectors to concatenate is unknown.

For these scenarios, we can instead employ the function vcat, which merges all its arguments into one vector. By use of the splat operator ...., this function also accepts a list of vectors as its arguments. [note] Recall that the operator ... splits a collection into multiple arguments. This enables the use of a vector or tuple to denote multiple function arguments. For further details, see here under the subsection "Splatting".

x = [3,4,5]
y = [6,7,8]


z = vcat(x,y)
Output in REPL
julia>
x
6-element Vector{Int64}:
 3
 4
 â‹®
 7
 8
x = [3,4,5]
y = [6,7,8]

A = [x, y]
z = vcat(A...)
Output in REPL
julia>
x
6-element Vector{Int64}:
 3
 4
 â‹®
 7
 8

Closely related to vector concatenation is the repeat function, which defines a vector containing the same object multiple times. Importantly, repeat requires a vector as its input, throwing an error if a scalar is passed.

nr_repetitions   = 3
vector_to_repeat = [1,2]

x                = repeat(vector_to_repeat, nr_repetitions)
Output in REPL
julia>
x
6-element Vector{Int64}:
 1
 2
 â‹®
 1
 2
nr_repetitions   = 3
vector_to_repeat = [1]

x                = repeat(vector_to_repeat, nr_repetitions)
Output in REPL
julia>
x
3-element Vector{Int64}:
 1
 1
 1
nr_repetitions   = 3
vector_to_repeat = 1

x                = repeat(vector_to_repeat, nr_repetitions)
Output in REPL
ERROR: MethodError: no method matching repeat(::Int64, ::Int64)

Adding, Removing, and Replacing Elements (OPTIONAL)

Warning!
This subsection applies a couple of concepts we haven't fully discussed yet. Considering this, the subsection is labelled as optional.

One concept is in-place functions, which are denoted by ! at the end of the function's name. The symbol is notation hinting that the function modifies one of its arguments, with no impact on its behavior. In our context, it'll indicate that the functions update the original vector, in which case the operation is to be said in-place. In-place functions will be explored thoroughly in a future section.

The other concept introduced is pairs, also studied in a future section. For its application, you only need to know that pairs are denoted by a => b, where a will refer to a value and b its replacement in our applications.

This last part considers how to add, remove, and replace elements of a vector. In the case of adding elements, the methods are as follows.

x                 = [3,4,5]
element_to_insert = 0


push!(x, element_to_insert)                 # add 0 at the end - faster
Output in REPL
julia>
x
4-element Vector{Int64}:
 3
 4
 5
 0
x                 = [3,4,5]
element_to_insert = 0


pushfirst!(x, element_to_insert)            # add 0 at the beginning - slower
Output in REPL
julia>
x
4-element Vector{Int64}:
 0
 3
 4
 5
x                 = [3,4,5]
element_to_insert = 0
at_index          = 2

insert!(x, at_index, element_to_insert)     # add 0 at index 2
Output in REPL
julia>
x
4-element Vector{Int64}:
 3
 0
 4
 5
x                 = [3,4,5]
vector_to_insert  = [6,7]


append!(x, vector_to_insert)                # add 6 and 7 at the end
Output in REPL
julia>
x
5-element Vector{Int64}:
 3
 4
 5
 6
 7

The function push! is particularly helpful to collect results in a vector: as it doesn't require any prior knowledge about the number of elements to be stored, we can dynamically grow the vector by adding more results. Furthermore, adding elements at the end by push! is faster than doing at the beginning by pushfirst!.

We can also remove elements in a similar fashion.

x                  = [5,6,7]


pop!(x)                            # delete last element
Output in REPL
julia>
x
2-element Vector{Int64}:
 5
 6
x                  = [5,6,7]


popfirst!(x)                       # delete first element
Output in REPL
julia>
x
2-element Vector{Int64}:
 6
 7
x                  = [5,6,7]
index_of_removal   = 2

deleteat!(x, index_of_removal)      # delete element at index 2
Output in REPL
julia>
x
2-element Vector{Int64}:
 5
 7
x                  = [5,6,7]
indices_of_removal = [1,3]

deleteat!(x, indices_of_removal)    # delete elements at indices 1 and 3
Output in REPL
julia>
x
1-element Vector{Int64}:
 6

Emulating the behavior of deleteat!, we can also indicate which elements we want to keep instead.

x               = [5,6,7]
index_to_keep   = 2

keepat!(x, index_to_keep)
Output in REPL
julia>
x
1-element Vector{Int64}:
 6
x               = [5,6,7]
indices_to_keep = [2,3]

keepat!(x, index_to_keep)
Output in REPL
julia>
x
1-element Vector{Int64}:
 6

Finally, we can replace specific values with new ones. This can be done through a new copy through replace or updating the value of the original vaector through replace!.

Both functions make use of pairs, which we haven't covered yet. At this point, you only need to know that the syntax for pairs is a => b, where a is the value and b its replacement. Also note that the functions substitute by values, not by indices. This implies that the functions replace all the values matched.

x = [3,3,5]

replace!(x, 3 => 0)              # in-place (it updates x)
Output in REPL
julia>
x
3-element Vector{Int64}:
 0
 0
 5
x = [3,3,5]

y = replace(x, 3 => 0)           # new copy
Output in REPL
julia>
y
3-element Vector{Int64}:
 0
 0
 5
x = [3,3,5]

replace!(x, 3 => 0, 5 => 1)      # in-place (it updates x)
Output in REPL
julia>
x
3-element Vector{Int64}:
 0
 0
 1
x = [3,3,5]

y = replace(x, 3 => 0, 5 => 1)   # new copy
Output in REPL
julia>
y
3-element Vector{Int64}:
 0
 0
 1