Besides multidimensional arrays, which were discussed in the previous chapter, Julia comes with several built-in basic data structures that can be useful to access data in various ways.

Tuples

What are Tuples in Julia?

Tuples in Julia are collections of values of possibly different data types, which can’t be modified once initialized. Tuples are mostly good for small fixed-length collections, and are closely related to function arguments.

Initializing Tuples

Tuples can be created similarly to arrays, replacing square brackets with parenthesis. For example,

t = (3.14, 2.72)

Creates a variable with type Tuple{Float64, Float64}, whose values can be accessed via t[1] and t[2].

In fact, we don’t even need the parenthesis to initialize a tuple, and simply doing

t = 3.14, 2.72

will work just as well.

Destructuring Tuples

We can cast the elements of a tuple as different variables in a very straightforward manner:

pi_approx, e_approx = t

Tuples are, for this reason, a convenient return type for a function that seemingly returns multiple outputs, such as we discussed in the post about functions.

Converting Tuples to Arrays

Tuples can be converted to arrays in various ways: using the collect function, array comprehensions, or the splat notation.

a = (1, 2, 3)

t1 = collect(a);
t2 = [x for x in a];
t3 = [a...];           

all producing the same output:

3-element Vector{Int64}:
 1
 2
 3

Named Tuples

An interesting variant is that we can assign names to the different elements of a tuple. For example, by declaring

p = ( x = 1.1, y = 2.4)

The elements of p can be accessed via p.x and p.y as well as p[1] and p[2].

p is of type NamedTuple{(:x, :y), Tuple{Float64, Float64}}.

We can also retrieve the names of the keys and the values of a named tuple, using the keys and values functions, as follows:

K = keys(p)                 # (:x, :y)
V = values(p)               # (1.1, 2.4)

and we can merge this key/value pairs into a named tuple again with the zip function:

p_new = (; zip(K,V)...)     # (x = 1.1, y = 2.4)

Dictionaries

A Dictionary in Julia is a collection of key-value pairs, which provide much more flexibility than arrays or named tuples. In particular, Dictionaries are mutable, and the keys can be of any type (where in Arrays they have to be Integers, and in Named Tuples, symbols).

Creating a Dictionary

We can create a dictionary using the following syntax:

D = Dict("a" => 1, "b" => 2, 1 => "a")

Alternatively, we can initialize a Dictionary using key/value pairs:

D = Dict([("a", 1), ("b", 2), (1,"a")])

Accessing Elements of a Dictionary

Elements of a dictionary can be accessed similarly to arrays, using the corresponding keys: for example, doing D["a"] or D[1] will return the corresponding values of 1 and "a".

Dictionaries are also iterable objects, so we can loop through the elements of a given dictionary:

for e in D
    println(e)
end

as each element in a dictionary is a Pair, we can access individual components of e doing e[1] and e[2]. More conveniently, we can unstructure the pair into two variables, as follows:

for (k,v) in D
    println(k, " => ", v)
end
b => 2
a => 1
1 => a

Modifying a Dictionary

One important characteristics of dictionaries is that they are mutable structures, so can be modified. For example, the following is possible:

D["c"] = 3           # Adding a new key
D["c"] = "Hello"     # Updating existing key
D = delete!(D, "c")  # Deleting an existing key

Structs

Structs are a great way to represent data in a compact and easy-to-understand way. Additionally, there is so much that can be accomplished with just an array of struct.

In Julia, the struct keyword defines a new Composite Type, based on given field names, and optionally annotated individual types. By default, structs cannot be modified once initialized (i.e. are inmutable unless explicitly specified as mutable)

Let’s say we want to create a Location type, containing a name, and (lat,lon) coordinates. We can do this as follows:

struct Location
    name::String
    lat::Float32
    lon::Float32
end

To initialize a struct with values, the default constructor is simply using the struct name as a function:

loc1 = Location("Los Angeles", 34.0522,-118.2437)

We can access the struct fields with the dot notation, as is common in several languages:

loc1.name   # "Los Angeles"
loc1.lat    # 34.0522
loc1.lon    # -118.2437

Having defined this Location struct, now enables us to do things such as defining a vector of Locations called sites, and dynamically filling it with Location elements:

sites = Location[]
push!(sites, Location("Los Angeles", 34.0522,-118.2437))
push!(sites, Location("Las Vegas", 36.1699,-115.1398))

Notice that a very similar result could have been obtained via Named Tuples. Introducing a new Type such as our Location type can be convenient for additional clarity.

Mutable Structs

If we want to be able to modify the components of a struct after it has been initialized, it suffices to declare the struct as mutable, as follows:

mutable struct mLocation
    name::String
    lat::Float32
    lon::Float32
end

We can then do things like:

loc1 = mLocation("Los Angeles", 34.0522,-118.2437)
loc1.name = "LA"

[email protected]: Defaults Values and Keyword-Based Constructors

The [email protected] macro is a helpful tool that enables the use of default values in structs, and also keyword-based constructors.

For example, we can do the following:

[email protected] mutable struct Param
    Δt :: Float64 = 0.1
    n :: Int64
    m :: Int64
end

p = Params(m=50, n=35)

Further reading

More classical data structures in DataStructures.jl.

The official documentation contains many more things about Julia’s Type system not covered in this tutorial.

I’ll be adding more content about this topic, so stay tuned!