Basic Data Structures
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
The struct
keyword in Julia allows to define new Composite Types, 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!