6.2. Array Functions#

6.2.1. Introduction to Array functions#

We have seen before how many for-loops over arrays can be avoided using e.g. the dot-operator, which evaluates functions and operators element-wise. A number of other helper functions are available to extend this so-called vectorization technique, and allow for shorter code that is easier to understand and which might execute faster.

For example, a very common operation is to sum all the elements of an array. While this is easy to do with a for-loop, the function sum makes it even easier:

x = rand(1:100, 5)
println("Sum of ", x, " = ", sum(x))
Sum of [82, 12, 4, 59, 67] = 224

The sum function can also take an additional dimension argument dims, to specify which dimension it should add along. For example, it can add all the elements in each column (along dimension 1, producing a vector of sums):

A = rand(-100:100, 3, 5)
3×5 Matrix{Int64}:
 -35  -70  82   24  -33
  18    0  60   -4   65
   3   92  62  -60  -76
sum(A, dims=1)
1×5 Matrix{Int64}:
 -14  22  204  -40  -44

or it can add all the elements in each row (along dimension 2):

sum(A, dims=2)
3×1 Matrix{Int64}:
 -32
 139
  21

Note that while these functions reduce a 2D array to a 1D array, the output is still a 2D array (a row of a column vector). The syntax [:] after any array will convert it to a 1D array, which might be needed for example when using the array as indices.

sum(A, dims=1)[:]
5-element Vector{Int64}:
 -14
  22
 204
 -40
 -44

Some other examples of these so-called array reduction functions are shown below.

x = rand(1:100, 5)
display(x)
prod(x)                         # Product of all elements
5-element Vector{Int64}:
 31
 60
 62
 82
 85
803780400
display(A)
display(maximum(A, dims=1))     # Largest element in each column
display(minimum(A, dims=1))     # Smallest element in each column
3×5 Matrix{Int64}:
 -35  -70  82   24  -33
  18    0  60   -4   65
   3   92  62  -60  -76
1×5 Matrix{Int64}:
 18  92  82  24  65
1×5 Matrix{Int64}:
 -35  -70  60  -60  -76

The cumulative sum and product functions work in a similar way, but produce arrays of the same size as the original array:

x = 1:5
display(cumsum(x))                   # Cumulative sum, that is, entry n is the sum of x_1,...,x_n
display(cumprod(x))                  # Cumulative product
5-element Vector{Int64}:
  1
  3
  6
 10
 15
5-element Vector{Int64}:
   1
   2
   6
  24
 120
A = reshape(1:6, 2, 3)
2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
 1  3  5
 2  4  6
cumsum(A, dims=1)                    # Cumulative sum along dimension 1 (that is, column-wise)
2×3 Matrix{Int64}:
 1  3   5
 3  7  11
cumprod(A, dims=2)                   # Cumulative product along dimension 2
2×3 Matrix{Int64}:
 1  3  15
 2  8  48

6.2.2. Example: Taylor polynomial using array functions#

In part 1, we used for-loops to evaluate the Taylor polynomial for \(\cos x\) of a given degree \(2n\):

function taylor_cos(x,n)
    term = 1
    y = 1
    for k = 1:n
        term *= -x^2 / ((2k-1) * 2k)
        y += term
    end
    y
end

We can rewrite this in a vectorized form by using the cumprod and the sum functions. Note that each term in the sum is built up by multiplying the previous term by the expression -x^2 / ((2k-1) * 2k). This means we can create a vector with these values, and create all the terms using the cumprod function. Finally, the Taylor polynomial is obtained by adding all the terms using the sum function, and manually adding the first term 1 which is a special case:

function taylor_cos(x,n)
    factors = [ -x^2 / ((2k-1) * 2k) for k = 1:n ]
    y = 1 + sum(cumprod(factors))
end
taylor_cos (generic function with 1 method)
println(taylor_cos(10, 50)) # Taylor approximation
println(cos(10))            # true value
-0.8390715290752269
-0.8390715290764524

Since we now know how to plot functions, let us make the typical calculus plot that compares Taylor polynomial approximations of different degrees. Note how we can generate a 2D array yy_taylor of 6 different Taylor polynomials, for all values of x, using an array comprehension.

xx = -10:0.01:10
yy = cos.(xx)
yy_taylor = [ taylor_cos(x,n) for x in xx, n in 0:5 ]

using PyPlot
plot(xx, yy, linewidth=2, color="k")
plot(xx, yy_taylor)
grid(true)
axis([-10,10,-1.2,1.2]);
../../_images/37706eab4c5c014be89c2c66f639f7264d4d7a32bcf5b09cc950016caf2b3dca.png

6.2.3. Array functions for indices and booleans#

Another useful set of array functions are based on indices and boolean variables. For example, the in function (or the \(\in\) symbol) will loop through an array and return true if a given number appears anywhere in the array:

x = 1:2:1000                 # Odd numbers
println(503 in x)            # True, 503 is in the list
println(1000  x)            # False, 1000 is not in the list
true
false

The all function returns true if all elements in a boolean array are true:

println(all(x .< 500))
println(all(x .> 0))
false
true

The any function returns true if any element in a boolean array is true:

println(any(x .== 503))
println(any(x .== 1000))
true
false

We can also find the index of e.g. the first element that is true in a boolean array:

idx = findfirst(x .== 503)    # Index in x with the value 503
252

or more generally, a vector with the indices to all the elements that are true:

ind = findall(@. x % 97 == 0)
println("Indices in x with numbers that are multiples of 97: ", ind)
Indices in x with numbers that are multiples of 97: [49, 146, 243, 340, 437]

Finally, the count function returns the number of times true appears in a boolean array:

count(@. x % 97 == 0)
5