In this module, we will see how we *actually* iterate over objects in *Julia*.
Although the content of the previous module is very important, as it forms the
basis of all ways to iterate, there are a number of functions that greatly
facilitate our task. We finish this module by simulating a simple
host-parasitoid model.

What are the numbers between 12 and 17? We can represent this as a `UnitRange`

(a memory efficient way to store sequences):

```
our_seq = 12:17
```

```
12:17
```

After reading the module on indexing, we could get the second entry in this sequence with

```
our_seq[2]
```

```
13
```

But of course, performing any operation on *all* numbers in the sequence would
be a little time-consuming. Therefore, we will iterate.

It would be very tempting to iterate from the first index (`1`

) to the last
index of the sequence (its `length`

). *Julia* discourages this, because
`length`

is not really meant to help with iteration. And luckily, there is a
much, much better

```
for i in eachindex(our_seq)
@info our_seq[i]
end
```

```
[ Info: 12
[ Info: 13
[ Info: 14
[ Info: 15
[ Info: 16
[ Info: 17
```

What is this mysterious `eachindex`

?

```
eachindex(our_seq)
```

```
Base.OneTo(6)
```

In short, it is an object that *Julia* prepares for us, that makes iteration
safe. But there is an even better way to iterate. Let’s assume that we have a
sequence of evenly spaced numbers:

```
our_other_seq = LinRange(0.0, 1.0, 6)
```

```
6-element LinRange{Float64, Int64}:
0.0, 0.2, 0.4, 0.6, 0.8, 1.0
```

We can iterate on these values to take, for example, their square, this way:

```
for i in eachindex(our_other_seq)
@info "i = $(i) (xᵢ)² = $(our_other_seq[i]^2.0)"
end
```

```
[ Info: i = 1 (xᵢ)² = 0.0
[ Info: i = 2 (xᵢ)² = 0.04000000000000001
[ Info: i = 3 (xᵢ)² = 0.16000000000000003
[ Info: i = 4 (xᵢ)² = 0.36
[ Info: i = 5 (xᵢ)² = 0.6400000000000001
[ Info: i = 6 (xᵢ)² = 1.0
```

`0.2^2.0`

is *not*

`0.04`

. There is a reason for
this, and it is: computers are not very good with
numbers. It’s OK, neither are
we; hopefully it’ll sort itself out (it
won’t).But there is a more efficient way to iterate:

```
for (i, x) in enumerate(our_other_seq)
@info "i = $(i) (xᵢ)² = $(x^2.0)"
end
```

```
[ Info: i = 1 (xᵢ)² = 0.0
[ Info: i = 2 (xᵢ)² = 0.04000000000000001
[ Info: i = 3 (xᵢ)² = 0.16000000000000003
[ Info: i = 4 (xᵢ)² = 0.36
[ Info: i = 5 (xᵢ)² = 0.6400000000000001
[ Info: i = 6 (xᵢ)² = 1.0
```

The `enumerate`

function is making things a little more complex because our
mental model of `for`

, `variable`

, `values`

is a little bit invalidated now.
This is because it returns not one value but two: the position in the object
we are iterating over, and the *value* at this position. This is a little
confusing, so let’s open-up the `enumerate`

function:

```
collect(enumerate(our_other_seq))
```

```
6-element Vector{Tuple{Int64, Float64}}:
(1, 0.0)
(2, 0.2)
(3, 0.4)
(4, 0.6)
(5, 0.8)
(6, 1.0)
```

This is something we know! It’s a tuple! It’s tuples in a vector! And we know
from the module on tuples that they can be used to store values until we are
ready to use them, and so this is what `enumerate`

does: it stores together
the position and the value.

But what about arrays with higher dimensions? The same logic applies. Let’s create a little matrix, and see how we can iterate over it:

```
A = reshape(Array(7:12), 2, 3)
```

```
2×3 Matrix{Int64}:
7 9 11
8 10 12
```

Let’s start to get a sense of the output of `eachindex`

:

```
collect(enumerate(A))
```

```
2×3 Matrix{Tuple{Int64, Int64}}:
(1, 7) (3, 9) (5, 11)
(2, 8) (4, 10) (6, 12)
```

This is very similar to the output we got for a vector, with the exception that the shape of the enumerated elements matches the shape of the matrix. Will it be an issue? Is there something we need to do? No.

Recall from the module on indexing that we can index a matrix linearly, so we don’t need to change the way we work:

```
for (pos, val) in enumerate(A)
@info "A[$(pos)] = $(val)"
end
```

```
[ Info: A[1] = 7
[ Info: A[2] = 8
[ Info: A[3] = 9
[ Info: A[4] = 10
[ Info: A[5] = 11
[ Info: A[6] = 12
```

But what if we wanted to use the fact that matrices have rows and columns? In
this case, we can use the `axes`

function:

```
axes(A)
```

```
(Base.OneTo(2), Base.OneTo(3))
```

When called on an array, it will return a tuple of iterators (`OneTo`

is a
weird object, but essentially, `OneTo(3)`

will return the numbers from 1 to
3), one for each dimension. The `axes`

function has additional methods where
we specify the arguments, so we can write, for example:

```
for row in axes(A, 1)
for col in axes(A, 2)
@info "A[$(row),$(col)] = $(A[row,col])"
end
end
```

```
[ Info: A[1,1] = 7
[ Info: A[1,2] = 9
[ Info: A[1,3] = 11
[ Info: A[2,1] = 8
[ Info: A[2,2] = 10
[ Info: A[2,3] = 12
```

But wait! This is two loops, one nested in the other. There has got to be an easier way to write this. When we are are dealing with nested loops, we can declare all of them on the same line:

```
for row in axes(A, 1), col in axes(A, 2)
@info row, col
end
```

```
[ Info: (1, 1)
[ Info: (1, 2)
[ Info: (1, 3)
[ Info: (2, 1)
[ Info: (2, 2)
[ Info: (2, 3)
```