# 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)