In a module named Stats
, write these functions:
minimum/1
, which takes a list of numbers as its argument and returns the smallest value.
maximum/1
, which takes a list of numbers as its argument and returns the largest value.
range/1
, which takes a list of numbers as its argument and returns a list containing the maximum and minimum values in the list.
Here’s the pseudocode for minimum/1
.

.
minimum/2
, which takes a list as its first argument and
the "smallest number so far" (the current candidate) as its second argument.
Use the remainder of the list (which you extracted in the previous step) as
the first argument to minimum/2
, and the first item in the list as the second
argument.
Here’s the pseudocode for minimum/2
.
minimum/2
is empty, the final result is the current
candidate. This stops the recursion.
If the list passed to minimum/2
is not empty, then see if the head of
the list is less than the current candidate.
minimum/2
with the tail of the list as the first argument
and the list head (the new "smallest number") as the second argument.
minimum/2
with the tail of the list as the first argument
and the current candidate (still the "smallest number")
as the second argument.
Of course, the code for maximum/1
is indentical to that of minimum/1
except that it tests for greater than rather than less than. The range/1
function will call both minimum/1
and maximum/1
.
Unlike most examples in Introducing Elixir, passing an empty list to this function will make it crash. That’s a reasonable thing to do, as an empty list can’t really be said to have a minimum value.
iex(1)> c("stats.ex") [Stats] iex(2)> data = [4, 1, 7, 17, 8, 2, 5] [4,1,7,17,8,2,5] iex(3)> Stats.minimum(data) 17 iex(4)> Stats.minimum([52, 46]) 46 iex(5)> Stats.maximum(data) 8 iex(6)> Stats.range(data) [17,8]
Add a function julian/1
to the dates
module that you wrote in
Étude 53. Given a string in ISO format ("yyyymmdd"
), it
returns the Julian date: the day of the year.
Here is some sample output.
iex(1)> c("dates.ex") [Dates] iex(2)> Dates.julian("20131231") 365 iex(3)> Dates.julian("20121231") 366 iex(4)> Dates.julian("20120205") 36 iex(5)> Dates.julian("20130205") 36 iex(6)> Dates.julian("19000301") 60 iex(7)> Dates.julian("20000301") 61 iex(8)> Dates.julian("20130101") 1
This is the approach I used when solving the problem.
The julian/1
function starts out by using the date_parts/1
function you wrote in Étude 52 to split the date into the year, month, and day. It then defines a 12item list called days_per_month
that contains the number of days in each month.
julian/1
then calls a helper function named month_total/3
to add up the total number of days in all the months preceding the one given in the date.
Its arguments are the month, the list of days per month, and an accumulated total, which starts at zero. month_total/3
takes the head of the days per month list and adds it to the accumulator. It then calls month_total/3
again with the month decreased by one, the tail of the days per month list, and the
accumulator value as its arguments.
Let’s take, as an example, the sequence of calls for April 18, 2013:
month_total(4, [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 0) month_total(3, 18, [28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 31) month_total(2, 18, [31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 59) month_total(1, 18, [30, 31, 30, 31, 31, 30, 31, 30, 31], 90)
When the month number hits 1 (hint: use a clause with 1 for the month), the accumulator returns the total number of days up through the beginning of April. This number (90) is returned to the caller, julian/1
, which adds the 18 remaining days to get a result of day number 108.
Of course, there’s still the problem of leap years. You need to add one to the result if it’s a leap year—but only if the month is after February.
You could also use a cond
to assign either 28
or 29
to a variable named days_in_feb
(depending on whether the year is a leap year), and then use that variable instead of 28 when you construct your original days per month list.
I’ll give you the code for the is_leap_year/1
function for free; it returns
true
if the given year is a leap year, false
otherwise.
defp is_leap_year(year) do (rem(year,4) == 0 and rem(year,100) != 0) or (rem(year, 400) == 0) end
As I was writing the next two études, I tried, despite the examples in the
book, to avoid using Enum.reverse/1
. I thought, "Why can’t I add items
to the end of a list using the cons (vertical bar; 
) notation?" Here’s why.
I decided to do a simple task: take a list of numbers and return a list consisting of the squares of the numbers. I tried adding new items to the end of the list with this code:
defmodule BadCode do def squares(numbers), do: squares(numbers, []) def squares([], result), do: result def squares([h  t], result), do: squares(t, [result  h * h]) end
The resulting list was in the correct order, but it was an improper list.
iex(1)> c("bad_code.ex") [BadCode] iex(2)> BadCode.squares([9, 4.22, 5]) [[[[]81]17.8084]25]
That didn’t work. Wait a minute—the book said that the right hand side of the
cons (
) operator should be a list. "OK, you want a list?" I thought.
"I’ve got your list right here." (See the last
line of the code, where I wrap the new item in square brackets.)
def squares2(numbers), do: squares2(numbers, []) def squares2([], result), do: result def squares2([h  t], result), do: squares2(t, [result  [h * h]])
There. That should do the trick.
iex(3)> c("bad_code.ex") /Users/elixir/code/ch06interlude/bad_code.ex:1: redefining module BadCode [BadCode] iex(4)> BadCode.squares2([9, 4.22, 5]) [[[[],81],17.8084],25]
The result was better, but only slightly better. I didn’t have an improper list any more, but now I had a list of list of list of lists. I could fix the problem by changing one line to flatten the final result.
def squares2([], result), do: List.flatten(result)
That worked, but it wasn’t a satisfying solution.
List
module, and
List.flatten
calls Erlang’s :list.flatten
function, and a look at http://www.erlang.org/doc/efficiency_guide/listHandling.html showed
that this is a very expensive operation.
In light of all of this, I decided to use Enum.reverse/1
and write
the code to generate a proper, nonnested list.
defmodule GoodCode do def squares(numbers), do: squares(numbers, []) def squares([], result), do: Enum.reverse(result) def squares([h  t], result), do: squares(t, [h * h  result]) end
iex(5)> c("good_code.ex") [GoodCode] iex(6)> GoodCode.squares([9, 4.22, 5]) [81,17.8084,25]
Success at last! The moral of the story?
OK. Back to work.
Dentists check the health of your gums by checking the depth of the "pockets" at six different locations around each of your 32 teeth. The depth is measured in millimeters. If any of the depths is greater than or equal to four millimeters, that tooth needs attention. (Thanks to Dr. Patricia Lee, DDS, for explaining this to me.)
Your task is to write a module named Teeth
and a function named
alert/1
. The function takes a list of 32 lists of six numbers as its
input. If a tooth isn’t present, it is represented by the list
[0]
instead of a list of six numbers. The function produces
a list of the tooth numbers that require attention. The numbers
must be in ascending order.
Here’s a function that returns a set of pocket depths for a person who has had her upper wisdom teeth, numbers 1 and 16, removed. Just copy and paste it into your module.
def pocket_depths do [[0], [2,2,1,2,2,1], [3,1,2,3,2,3], [3,1,3,2,1,2], [3,2,3,2,2,1], [2,3,1,2,1,1], [3,1,3,2,3,2], [3,3,2,1,3,1], [4,3,3,2,3,3], [3,1,1,3,2,2], [4,3,4,3,2,3], [2,3,1,3,2,2], [1,2,1,1,3,2], [1,2,2,3,2,3], [1,3,2,1,3,3], [0], [3,2,3,1,1,2], [2,2,1,1,3,2], [2,1,1,1,1,2], [3,3,2,1,1,3], [3,1,3,2,3,2], [3,3,1,2,3,3], [1,2,2,3,3,3], [2,2,3,2,3,3], [2,2,2,4,3,4], [3,4,3,3,3,4], [1,1,2,3,1,2], [2,2,3,2,1,3], [3,4,2,4,4,3], [3,3,2,1,2,3], [2,2,2,2,3,3], [3,2,3,2,3,2]] end
And here’s the output:
iex(1)> c("teeth.ex") [Teeth] iex(2)> Teeth.alert(Teeth.pocket_depths()) [9,11,25,26,29]
Hint: use the Stats.maximum
function you wrote in “Étude 61: Recursive Iteration through a List” to see if a tooth needs attention.
How do you think I got the numbers for the teeth in the preceding étude? Do you really think I made up and typed all 180 of them? No, of course not. Instead, I wrote an Elixir program to create the list of lists for me, and that’s what you’ll do in this étude.
In order to create the data for the teeth,
I had to generate random numbers with Erlang’s
:random
module. Try generating a random number uniformly
distributed between 0 and 1.0 by typing this in iex
:
iex(1)> :random.uniform() 0.4435846174457203
Now, exit iex
, restart, and type the same command again. You’ll get the same number. In order to ensure that you get different sets of random numbers, you have to seed the random number generator with a threetuple. The easiest way to get a different seed every time you run the program is to use the :erlang.now/0
builtin function, which returns a different threetuple every time you call it.
iex(1)> :erlang.now() {1368,203897,899678} iex(2)> :erlang.now() {1368,203904,416818} iex(3)> :erlang.now() {1368,203909,179152}
Exit iex
, restart, it and try these commands. Do this a couple of times to
convince yourself that you really get different random numbers. Don’t worry
about the :undefined
; that’s just Erlang’s way of telling you that the
random number generator wasn’t seeded before.
iex(1)> :random.seed(:erlang.now()) :undefined iex(2)> :random.uniform() 0.4102329513116634
If you want to generate a random integer between 1 and N
, use
uniform/1
; thus :random.uniform(10)
will generate a
random integer from 1 to 10.
Functions that use random numbers have side effects; unlike the :math.sin
or
:math.sqrt
functions, which always give you the same numbers for the same input,
functions that use random numbers should always give you different numbers
for the same input. Since these functions aren’t "pure," it’s best to isolate
them in a module of their own.
In this étude, create a module named NonFP
, and write a function
generate_pockets/2
. This function has a character list consisting of
T
and F
for its first argument.
A T
in the list indicates that the tooth is present, and a
F
indicates a missing tooth. This will be a single quoted character list,
so you can treat it just as you would any other list. Remember to
refer to individual characters as ?T
and ?F
.
The second argument is a floating point number between 0 and 1.0 that indicates the probability that a tooth will be a good tooth.
The result is a list of lists, one list per tooth. If a tooth is present, the sublist has six entries; if a tooth is absent, the sublist is [0]
.
These are the helper functions I needed:
generate_pockets/3
The first two arguments are the same as for generate_pockets/2
; the
third argument is the accumulated list. When the first argument is
an empty list, the function yields the reverse of the accumulated list.
Hint: use pattern matching to figure out whether a tooth is present or not.
For a nonpresent tooth, add [0]
to the accumulated list; for a tooth
that is present, create a list of six numbers by calling generate_tooth/1
with the probability of a good tooth as its argument.
generate_tooth/1
This function takes the probability of a good tooth as its argument and generates the list of numbers for a single tooth. It generates a random number between 0 and 1. If that number is less than the probability of a good tooth, it sets the "base depth" to 2, otherwise it sets the base depth to 3.
The function then calls generate_tooth/3
with the base depth, the
number 6, and an empty list as its arguments.
generate_tooth/3