Section 7.2 Reducing an array
The
map command in general takes in an array and returns an array. Another common task with arrays is to reduce the entire array to a single value. If
arr=[1,2,3,4,5], then we can sum the values with the
reduce command as in:
which returns
15. This could be written simpler with
reduce(+, arr).
To multiply all numbers, we can type:
and the result is
120. This could be written simpler with
reduce(*, arr). In both of these cases, there is a little bit hidden, so let’s look at these in more detail.
-
First, the first argument of the
reduce command is a 2-argument (or binary) function.
-
Secondly, the operation is done on all numbers on the array, but needs to start with some value (because it is a binary function). In the sum case, the initial value is 0 (by default) and in the multiply case it is 1.
These are perhaps obvious examples and each of these have the built-in functions
sum and
prod that find the sum and product of an array.
Let’s look at an example that returns the number of array elements that are greater than zero:
numPos(arr) = reduce((n,val) -> val > 0 ? n+1 : n, arr, init=0)
and then if it is tested on an array of both positive and negative numbers:
this returns
3. How this works is as follows. There are two values associated within reduce from the function. The first is
n and the second is
val.
-
The variable
n is initially 0 (this is the
init=0 part of the function call)
-
On the first step,
val takes on the first value in the array (or
-3). It is checked if it is positive and if so, return
n+1 or
n. Since n=0 and
-3 is not positive, then the function returns 0.
-
On the second step,
val is 5 and this time the function returns
n+1 or 1
-
val is 8 and the function returns
n+1 or 2
-
val is -2 and the function returns
n or 2
-
val is 11 and the function returns
n+1 or 3.
-
Since the array has been passed through, the result is the last value or 3.
Check Your Understanding 7.2.
-
Write a reduce function that will count the number of times the string
"hi" appears in an array. Test it on
["hi","bye","hi","hello"] and other arrays of strings.
-
What does
reduce(*, ["J","u","l","i","a"],init="") do?
Section 7.3 The mapreduce function
The
mapreduce function is perhaps more helpful than
reduce. For example, if we want to sum the squares of each number in an array, then
mapreduce can do this easily.
mapreduce(x->x^2,+,[1,2,3],init=0)
is a short-hand way to do
\(1^2+2^2+3^2=14\text{.}\) For
mapreduce, the first argument is a function of one variable (unary) that is applied to every element of array. The second argument is a binary element that is used for the reduce part. Note: Julia also has a version of the
sum function that can apply a function. For example,
sum(x->x^2,[1,2,3]) returns the sum of
14.
Another example that we will see later is how to write a polynomial using its coefficients. For example, if
coeffs = [-2,4,5,7], then we want to create a way to produce the polynomial
"-2 x^0 + 4 x^1 + 5 x^2 + 7 x^3". This is a clear candidate for either a reduce or mapreduce because we have an array and reduce a single thing (in this case a string). We will use
mapreduce in this case because there is a transformation of each element which produces the polynomial term and then the reduce is a concatenation.
Although it seems like we should use the array itself, we are going to use the array (technically the range),
1:length(coeffs), so we can produce the powers in each of the term. Therefore the mapping function will be
i -> "$(coeffs[i])x^($i-1)"
which take a number
i and looks up the right coeffs and raise to the
i-1 power, since we need to shift the power by one. Next, we need a function that will concatenate the terms. This will be
(str, term) -> str * " + $term "
where
str is the current version of the polynomial string and
term will be the current term (which is created with the mapping function). Putting this altogether
mapreduce(i -> "$(coeffs[i]) x^$(i-1)", (str, term) -> str * " + $term " , 1:length(coeffs) )
and this will return the string
"-2 x^0 + 4 x^1 + 5 x^2 + 7 x^3 ". We will use this in
Section 12.3 when we crate a Polynomial type and this function will show the results in a more natural way.
Check Your Understanding 7.3.
In calculus, an important infinite series is
\begin{equation*}
1 + \frac{1}{4} + \frac{1}{9} + \frac{1}{16} + \cdots
\end{equation*}
and although we can’t sum an infinite number of terms, a finite version of this is still useful.
Use
mapreduce to sum the first 20 terms.
Hint.
Use
arr=1:20 for the array and the mapping function is the reciprocal.
Section 7.4 Mapping a Function over an 2D array
Above, we saw the
map function which applies a function over each element of the array. We will see in
Chapter 19 that the
mapslices functions is quite helpful.
Consider the array A=[i+j for i=1:10,j=1:3] which returns the array
10×3 Matrix{Int64}:
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
8 9 10
9 10 11
10 11 12
11 12 13
If we want to sum down the columns of the array, we can enter
mapslices(sum,A,dims=[1])
which returns the array
[65 75 85], which is a 1D array with the column sums. The
mapslices function needs three arguments, a function that can take an array, a 2D array and the keyword
dims which says how to apply the sum. Note: we can also replace the command
sum with
+ as well.
If instead we wanted to sum along rows, then
mapslices(sum,A,dims=[2])
10×1 Matrix{Int64}:
9
12
15
18
21
24
27
30
33
36
Although note that this is a 2D array. The function
mapslices returns an array of one fewer dimensions as the original.
Check Your Understanding 7.4.
Using the matrix
A=[i+j for i=1:10,j=1:3],
-
evaluate
mapslices(prod,A,dims=[1]). What does that function do?
-
evaluate
mapslices(prod,A,dims=[2]). What does that function do?
-
use the
mapslices function to find the maximum element in each row.
-
use the
mapslices function to find the maximum element in each column.
Section 7.5 Do-Begin-End blocks
All of the functions that are applied in the examples in this chapter are relatively simple in that they can be written as a single line and we do so in anonymous form. Consider instead a more complicated example that would be very difficult and not readable as a single line.
The input will be an array. For each number
i in the array if it is a multiple of 3, return the number, if
i mod 3 is 1, then return the square. If
i mod 3 is 2, then return twice the number.
map(1:10) do i
begin
if i % 3 == 0
x = i
elseif i % 3 == 1
x = i^2
else
x = 2i
end
x
end
end
and you may notice that this looks much different that the map function above. The structure is basically
map(values) do var
begin
# some steps/expression on variable var
#
# last line is the output for the given value of values.
end
end
This works with other functions as well. The function that counts the number of positive numbers in a list can be written as
numPos(values::Vector{Int}) = reduce(values, init = 0) do n, val
begin
pos = n
if val > 0
pos += 1
end
pos
end
end
and then can be run with
numPos([4,-8,22, 100, -2, 0, 4]) and return 4. This is clearly longer than the previous version of this, but perhaps much more readable.
NOTE: ADD AN EXERCISE HERE