description = "Calculus with Julia: Ranges and Sets",
tags = ["CalculusWithJulia", "precalc", "ranges and sets"],
);
nothing
```
## Arithmetic sequences
Sequences of numbers are prevalent in math. A simple one is just counting by ones:
```math
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, \dots
```
Or counting by sevens:
```math
7, 14, 21, 28, 35, 42, 49, \dots
```
More challenging for humans is [counting backwards](http://www.psychpage.com/learning/library/assess/mse.htm) by 7:
```math
100, 93, 86, 79, \dots
```
These are examples of [arithmetic sequences](http://en.wikipedia.org/wiki/Arithmetic_progression). The form of the first $n+1$ terms in such a sequence is:
```math
a_0, a_0 + h, a_0 + 2h, a_0 + 3h, \dots, a_0 + nh
```
The formula for the $a_n$th term can be written in terms of $a_0$, or
any other $0 \leq m \leq n$ with $a_n = a_m + (n-m)\cdot h$.
A typical question might be: The first term of an arithmetic sequence
is equal to ``200`` and the common difference is equal to ``-10``. Find the
value of $a_{20}$. We could find this using $a_n = a_0 + n\cdot h$:
```julia;hold=true
a0, h, n = 200, -10, 20
a0 + n * h
```
More complicated questions involve an unknown first value, as
with: an arithmetic sequence has a common difference equal to ``10`` and
its ``6``th term is equal to ``52``. Find its ``15``th term, $a_{15}$. Here we have to
answer: $a_0 + 15 \cdot 10$. Either we could find $a_0$ (using $52 = a_0 +
6\cdot(10)$) or use the above formula
```julia;hold=true
a6, h, m, n = 52, 10, 6, 15
a15 = a6 + (n-m)*h
```
### The colon operator
Rather than express sequences by the $a_0$, $h$, and $n$, `Julia` uses
the starting point (`a`), the difference (`h`) and a *suggested*
stopping value (`b`). That is, we need three values to specify these
ranges of numbers: a `start`, a `step`, and an `endof`. `Julia` gives
a convenient syntax for this: `a:h:b`. When the difference is just $1$, all
numbers between the start and end are specified by `a:b`, as in
```julia;
1:10
```
But wait, nothing different printed? This is because `1:10` is efficiently
stored. Basically, a recipe to generate the next number from the previous number is created and `1:10` just stores the start and end point and that recipe is used to generate the set of all values. To expand the values, you have to ask for them
to be `collect`ed (though this typically isn't needed in practice):
```julia;
collect(1:10)
```
When a non-default step size is needed, it goes in the middle, as in
`a:h:b`. For example, counting by sevens from ``1`` to ``50`` is achieved by:
```julia;
collect(1:7:50)
```
Or counting down from 100:
```julia;
collect(100:-7:1)
```
In this last example, we said end with ``1``, but it ended with ``2``. The
ending value in the range is a suggestion to go up to, but not exceed. Negative values for `h` are used to make decreasing sequences.
### The range function
For generating points to make graphs, a natural set of points to
specify is $n$ evenly spaced points between $a$ and $b$. We can mimic
creating this set with the range operation by solving for the correct
step size. We have $a_0=a$ and $a_0 + (n-1) \cdot h = b$. (Why $n-1$
and not $n$?) Solving yields $h = (b-a)/(n-1)$. To be concrete we
might ask for ``9`` points between $-1$ and $1$:
```julia;hold=true
a, b, n = -1, 1, 9
h = (b-a)/(n-1)
collect(a:h:b)
```
Pretty neat. If we were doing this many times - such as once per plot - we'd want to encapsulate this into a function, for example:
```julia;
function evenly_spaced(a, b, n)
h = (b-a)/(n-1)
collect(a:h:b)
end
```
Great, let's try it out:
```julia;
evenly_spaced(0, 2pi, 5)
```
Now, our implementation was straightforward, but only because it avoids somethings. Look at something simple:
```julia;
evenly_spaced(1/5, 3/5, 3)
```
It seems to work as expected. But looking just at the algorithm it isn't quite so clear:
```julia;
1/5 + 2*1/5 # last value
```
Floating point roundoff leads to the last value *exceeding* `0.6`, so should it be included? Well, here it is pretty clear it *should* be, but
better to have something programmed that hits both `a` and `b` and adjusts `h` accordingly.
Enter the base function `range` which solves this seemingly
simple - but not really - task. It can use `a`, `b`, and `n`. Like the
range operation, this function returns a generator which can be
collected to realize the values.
The number of points is specified with keyword arguments, as in:
provides this ability using a construct that closely mirrors a set definition, such as $\{2k:
k=0, \dots, 50\}$. The simplest use of a comprehension takes this
form (as we described in the section on vectors):
`[expr for variable in collection]`
The expression typically involves the variable specified after the keyword
`for`. The collection can be a range, a vector, or many other items
that are *iterable*. Here is how the mathematical set $\{2k: k=0,
\dots, 50\}$ may be generated by a comprehension:
```julia;
[2k for k in 0:50]
```
The expression is `2k`, the variable `k`, and the collection is the range
of values, `0:50`. The syntax is basically identical to how the math
expression is typically read aloud.
For some other examples, here is how we can create the first ``10`` numbers divisible by ``7``:
```julia;
[7k for k in 1:10]
```
Here is how we can square the numbers between ``1`` and ``10``:
```julia;
[x^2 for x in 1:10]
```
To generate other progressions, such as powers of ``2``, we could do:
```julia;
[2^i for i in 1:10]
```
Here are decreasing powers of ``2``:
```julia;
[1/2^i for i in 1:10]
```
Sometimes, the comprehension does not produce the type of output that
may be expected. This is related to `Julia`'s more limited abilities
to infer types at the command line. If the output type is important,
the extra prefix of `T[]` can be used, where `T` is the desired
type. We will see that this will be needed at times with symbolic math.
### Generators
A typical pattern would be to generate a collection of numbers and then apply a function to them. For example, here is one way to sum the powers of ``2``:
```julia;
sum([2^i for i in 1:10])
```
Conceptually this is easy to understand, but computationally it is a
bit inefficient. The generator syntax allows this type of task to be
done more efficiently. To use this syntax, we just need to drop the
`[]`:
```julia;
sum(2^i for i in 1:10)
```
(The difference being no intermediate object is created to store the collection of all values specified by the generator.)
### Filtering generated expressions
Both comprehensions and generators allow for filtering through the keyword `if`. The following shows *one* way to add the prime numbers in $[1,100]$:
```julia;
sum(p for p in 1:100 if isprime(p))
```
The value on the other side of `if` should be an expression that
evaluates to either `true` or `false` for a given `p` (like a
predicate function, but here specified as an expression). The value
returned by `isprime(p)` is such.
In this example, we use the fact that `rem(k, 7)` returns the
remainder found from dividing `k` by `7`, and so is `0` when `k` is a
multiple of `7`:
```julia;
sum(k for k in 1:100 if rem(k,7) == 0) ## add multiples of 7
```
The same `if` can be used in a comprehension. For example, this is an alternative to `filter` for identifying the numbers divisble by `7` in a range of numbers:
```julia;
[k for k in 1:100 if rem(k,7) == 0]
```
#### Example: Making change
This example of Stefan Karpinski comes from a
[blog](http://julialang.org/blog/2016/10/julia-0.5-highlights) post
highlighting changes to the `Julia` language with version
`v"0.5.0"`, which added features to comprehensions that made this example possible.
First, a simple question: using
pennies, nickels, dimes, and quarters how many different ways can we
generate one dollar? Clearly $100$ pennies, or $20$ nickels, or $10$ dimes,
or $4$ quarters will do this, so the answer is at least four, but how
much more than four?
Well, we can use a comprehension to enumerate the
possibilities. This example illustrates how comprehensions and generators can involve one or more variable for the iteration.
First, we either have $0,1,2,3$, or $4$ quarters, or $0$, $25$
cents, $50$ cents, $75$ cents, or a dollar's worth. If we have, say, $1$
quarter, then we need to make up $75$ cents with the rest. If we had $3$
dimes, then we need to make up $45$ cents out of nickels and pennies,
if we then had $6$ nickels, we know we must need $15$ pennies.
The following expression shows how counting this can be done through
enumeration. Here `q` is the amount contributed by quarters, `d` the
amount from dimes, `n` the amount from nickels, and `p` the amount from
pennies. `q` ranges over $0, 25, 50, 75, 100$ or `0:25:100`, etc. If
we know that the sum of quarters, dimes, nickels contributes a certain
amount, then the number of pennies must round things up to $100$.
```julia;
ways = [(q, d, n, p) for q = 0:25:100 for d = 0:10:(100 - q) for n = 0:5:(100 - q - d) for p = (100 - q - d - n)]
length(ways)
```
We see ``242`` cases, each distinct. The first $3$ are:
```julia;
ways[1:3]
```
The generating expression reads naturally. It introduces the use of
multiple `for` statements, each subsequent one depending on the value
of the previous (working left to right). Now suppose, we want to
ensure that the amount in pennies is less than the amount in nickels,
etc. We could use `filter` somehow to do this for our last answer, but
using `if` allows for filtering while the events are generating. Here
our condition is simply expressed: `q > d > n > p`:
```julia;
[(q, d, n, p) for q = 0:25:100
for d = 0:10:(100 - q)
for n = 0:5:(100 - q - d)
for p = (100 - q - d - n)
if q > d > n > p]
```
## Random numbers
We have been discussing structured sets of numbers. On the opposite
end of the spectrum are random numbers. `Julia` makes them easy to
generate, especially random numbers chosen uniformly from $[0,1)$.
- The `rand()` function returns a randomly chosen number in $[0,1)$.
- The `rand(n)` function returns a vector of `n` randomly chosen numbers in $[0,1)$.
To illustrate, this will command return a single number
```julia;
rand()
```
If the command is run again, it is almost certain that a different value will be returned:
```julia;
rand()
```
This call will return a vector of ``10`` such random numbers:
```julia;
rand(10)
```
The `rand` function is easy to use. The only common source of confusion is the subtle distinction between `rand()` and `rand(1)`, as the latter is a vector of ``1`` random number and the former just ``1`` random number.
## Questions
###### Question
Which of these will produce the odd numbers between ``1`` and ``99``?
How many numbers are in the sequence produced by `0:19:1000`?
```julia; hold=true;echo=false;
val = length(collect(0:19:1000))
numericq(val)
```
###### Question
The range operation (`a:h:b`) can also be used to countdown. Which of these will do so, counting down from `10` to `1`? (You can call `collect` to visualize the generated numbers.)