# Appendix A. Solutions to Études

Here are the solutions that I came up with for the études in this book.

## Solution 2-1

Here is a suggested solution for Étude 2-1.

### geom.ex

defmodule Geom do
def area(length, width) do
length * width
end
end

## Solution 2-2

Here is a suggested solution for Étude 2-2.

### geom.ex

defmodule Geom do
def area(length \\ 1, width \\ 1) do
length * width
end
end

## Solution 2-3

Here is a suggested solution for Étude 2-3.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a rectangle, given the length and width.
Returns the product of its arguments. The default value for
both arguments is 1.
"""

@spec area(number(), number()) :: number()

def area(length \\ 1, width \\ 1) do
length * width
end
end

## Solution 3-1

Here is a suggested solution for Étude 3-1.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the shape and two of the
dimensions. Returns the product of its arguments for a rectangle,
one half the product of the arguments for a triangle, and
:math.pi times the product of the arguments for an ellipse.
"""

@spec area(atom(), number(), number()) :: number()

def area(:rectangle, length, width) do
length * width
end

def area(:triangle, base, height) do
base * height / 2.0
end

def area(:ellipse, a, b) do
:math.pi * a * b
end
end

## Solution 3-2

Here is a suggested solution for Étude 3-2.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the shape and two of the
dimensions. Returns the product of its arguments for a rectangle,
one half the product of the arguments for a triangle, and
:math.pi times the product of the arguments for an ellipse.
Accepts only dimensions that are non-negative.
"""

@spec area(atom(), number(), number()) :: number()

def area(:rectangle, length, width) when length >= 0 and width >= 0 do
length * width
end

def area(:triangle, base, height) when base >= 0 and height >= 0 do
base * height / 2.0
end

def area(:ellipse, a, b) when a >= 0 and b >= 0 do
:math.pi * a * b
end
end

## Solution 3-3

Here is a suggested solution for Étude 3-3.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the
shape and two of the dimensions. Returns the product
of its arguments for a rectangle, one half the
product of the arguments for a triangle, and
:math.pi times the product of the arguments for
an ellipse. Any invalid data returns zero.
"""

@spec area(atom(), number(), number()) :: number()

def area(:rectangle, length, width) when length >= 0 and width >= 0 do
length * width
end

def area(:triangle, base, height) when base >= 0 and height >= 0 do
base * height / 2.0
end

def area(:ellipse, a, b) when a >= 0 and b >= 0 do
:math.pi * a * b
end

def area(_, _, _) do
0
end
end

## Solution 3-4

Here is a suggested solution for Étude 3-4.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the
shape and two of the dimensions as a tuple.
Returns the productof its arguments for a rectangle,
one half the product of the arguments for a triangle, and
:math.pi times the product of the arguments for
an ellipse. Any invalid data returns zero.
"""

@spec area({atom(), number(), number()}) :: number()

def area({shape, dim1, dim2}) do
area(shape, dim1, dim2)
end

# Individual functions to handle each shape

@spec area(atom(), number(), number()) :: number()

defp area(:rectangle, length, width) when length >= 0 and width >= 0 do
length * width
end

defp area(:triangle, base, height) when base >= 0 and height >= 0 do
base * height / 2.0
end

defp area(:ellipse, a, b) when a >= 0 and b >= 0 do
:math.pi * a * b
end

defp area(_, _, _) do
0
end
end

## Solution 4-1

Here is a suggested solution for Étude 4-1.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the shape and two of the
dimensions. Returns the product of its arguments for a rectangle,
one half the product of the arguments for a triangle, and
:math.pi times the product of the arguments for an ellipse.
Accepts only dimensions that are non-negative.
"""

@spec area(atom(), number(), number()) :: number()

def area(shape, a, b) when a >= 0 and b >= 0 do
case shape do
:rectangle -> a * b
:triangle -> a * b  / 2.0
:ellipse -> :math.pi * a * b
end
end
end

## Solution 4-2

Here is a suggested solution for Étude 4-2.

### dijkstra.ex

defmodule Dijkstra do

@moduledoc """
Recursive function for calculating GCD of two numbers using
Dijkstra's algorithm.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the greatest common divisor of two integers.
Uses Dijkstra's algorithm, which does not require any division.
"""

@spec gcd(number(), number()) :: number()

def gcd(m, n) do
cond do
m == n -> m
m > n -> gcd(m - n, n)
true -> gcd(m, n - m)
end
end
end

## Solution 4-2

Here is another solution for Étude 4-2. This solution uses multiple clauses with guards instead of cond.

### dijkstra.ex

defmodule Dijkstra do

@moduledoc """
Recursive function for calculating GCD of two numbers using
Dijkstra's algorithm.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the greatest common divisor of two integers.
Uses Dijkstra's algorithm, which does not require any division.
"""

@spec gcd(number(), number()) :: number()

def gcd(m, n) when m == n do
m
end

def gcd(m, n) when m > n do
gcd(m - n, n)
end

def gcd(m, n) do
gcd(m, n - m)
end
end

## Solution 4-3

Here is a suggested solution for Étude 4-3.

### powers.ex

defmodule Powers do
import Kernel, except: [raise: 2]

@moduledoc """
Function for raising a number to an integer power.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Raise a number x to an integer power n.
Any number to the power 0 equals 1.
Any number to the power 1 is that number itself.
When n is positive, x^n is equal to x times x^(n - 1)
When n is negative, x^n is equal to 1.0 / x^n
"""

@spec raise(number(), number()) :: number()

def raise(_, 0) do
1
end

def raise(x, 1) do
x
end

def raise(x, n) when n > 0 do
x * raise(x, n - 1)
end

def raise(x, n) when n < 0 do
1.0 / raise(x, -n)
end
end

### powers_traced.ex

This code contains output that lets you see the progress of the recursion.

defmodule Powers do
import Kernel, except: [raise: 2]

@moduledoc """
Function for raising a number to an integer power.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Raise a number x to an integer power n.
Any number to the power 0 equals 1.
Any number to the power 1 is that number itself.
When n is positive, x^n is equal to x times x^(n - 1)
When n is negative, x^n is equal to 1.0 / x^n
"""

@spec raise(number(), number()) :: number()

def raise(_, 0) do
1
end

def raise(x, 1) do
x
end

def raise(x, n) when n > 0 do
IO.puts("Enter with x = #{x}, n = #{n}")
result = x * raise(x, n - 1)
IO.puts("Result is #{result}")
result
end

def raise(x, n) when n < 0 do
1.0 / raise(x, -n)
end
end

## Solution 4-4

Here is a suggested solution for Étude 4-4.

### powers.ex

defmodule Powers do
import Kernel, except: [raise: 2, raise: 3]

@moduledoc """
Function for raising a number to an integer power.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Raise a number x to an integer power n.
Any number to the power 0 equals 1.
Any number to the power 1 is that number itself.
When n is positive, x^n is equal to x times x^(n - 1)
When n is negative, x^n is equal to 1.0 / x^n
"""

@spec raise(number(), number()) :: number()

def raise(_, 0) do
1
end

def raise(x, n) when n < 0 do
1.0 / raise(x, -n)
end

def raise(x, n) when n > 0 do
raise(x, n, 1)
end

# Helper function: counts down from n to 0,
# multiplying the accumulator by x each time.
# Returns the accumulator when n reaches zero.

@spec raise(number(), number(), number()) :: number()

defp raise(_x, 0, accumulator) do
accumulator
end

defp raise(x, n, accumulator) do
raise(x, n - 1, x * accumulator)
end
end

## Solution 4-5

Here is a suggested solution for Étude 4-5.

### powers.ex

defmodule Powers do
import Kernel, except: [raise: 2, raise: 3]

@moduledoc """
Function for raising a number to an integer power and finding
the nth root of a number using Newton's method.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculate the nth root of x using the Newton-Raphson method.
"""

@spec nth_root(number(), number()) :: number()

def nth_root(x, n) do
nth_root(x, n, x / 2.0)
end

# Helper function; given estimate for x^n,
# recursively calculates next estimated root as
# estimate - (estimate^n - x) / (n * estimate^(n-1))
# until the next estimate is within a limit of previous estimate.

defp nth_root(x, n, estimate) do
IO.puts("Current guess is #{estimate}")
f = raise(estimate, n) - x
f_prime = n * raise(estimate, n - 1)
next = estimate - f / f_prime
change = abs(estimate - next)
cond do
change < 1.0e-8 -> next
true -> nth_root(x, n, next)
end
end

@doc """
Raise a number x to an integer power n.
Any number to the power 0 equals 1.
Any number to the power 1 is that number itself.
When n is positive, x^n is equal to x times x^(n - 1)
When n is negative, x^n is equal to 1.0 / x^n
"""

@spec raise(number(), number()) :: number()

def raise(_, 0) do
1
end

def raise(x, n) when n < 0 do
1.0 / raise(x, -n)
end

def raise(x, n) when n > 0 do
raise(x, n, 1)
end

# Helper function: counts down from n to 0,
# multiplying the accumulator by x each time.
# Returns the accumulator when n reaches zero.

@spec raise(number(), number(), number()) :: number()

defp raise(_x, 0, accumulator) do
accumulator
end

defp raise(x, n, accumulator) do
raise(x, n - 1, x * accumulator)
end
end

## Solution 5-1

Here is a suggested solution for Étude 5-1.

### geom.ex

defmodule Geom do
@moduledoc """
Functions for calculating areas of geometric shapes.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculates the area of a shape, given the shape and two of the
dimensions. Returns the product of its arguments for a rectangle,
one half the product of the arguments for a triangle, and
:math.pi times the product of the arguments for an ellipse.
Accepts only dimensions that are non-negative.
"""

@spec area(atom(), number(), number()) :: number()

def area(shape, a, b) when a >= 0 and b >= 0 do
case shape do
:rectangle -> a * b
:triangle -> a * b  / 2.0
:ellipse -> :math.pi * a * b
end
end
end

### ask_area.ex

defmodule AskArea do
@moduledoc """
Validate input.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Requests a character for the name of a shape,
numbers for its dimensions, and calculates shape's area.
The characters are R for rectangle, T for triangle,
and E for ellipse. Input is allowed in either upper
or lower case. For unknown shapes, the first "dimension" will
be the unknown character.
"""

@spec area() :: number()

def area() do
input = IO.gets("R)ectangle, T)riangle, or E)llipse: ")
shape = char_to_shape(String.first(input))
{d1, d2} = case shape do
:rectangle -> get_dimensions("width", "height")
:triangle -> get_dimensions("base ", "height" )
:unknown -> {String.first(input), 0}
end
calculate(shape, d1, d2)
end

@doc """
Given a character, returns an atom representing the
specified shape (or the atom unknown if a bad character is given).
"""

@spec char_to_shape(char()) :: atom()

def char_to_shape(character) do
cond do
String.upcase(character) == "R" -> :rectangle
String.upcase(character) == "T" -> :triangle
String.upcase(character) == "E" -> :ellipse
true -> :unknown
end
end

@doc """
Present a prompt and get a number from the
user. Allow integers only.
"""
@spec get_number(String.t()) :: number()

def get_number(prompt) do
input = IO.gets("Enter #{prompt} > ")
binary_to_integer(String.strip(input))
end

@doc """
Get dimensions for a shape. Input are the two prompts,
output is a tuple {Dimension1, Dimension2}.
"""
@spec get_dimensions(String.t(), String.t()) :: {number(), number()}

def get_dimensions(prompt1, prompt2) do
n1 = get_number(prompt1)
n2 = get_number(prompt2)
{n1, n2}
end

@doc """
Calculate area of a shape, given its shape and dimensions.
Handle errors appropriately.
"""
@spec calculate(atom(), number(), number()) :: number()

def calculate(shape, d1, d2) do
cond do
shape == :unknown -> IO.puts("Unknown shape #{d1}")
d1 < 0 or d2 < 0 ->
IO.puts("Both numbers must be greater than or equal to zero.")
true -> Geom.area(shape, d1, d2)
end
end
end

## Solution 5-2

Here is a suggested solution for Étude 5-2.

The geom.ex file is the same as in Étude 5-1.

### ask_area.ex

defmodule AskArea do
@moduledoc """
Validate input.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Requests a character for the name of a shape,
numbers for its dimensions, and calculates shape's area.
The characters are R for rectangle, T for triangle,
and E for ellipse. Input is allowed in either upper
or lower case. For unknown shapes, the first "dimension" will
be the unknown character.
"""

@spec area() :: number()

def area() do
input = IO.gets("R)ectangle, T)riangle, or E)llipse: ")
shape = char_to_shape(String.first(input))
{d1, d2} = case shape do
:rectangle -> get_dimensions("width", "height")
:triangle -> get_dimensions("base ", "height" )
:unknown -> {String.first(input), 0}
end
calculate(shape, d1, d2)
end

@doc """
Given a character, returns an atom representing the
specified shape (or the atom unknown if a bad character is given).
"""

@spec char_to_shape(char()) :: atom()

def char_to_shape(character) do
cond do
String.upcase(character) == "R" -> :rectangle
String.upcase(character) == "T" -> :triangle
String.upcase(character) == "E" -> :ellipse
true -> :unknown
end
end

@doc """
Present a prompt and get a number from the
user. Allow integers only.
"""
@spec get_number(String.t()) :: number()

def get_number(prompt) do
input = IO.gets("Enter #{prompt} > ")
binary_to_integer(String.strip(input))
end

@doc """
Get dimensions for a shape. Input are the two prompts,
output is a tuple {Dimension1, Dimension2}.
"""
@spec get_dimensions(String.t(), String.t()) :: {number(), number()}

def get_dimensions(prompt1, prompt2) do
n1 = get_number(prompt1)
n2 = get_number(prompt2)
{n1, n2}
end

@doc """
Calculate area of a shape, given its shape and dimensions.
Handle errors appropriately.
"""
@spec calculate(atom(), number(), number()) :: number()

def calculate(shape, d1, d2) do
cond do
shape == :unknown -> IO.puts("Unknown shape #{d1}")
d1 < 0 or d2 < 0 ->
IO.puts("Both numbers must be greater than or equal to zero.")
true -> Geom.area(shape, d1, d2)
end
end
end

## Solution 5-3

Here is a suggested solution for Étude 5-3.

### dates.ex

defmodule Dates do
@moduledoc """
Functions for manipulating calendar dates.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Takes a string in ISO date format (yyyy-mm-dd) and
returns a list of integers in form [year, month, day].
"""
@spec date_parts(list) :: list

def date_parts(date_str) do
[y_str, m_str, d_str] = String.split(date_str, ~r/-/)
[binary_to_integer(y_str), binary_to_integer(m_str),
binary_to_integer(d_str)]
end
end

## Solution 6-1

Here is a suggested solution for Étude 6-1.

### stats.ex

defmodule Stats do
@moduledoc """
Functions for calculating basic statistics.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Recursively find the minimum entry in a list of numbers."

@spec minimum([number]) :: number

def minimum(list) do
end

# When there are no more numbers, return the result.

@spec minimum([number], number) :: number

defp minimum([], result) do
result
end

# If the current result is less than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.
# Note that you can use a variable assigned as part of a cons in a guard.

minimum(tail, result)
end

# Otherwise, the head of the list becomes the new minimum,
# and recursively look at the remainder of the list.

defp minimum([head | tail], _result) do
end

@doc "Recursively find the maximum entry in a list of numbers."
@spec maximum([number]) :: number

def maximum(list) do
end

# When there are no more numbers, return the result.

@spec maximum([number], number) :: number

defp maximum([], result) do
result
end

# If the current result is greater than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.

maximum(tail, result)
end

# Otherwise, the head of the list becomes the new maximum,
# and recursively look at the remainder of the list.

defp maximum([head | tail], _result) do
end

# @doc "Find the range of a list of numbers as a list [min, max]."
@spec range([number]) :: [number]

def range(list) do
[minimum(list), maximum(list)]
end

end

## Solution 6-2

Here is a suggested solution for Étude 6-2.

### dates.ex

defmodule Dates do
@moduledoc """
Functions for manipulating calendar dates.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Calculate julian date from an ISO date string"

@spec julian(String.t) :: number

def julian(date_str) do
[y, m, d] = date_parts(date_str)
days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
result = month_total(m, days_per_month, 0) + d
cond  do
is_leap_year(y) and m > 2 -> result + 1
true -> result
end
end

@spec month_total(number, [number], number) :: number

# Helper function that recursively accumulates days
# for all months up to (but not including) the current month

defp month_total(1, _days_per_month, total) do
total
end

defp month_total(m, [this_month|other_months], total) do
month_total(m - 1, other_months, total + this_month)
end

defp is_leap_year(year) do
(rem(year,4) == 0 and rem(year,100) != 0)
or (rem(year, 400) == 0)
end

@doc """
Takes a string in ISO date format (yyyy-mm-dd) and
returns a list of integers in form [year, month, day].
"""
@spec date_parts(list) :: list

def date_parts(date_str) do
[y_str, m_str, d_str] = String.split(date_str, ~r/-/)
[binary_to_integer(y_str), binary_to_integer(m_str),
binary_to_integer(d_str)]
end
end

Here is a suggested solution for Étude 6-3 with leap years handled in a different way.

### dates.ex

defmodule Dates do
@moduledoc """
Functions for manipulating calendar dates.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Calculate julian date from an ISO date string"

@spec julian(String.t) :: number

def julian(date_str) do
[y, m, d] = date_parts(date_str)
days_in_feb = cond do
is_leap_year(y) -> 29
true -> 28
end
days_per_month = [31, days_in_feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
month_total(m, days_per_month, 0) + d
end

@spec month_total(number, [number], number) :: number

# Helper function that recursively accumulates days
# for all months up to (but not including) the current month

defp month_total(1, _days_per_month, total) do
total
end

defp month_total(m, [this_month|other_months], total) do
month_total(m - 1, other_months, total + this_month)
end

defp is_leap_year(year) do
(rem(year,4) == 0 and rem(year,100) != 0)
or (rem(year, 400) == 0)
end

@doc """
Takes a string in ISO date format (yyyy-mm-dd) and
returns a list of integers in form [year, month, day].
"""
@spec date_parts(list) :: list

def date_parts(date_str) do
[y_str, m_str, d_str] = String.split(date_str, ~r/-/)
[binary_to_integer(y_str), binary_to_integer(m_str),
binary_to_integer(d_str)]
end
end

## Solution 6-3

Here is a suggested solution for Étude 6-3.

### teeth.ex

defmodule Teeth do
@moduledoc """
Manipulate a list of lists representing tooth pocket depths.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Convenience function for providing a list of lists."
@spec pocket_depths() :: list(list())

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

@doc """
Given a list of list representing tooth pocket depths, return
a list of the tooth numbers that require attention (any pocket
depth greater than or equal to four).
"""

end

# Helper function that adds to the list of teeth needing attention

Enum.reverse(result)
end

def alert([h | tail], tooth_number, result) do
cond do
Stats.maximum(h) >= 4 ->
alert(tail, tooth_number + 1, [tooth_number | result])
true ->
end
end
end

### stats.ex

defmodule Stats do
@moduledoc """
Functions for calculating basic statistics.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Recursively find the minimum entry in a list of numbers."

@spec minimum([number]) :: number

def minimum(list) do
end

# When there are no more numbers, return the result.

@spec minimum([number], number) :: number

defp minimum([], result) do
result
end

# If the current result is less than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.
# Note that you can use a variable assigned as part of a cons in a guard.

minimum(tail, result)
end

# Otherwise, the head of the list becomes the new minimum,
# and recursively look at the remainder of the list.

defp minimum([head | tail], _result) do
end

@doc "Recursively find the maximum entry in a list of numbers."
@spec maximum([number]) :: number

def maximum(list) do
end

# When there are no more numbers, return the result.

@spec maximum([number], number) :: number

defp maximum([], result) do
result
end

# If the current result is greater than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.

maximum(tail, result)
end

# Otherwise, the head of the list becomes the new maximum,
# and recursively look at the remainder of the list.

defp maximum([head | tail], _result) do
end

# @doc "Find the range of a list of numbers as a list [min, max]."
@spec range([number]) :: [number]

def range(list) do
[minimum(list), maximum(list)]
end

end

## Solution 6-4

Here is a suggested solution for Étude 6-4.

### non_fp.ex

defmodule NonFP do
@moduledoc """
Use non-functional-programming constructs to create
a list of lists with random entries.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Generate a list of list representing pocket depths for a set of teeth.
The first argument is a character list consisting of T and F for teeth
that are present or absent. The second argument is the probability that
any given tooth will be good.
"""
@spec generate_pockets(list, number) :: list(list)

def generate_pockets(tooth_list, prob_good) do
:random.seed(:erlang.now())
generate_pockets(tooth_list, prob_good, [])
end

# When the tooth list is empty, return the final list of lists
# If a tooth is present, generate a set of six depths and add it
# to the result; otherwise add a [0] to the result.

defp generate_pockets([], _, result) do
Enum.reverse(result)
end

tooth = generate_tooth(prob_good)
generate_pockets(tail, prob_good, [tooth | result])
end

defp generate_pockets([_head | tail], _prob_good, result) do
generate_pockets(tail, _prob_good, [[0] | result])
end

@doc """
Generate a set of six pocket depths for a tooth, given a probability
that a tooth is good.
"""
@spec generate_tooth(number) :: list(number)

def generate_tooth(prob_good) do
r = :random.uniform()
if (r < prob_good) do
base_depth = 2
else
base_depth = 3
end
generate_tooth(base_depth, 6, [])
end

def generate_tooth(_base, 0, result) do
result
end

def generate_tooth(base, n, result) do
delta = :random.uniform(3) - 2  # result will be -1, 0, or 1
generate_tooth(base, n - 1, [base + delta | result])
end

def test_pockets() do
tlist = 'FTTTTTTTTTTTTTTFTTTTTTTTTTTTTTTT'
big_list = generate_pockets(tlist, 0.75)
print_pockets(big_list)
end

def print_pockets([]), do: IO.puts("Finished.")

print_pockets(tail)
end
end

## Solution 7-1

Here is a suggested solution for Étude 7-1.

### college.ex

defmodule College do
@moduledoc """
Using files and hash dictionaries.
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Open a file with columns course ID, name, and room.
Construct a HashDict with the room as a key and the courses
taught in that room as the value.
"""
@spec make_room_list(String.t) :: HashDict.t

def make_room_list(file_name) do
{_result, device} = File.open(file_name, [:read, :utf8])
room_list = HashDict.new()
process_line(device, room_list)
end

# Read next line from file; if not end of file, process
# the room on that line. Recursively read through end of file.

defp process_line(device, room_list) do
case data do
:eof ->
File.close(device)
room_list
_ ->
updated_list = process_room(data, room_list)
process_line(device, updated_list)
end
end

# Extract information from a line in the file, and append
# course to hash dictionary value for the given room.

defp process_room(data, room_list) do
[_id, course, room] = String.split(String.strip(data), ",")
course_list = HashDict.get(room_list, room)
case course_list do
nil -> updated_list = HashDict.put_new(room_list, room, [course])
_ -> updated_list = HashDict.put(room_list, room, [course | course_list])
end
updated_list
end
end

## Solution 7-1

Here is a suggested solution for Étude 7-2.

### geography.ex

defmodule City do
defstruct name: "",  population: 0, latitude: 0.0, longitude: 0.0
end

defmodule Country do
defstruct name: "", language: "", cities: []
end

defmodule Geography do
@moduledoc """
Using files and structures.
from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Open a file with columns country code, city, population,
latitude, and longitude.
Construct a Country structure for each country containing
the cities in that country.
"""
@spec make_geo_list(String.t) :: Country.t

def make_geo_list(file_name) do
{_result, device} = File.open(file_name, [:read, :utf8])
process_line(device, [])
end

# Read next line from file; if not end of file, process
# the data on that line. Recursively read through end of file.

defp process_line(device, geo_list) do
case data do
:eof ->
File.close(device)
geo_list
_ ->
info = String.split(String.strip(data), ",")
updated_list = process_info(info, geo_list)
process_line(device, updated_list)
end
end

# If the info has only two elements, start a new country

defp process_info([country, language], geo_list) do
[%Country{name: country, language: language, cities: []}
| geo_list]
end

# If it has four elements, it's a city; add it to the list of
# cities. Notice the code for updating the cities field in the

defp process_info([city, populn, lat, long], [hd|tail]) do
new_cities = [%City{name: city, population: String.to_integer(populn),
latitude: String.to_float(lat), longitude: String.to_float(long)} |
hd.cities]
[%Country{ hd | cities: new_cities} | tail]
end

end

## Solution 7-3

Here is a suggested solution for Étude 7-3.

### geography.ex

defmodule City do
defstruct name: "",  population: 0, latitude: 0.0, longitude: 0.0
end

defmodule Country do
defstruct name: "", language: "", cities: []
end

defmodule Geography do
@moduledoc """
Using files and structures.
from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Open a file whose name is given in the first argument.
The file contains country name and primary language,
followed by (for each country) lines giving the name
of a city, its population, latitude, and longitude.
Construct a Country structure for each country containing
the cities in that country.
"""
@spec make_geo_list(String.t) :: Country.t

def make_geo_list(file_name) do
{_result, device} = File.open(file_name, [:read, :utf8])
process_line(device, [])
end

@doc """
Find the total population of all cities in the list
that are in countries with a given primary language.
"""

@spec total_population([Country], String.t) :: integer

def total_population(geo_list, language) do
total_population(geo_list, language, 0)
end

defp total_population([], _language, total) do
total
end

else
total_population(tail, language, total)
end
end

defp subtotal([], accumulator) do
accumulator
end

defp subtotal([head | tail], accumulator) do
end

# Read next line from file; if not end of file, process
# the data on that line. Recursively read through end of file.

defp process_line(device, geo_list) do
case data do
:eof ->
File.close(device)
geo_list
_ ->
info = String.split(String.strip(data), ",")
updated_list = process_info(info, geo_list)
process_line(device, updated_list)
end
end

# If the info has only two elements, start a new country

defp process_info([country, language], geo_list) do
[%Country{name: country, language: language, cities: []}
| geo_list]
end

# If it has four elements, it's a city; add it to the list of
# cities. Notice the code for updating the cities field in the

defp process_info([city, populn, lat, long], [hd|tail]) do
new_cities = [%City{name: city, population: String.to_integer(populn),
latitude: String.to_float(lat), longitude: String.to_float(long)} |
hd.cities]
[%Country{ hd | cities: new_cities} | tail]
end

end

## Solution 7-4

Here is a suggested solution for Étude 7-4. The code shown here is the code required to implement the additional protocols; the remaining code is the same as in

### city.ex

defprotocol Valid do
@doc "Returns true if data is considered valid"
def valid?(data)
end

defmodule City do
defstruct name: "",  population: 0, latitude: 0.0, longitude: 0.0
end

defimpl Valid, for: City do
def valid?(%City{population: p, latitude: lat, longitude: lon}) do
p > 0 && lat >= -90 && lat <= 90 &&
lon >= -180 && lon <= 180
end
end

defimpl Inspect, for: City do
import Inspect.Algebra

def inspect(item, _options) do
lat = if (item.latitude < 0) do
concat(to_string(Float.round(abs(item.latitude * 1.0), 2)), "°S")
else
concat(to_string(Float.round(item.latitude * 1.0, 2)), "°N")
end

lon = if (item.longitude < 0) do
concat(to_string(Float.round(abs(item.longitude * 1.0), 2)), "°W")
else
concat(to_string(Float.round(item.longitude * 1.0, 2)), "°E")
end

msg = concat([item.name, break,
"(", to_string(item.population), ")", break,
lat, break, lon])
pretty(msg, 80)
end
end

## Solution 8-1

Here is a suggested solution for Étude 8-1.

### calculus.ex

defmodule Calculus do
@moduledoc """
from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Calculate the approximation to the derivative of
function f and point x by using the mathematical
definition of a derivative.
"""
@spec derivative(fun, number) :: number

def derivative(f, x) do
delta = 1.0e-10
(f.(x + delta) - f.(x)) / delta
end
end

## Solution 8-2

Here is a suggested solution for Étude 8-2.

### list_comp.ex

defmodule ListComp do

# private function to return a list of people

@spec get_people() :: list(tuple())

defp get_people() do
[{"Federico", "M", 22}, {"Kim", "F", 45}, {"Hansa", "F", 30},
{"Tran", "M", 47}, {"Cathy", "F", 32}, {"Elias", "M", 50}]
end

@doc """
Select all males older than 40 from a list of tuples giving
name, gender, and age.
"""
@spec older_males() :: list(tuple())

def older_males() do
lc {person, gender, age} inlist get_people(), age > 40, gender == "M" do
{person, gender, age}
end
end

@doc"""
Select all people who are male or older than 40 from a list of
tuples giving name, gender, and age.
"""
@spec older_or_male() :: list
def older_or_male() do
lc {person, gender, age} inlist get_people(), age > 40 or gender == "M" do
{person, gender, age}
end
end
end

## Solution 8-3

Here is a suggested solution for Étude 8-3.

### stats.ex

defmodule Stats do
@moduledoc """
Functions for calculating basic statistics.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Compute the mean of a list of numbers. Uses List.foldl/3"
@spec mean([number]) :: number

def mean(list) do
List.foldl(list, 0, fn(x, acc) -> x + acc end) / Enum.count(list)
end

@doc """
Compute the standard deviation of a list of numbers. Uses
List.foldl/3 with a tuple as an accumulator.
"""
@spec stdv([number]) :: number

def stdv(list) do
n = Enum.count(list)
{sum, sum_sq} = List.foldl(list, {0,0}, fn(x, {acc, acc_sq}) ->
{acc + x, acc_sq + x * x} end)
:math.sqrt((n * sum_sq - sum * sum) / (n * (n - 1)))
end

@doc "Recursively find the minimum entry in a list of numbers."

@spec minimum([number]) :: number

def minimum(list) do
end

# When there are no more numbers, return the result.

@spec minimum([number], number) :: number

defp minimum([], result) do
result
end

# If the current result is less than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.
# Note that you can use a variable assigned as part of a cons in a guard.

minimum(tail, result)
end

# Otherwise, the head of the list becomes the new minimum,
# and recursively look at the remainder of the list.

defp minimum([head | tail], _result) do
end

@doc "Recursively find the maximum entry in a list of numbers."
@spec maximum([number]) :: number

def maximum(list) do
end

# When there are no more numbers, return the result.

@spec maximum([number], number) :: number

defp maximum([], result) do
result
end

# If the current result is greater than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.

maximum(tail, result)
end

# Otherwise, the head of the list becomes the new maximum,
# and recursively look at the remainder of the list.

defp maximum([head | tail], _result) do
end

# @doc "Find the range of a list of numbers as a list [min, max]."
@spec range([number]) :: [number]

def range(list) do
[minimum(list), maximum(list)]
end

end

## Solution 8-4

Here is a suggested solution for Étude 8-4.

### dates.ex

defmodule Dates do
@moduledoc """
Functions for manipulating calendar dates.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Calculate julian date from an ISO date string"

@spec julian(String.t) :: number

def julian(date_str) do
[y, m, d] = date_parts(date_str)
days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
result = month_total(m, days_per_month) + d
cond  do
is_leap_year(y) and m > 2 -> result + 1
true -> result
end
end

@spec month_total(number, [number]) :: number

# Helper function that recursively accumulates days
# for all months up to (but not including) the current month

defp month_total(m, days_per_month) do
{relevant, _} = Enum.split(days_per_month, m - 1)
List.foldl(relevant, 0, fn(x, acc) -> x + acc end)
end

defp is_leap_year(year) do
(rem(year,4) == 0 and rem(year,100) != 0)
or (rem(year, 400) == 0)
end

@doc """
Takes a string in ISO date format (yyyy-mm-dd) and
returns a list of integers in form [year, month, day].
"""
@spec date_parts(list) :: list

def date_parts(date_str) do
[y_str, m_str, d_str] = String.split(date_str, ~r/-/)
[binary_to_integer(y_str), binary_to_integer(m_str),
binary_to_integer(d_str)]
end
end

## Solution 8-5

Here is a suggested solution for Étude 8-5.

### cards.ex

defmodule Cards do
@moduledoc """
Functions for simulating a deck of cards.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Create a deck of 52 tuples in the form [{"A", "Clubs"},
{"A", "Diamonds"}...]
"""
@spec make_deck() :: list(tuple)

def make_deck() do
lc value inlist ["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"],
suit inlist ["Clubs", "Diamonds", "Hearts", "Spades"], do:
{value, suit}
end
end

## Solution 8-6

Here is a suggested solution for Étude 8-6.

### cards.ex

defmodule Cards do
@moduledoc """
Functions for simulating a deck of cards.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Shuffle a list into random order using the Fisher-Yates method.
"""
@spec shuffle(list) :: list

def shuffle(list) do
:random.seed(:erlang.now())
shuffle(list, [])
end

# The helper function takes a list to shuffle as its first
# argument and the accumulated (shuffled) list as its second
# argument.

# When there are no more cards left to shuffle,
# return the accumulated list.

def shuffle([], acc) do
acc
end

# This is easier to understand if you look at it as a
# physical process. Split the deck at a random point.
# Put the part above the "split point" aside (leading), and
# take the first card (h) off the part below the split (t).
# That first card goes onto a new pile ([h | acc]).
# Now put together the part above the split and the
# part below the split (leading ++ t) and go through
# the process with the deck (which now has one less card).
# This keeps going until you run out of cards to shuffle;
# at that point, all the cards will have gotten to the
# new pile, and that's your shuffled deck.

def shuffle(list, acc) do
Enum.split(list, :random.uniform(Enum.count(list)) - 1)
shuffle(leading ++ t, [h | acc])
end

@doc """
Create a deck of 52 tuples in the form [{"A", "Clubs"},
{"A", "Diamonds"}...]
"""
@spec make_deck() :: list(tuple)

def make_deck() do
lc value inlist ["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"],
suit inlist ["Clubs", "Diamonds", "Hearts", "Spades"], do:
{value, suit}
end

end

## Solution 9-1

Here is a suggested solution for Étude 9-1.

### cards.ex

defmodule Cards do
@moduledoc """
Functions for simulating a deck of cards.

from *Études for Elixir*, O'Reilly Media, Inc., 2013.
Copyright 2013 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Shuffle a list into random order using the Fisher-Yates method.
"""
@spec shuffle(list) :: list

def shuffle(list) do
:random.seed(:erlang.now())
shuffle(list, [])
end

# The helper function takes a list to shuffle as its first
# argument and the accumulated (shuffled) list as its second
# argument.

# When there are no more cards left to shuffle,
# return the accumulated list.

def shuffle([], acc) do
acc
end

# This is easier to understand if you look at it as a
# physical process. Split the deck at a random point.
# Put the part above the "split point" aside (leading), and
# take the first card (h) off the part below the split (t).
# That first card goes onto a new pile ([h | acc]).
# Now put together the part above the split and the
# part below the split (leading ++ t) and go through
# the process with the deck (which is now has one less card).
# This keeps going until you run out of cards to shuffle;
# at that point, all the cards will have gotten to the
# new pile, and that's your shuffled deck.

def shuffle(list, acc) do
Enum.split(list, :random.uniform(Enum.count(list)) - 1)
shuffle(leading ++ t, [h | acc])
end

@doc """
Create a deck of 52 tuples in the form [{"A", "Clubs"},
{"A", "Diamonds"}...]
"""
@spec make_deck(list, list) :: list(tuple)

def make_deck(values, suits) do
lc value inlist values, suit inlist suits, do:
{value, suit}
end

end

### game.ex

defmodule Game do

def start() do
deck = Cards.shuffle(Cards.make_deck(
#      ["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"],
[2, 3, 4, 5],
{hand1, hand2} = Enum.split(deck, trunc(Enum.count(deck) / 2))
player1 = spawn(Player, :start, [hand1])
player2 = spawn(Player, :start, [hand2])
play([player1, player2], :pre_battle, [], [], 0, [])
end

@doc """
Arguments are: list of player pids, state of game (atom),
number of players who have given cards, and pile in middle of table
"""
@spec play(list, atom, list, list, integer, list) :: nil

def play(players, state, cards1, cards2, n_received,
pile) do
[player1, player2] = players
case state do

# Ask players to give you cards. If there are no
# cards in the pile, it's a simple battle; otherwise
# it's a war.

:pre_battle ->
IO.puts("") # for spacing
case pile do
[] ->
IO.puts("Requesting 1 card from each player")
request_cards(players, 1)
_ ->
IO.puts("Requesting 3 cards from each player")
request_cards(players, 3)
end
play(players, :await_battle, cards1, cards2, n_received, pile)

# When both players have given you their card(s),
# you need to check them.

:await_battle when n_received == 2 ->
play(players, :check_cards, cards1, cards2, 0, pile)

# Otherwise, wait for players to send you card(s). Each time
# total number of players who have responded.

:await_battle ->
{:take, new_cards, from} ->
IO.puts("Got #{inspect(new_cards)} from #{inspect(from)}")
cond do
from == player1 -> play(players, state, new_cards,
from == player2 -> play(players, state, cards1,
end
end

# If both people have run out of cards, it's a draw.
# If one person is out of cards, the other player is the winner.
# Otherwise, evaluate the cards and prepare for next
# battle or war.

:check_cards ->
cond do
cards1 == [] and cards2 == [] ->
IO.puts("Draw")
endgame(players)
cards1 == [] ->
IO.puts("Player 2 wins")
endgame(players)
cards2 == [] ->
IO.puts("Player 1 wins")
endgame(players)
true ->
new_pile = evaluate(players, cards1, cards2, pile)
play(players, :pre_battle, [], [], 0, new_pile)
end
end
end

@spec evaluate(list, list, list, list) :: list

# Evaluate the cards from both players. If their
# values match, add them to the pile and.
# If they don't, the winner is told to pick up the cards
# (and whatever's in the pile), and the pile is cleared.
#
# Wait for players to respond before proceeding with the
# game. Otherwise, a player with an empty hand might be
# asked to give a card before picking up the cards she won.

defp evaluate(players, cards1, cards2, pile) do
[player1, player2] = players
v1 = card_value(hd(cards1))
v2 = card_value(hd(cards2))
IO.puts("Value of card 1 is #{v1}; value of card 2 is #{v2}")
new_pile = Enum.concat([pile, cards1, cards2])
IO.puts("Card pile is now #{inspect(new_pile)}")
cond do
v1 == v2 ->
IO.puts("Equal values; going to war.")
new_pile  # it's a war
v1 > v2 ->
IO.puts("Telling player 1 to pick up the cards")
send(player1, {:pick_up, new_pile, self()})
wait_for_pickup()
[]
v2 > v1 ->
IO.puts("Telling player 2 to pick up the cards")
send(player2, {:pick_up, new_pile, self()})
wait_for_pickup()
[]
end
end

# Wait for player to pick up cards
@spec wait_for_pickup() :: pid
def wait_for_pickup() do
{:got_cards, player} ->
IO.puts("Player #{inspect(player)} picked up cards.")
player
end
end

# Send each player a requst to send n cards
@spec request_cards(list, integer) :: nil

defp request_cards([p1, p2], n) do
send(p1, {:give, n, self()})
send(p2, {:give, n, self()})
end

# Send message to all players to exit their receive loop.
@spec endgame(list) :: nil

defp endgame(players) do
Enum.each(players, fn(x) -> send(x, :game_over) end)
end

# Return the value of a card; Aces are high.
@spec card_value(tuple) :: integer

defp card_value({value, _suit}) do
case value do
"A" -> 14
"K" -> 13
"Q" -> 12
"J" -> 11
_ -> value
end
end
end

### player.ex

defmodule Player do
def start(hand) do
play(hand)
end

#
# The player can either be told to give the dealer
# n cards (1 or 3), pick up cards (after having won a battle),
# or leave the game.

def play(hand) do
{:give, n, dealer} ->
{to_send, to_keep} = Enum.split(hand, n)
send(dealer, {:take, to_send, self()})
play(to_keep)
{:pick_up, cards, dealer} ->
new_hand = hand ++ cards
IO.puts("Player #{inspect(self)} has #{inspect(new_hand)}")
send(dealer, {:got_cards, self()})
play(new_hand)
:game_over -> IO.puts("Player #{inspect(self)} leaves.")
end
end
end

## Solution 10-1

Here is a suggested solution for Étude 10-1.

### stats.ex

defmodule Stats do
@moduledoc """
Functions for calculating basic statistics.

from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

@doc "Compute the mean of a list of numbers. Uses List.foldl/3"
@spec mean([number]) :: number

def mean(list) do
try do
List.foldl(list, 0, fn(x, acc) -> x + acc end) / Enum.count(list)
rescue
err -> err
end
end

@doc """
Compute the standard deviation of a list of numbers. Uses
List.foldl/3 with a tuple as an accumulator.
"""
@spec stdv([number]) :: number

def stdv(list) do
try do
n = Enum.count(list)
{sum, sum_sq} = List.foldl(list, {0,0}, fn(x, {acc, acc_sq}) ->
{acc + x, acc_sq + x * x} end)
:math.sqrt((n * sum_sq - sum * sum) / (n * (n - 1)))
rescue
err -> err
end
end

@doc "Recursively find the minimum entry in a list of numbers."

@spec minimum([number]) :: number

def minimum(list) do
try do
rescue
err -> err
end
end

# When there are no more numbers, return the result.

@spec minimum([number], number) :: number

defp minimum([], result) do
result
end

# If the current result is less than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.
# Note that you can use a variable assigned as part of a cons in a guard.

minimum(tail, result)
end

# Otherwise, the head of the list becomes the new minimum,
# and recursively look at the remainder of the list.

defp minimum([head | tail], _result) do
end

@doc "Recursively find the maximum entry in a list of numbers."
@spec maximum([number]) :: number

def maximum(list) do
try do
rescue
err -> err
end
end

# When there are no more numbers, return the result.

@spec maximum([number], number) :: number

defp maximum([], result) do
result
end

# If the current result is greater than the first item in the list,
# keep it as the result and recursively look at the remainder of the list.

maximum(tail, result)
end

# Otherwise, the head of the list becomes the new maximum,
# and recursively look at the remainder of the list.

defp maximum([head | tail], _result) do
end

# @doc "Find the range of a list of numbers as a list [min, max]."
@spec range([number]) :: [number]

def range(list) do
[minimum(list), maximum(list)]
end

end

## Solution 10-2

Here is a suggested solution for Étude 10-2.

### bank.ex

defmodule Bank do
@moduledoc """
Manipulate a "bank account" and log messages.

from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Create an account with a given balance, and repeatedly
"""
@spec account(number()) :: nil

def account(balance) do
input = IO.gets("D)eposit, W)ithdraw, B)alance, Q)uit: ")
action = String.upcase(String.first(input))
if (action != "Q") do
new_balance = transaction(action, balance)
account(new_balance)
end
end

@spec transaction(String.t(), number()) :: number()

def transaction(action, balance) do
case action do
"D" ->
amount = get_number("Amount to deposit: ")
cond do
amount >= 10000 ->
:error_logger.warning_msg("Large deposit $#{amount}\n") IO.puts("Your deposit of$#{amount} may be subject to hold.")
new_balance = balance + amount
IO.puts("Your new balance is $#{new_balance}") amount < 0 -> :error_logger.error_msg("Negative deposit$#{amount}\n")
IO.puts("Deposits may not be less than zero.")
new_balance = balance
amount >= 0 ->
:error_logger.info_msg("Successful deposit of $#{amount}\n") new_balance = balance + amount IO.puts("Your new balance is$#{new_balance}")
end
"W"->
amount = get_number("Amount to withdraw: ")
cond do
amount > balance ->
:error_logger.error_msg("Overdraw $#{amount} from$#{balance}\n")
IO.puts("You cannot withdraw more than your current balance of $#{balance}") new_balance = balance amount < 0 -> :error_logger.error_msg("Negative withdrawal amount$#{amount}\n")
IO.puts("Withdrawals may not be less than zero.")
new_balance = balance
amount >= 0 ->
:error_logger.info_msg("Successful withdrawal $#{amount}\n") new_balance = balance - amount IO.puts("Your new balance is$#{new_balance}")
end
"B" ->
:error_logger.info_msg("Balance inquiry $#{balance}\n") IO.puts("Your current balance is$#{balance}")
new_balance = balance
_ ->
IO.puts("Unknown command #{action}")
new_balance = balance
end
new_balance
end

@doc """
Present a prompt and get a number from the
user. Allow either integers or floats.
"""
@spec get_number(String.t()) :: number()

def get_number(prompt) do
input = IO.gets(prompt)
input_str = String.strip(input)
cond do
Regex.match?(~r/^[+-]?\d+$/, input_str) -> binary_to_integer(input_str) Regex.match?(~r/^[+-]?\d+\.\d+([eE][+-]?\d+)?$/, input_str) ->
binary_to_float(input_str)
true -> :error
end
end
end

## Solution 11-1

Here is a suggested solution for Étude 11-1.

### phone_ets.ex

defmodule Phone do
require Record;

Record.defrecord :phone, [number: "",
start_date: "1900-01-01",
start_time: "00:00:00",
end_date: "1900-01-01",
end_time: "00:00:00"]
end

defmodule PhoneEts do

require Phone;

@moduledoc """
Validate input using regular expressions.

from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

@doc """
Given a file name containing a CSV table of phone number,
start date/time, and end date/time, construct a corresponding
ETS table.
"""
@spec setup(String.t) :: atom

def setup(file_name) do
# delete table if it exists
case :ets.info(:call_table) do
:undefined -> false
_ -> :ets.delete(:call_table)
end

:ets.new(:call_table, [:named_table, :bag,
{:keypos, Phone.phone(:number) + 1}])
{result, input_file} = File.open(file_name)
if result == :ok do
end
end

#
# the ETS table.

cond do
data != :eof ->
[number, sdate, stime, edate, etime] =
String.split(String.strip(data), ",")
:ets.insert(:call_table, Phone.phone(number: number,
start_date: gregorianize(sdate, "-"),
start_time: gregorianize(stime, ":"),
end_date: gregorianize(edate, "-"),
end_time: gregorianize(etime, ":")))
true ->
:ok
end
end

# Convert a time or date string (given its delimiter, : or -)
# to a three-tuple of the constituent elements
@spec gregorianize(String.t, String.t) :: {integer, integer, integer}

defp gregorianize(str, delimiter) do
list_to_tuple(for item <- String.split(str, delimiter), do:
binary_to_integer(item))
end

@doc """
Summarize the number of minutes for a given phone number.
"""
@spec summary(String.t) :: list(tuple(String.t, integer))

def summary(phone_number) do
[calculate(phone_number)]
end

@doc """
Summarize the number of minutes for all phone numbers in the
data bases.
"""
@spec summary(String.t) :: list(tuple(String.t, integer))

def summary() do
summary(:ets.first(:call_table), [])
end

defp summary(key, acc) do
case key do
:"\$end_of_table" -> acc
_ -> summary(:ets.next(:call_table, key),
[calculate(key) | acc])
end
end

# Calculate total number of minutes used by given phone number.
# Returns tuple {phone_number, total}
@spec calculate(String.t) :: {String.t, integer}

defp calculate(phone_number) do
calls = :ets.lookup(:call_table, phone_number)
total = List.foldl(calls, 0, &call_minutes/2)
{phone_number, total}
end

# Helper function for calculate; adds the number of minutes
# for a given call to the accumulator
@spec call_minutes(Phone.t, integer) ::integer

defp call_minutes(phonecall, acc) do
c_start = :calendar.datetime_to_gregorian_seconds(
{Phone.phone(phonecall, :start_date),
Phone.phone(phonecall, :start_time)})
c_end = :calendar.datetime_to_gregorian_seconds(
{Phone.phone(phonecall, :end_date),
Phone.phone(phonecall, :end_time)})
div((c_end - c_start) + 59, 60) + acc
end
end

### generate_calls.ex

This is the program I used to generate the list of phone calls.

defmodule GenerateCalls do
@moduledoc """
Generate a random set of phone calls

from *Études for Elixir*, O'Reilly Media, Inc., 2014.
Copyright 2014 by J. David Eisenberg.
"""
@vsn 0.1

def make_call_list(n) do
now = :calendar.datetime_to_gregorian_seconds({{2014, 3, 10},
{9, 0, 0}})
numbers = [
{"213-555-0172", now},
{"301-555-0433", now},
{"415-555-7871", now},
{"650-555-3326", now},
{"729-555-8855", now},
{"838-555-1099", now},
{"946-555-9760", now}
]
call_list = make_call_list(n, numbers, [])
{result, output_file} = File.open("call_list.csv", [:write,
:utf8])
case result do
:ok -> write_item(output_file, call_list)
:error -> IO.puts("Error creating output file")
end
end

def make_call_list(0, _numbers, result) do
Enum.reverse(result)
end

def make_call_list(n, numbers, result) do
entry = :random.uniform(Enum.count(numbers))
{head, tail} = Enum.split(numbers, entry - 1)
{number, last_call} = hd(tail)
start_call = last_call + :random.uniform(120) + 20
duration = :random.uniform(180) + 40
end_call = start_call + duration
item = [number, format_date(start_call), format_time(start_call),
format_date(end_call), format_time(end_call)]
updated_numbers = head ++ [{number, end_call} | tl(tail)]
make_call_list(n - 1, updated_numbers, [item | result])
end

def write_item(output_file, []) do
File.close(output_file)
end

def write_item(output_file, [h | t]) do
[n, sd, st, ed, et] = h
IO.write(output_file, "#{n},#{sd},#{st},#{ed},#{et}\n")
write_item(output_file, t)
end

def format_date(g_seconds) do
{{y, m, d}, _time} =
:calendar.gregorian_seconds_to_datetime(g_seconds)
end

def format_time(g_seconds) do
{_date, {h, m, s}} =
:calendar.gregorian_seconds_to_datetime(g_seconds)
end

cond do
n < 10 -> "0" <> integer_to_binary(n)
true -> integer_to_binary(n)
end
end
end

## Solution 12-1

Here is a suggested solution for Étude 12-1.

### weather.ex

defmodule Weather do
use GenServer

# convenience method for startup
end

# callbacks for GenServer.Behaviour
def init([]) do
:inets.start()
{:ok, []}
end

def handle_call(request, _from, state) do
end

def handle_cast(_msg, state) do
IO.puts("Recently viewed: #{inspect(state)}")
end

def handle_info(_info, state) do
end

def terminate(_reason, _state) do
{:ok}
end

def code_change(_old_version, state, _extra) do
{:ok, state}
end

# internal functions

@doc """
Given a weather station name and the current server state,
get the weather data for that station, and add the station name
to the state (the list of recently viewed stations).
"""

def get_weather(station, state) do
url = "http://w1.weather.gov/xml/current_obs/" <> station
<> ".xml"
{status, data} = :httpc.request(to_char_list(url))
case status do
:error ->
new_state = state
:ok ->
{{_http, code, _message}, _attrs, xml_as_chars} = data
case code do
200 ->
xml = to_string(xml_as_chars)
(for item <- [:location, :observation_time_rfc822,
:weather, :temperature_string], do:
get_content(item, xml))}
# remember only the last 10 stations
new_state = [station | Enum.take(state, 9)]
_ ->
new_state = state
end
end
end

# Given an element name (as an atom) and an XML string,
# return a tuple with the element name and that element's text
# content.

@spec get_content(atom, String.t) :: {atom, String.t}

defp get_content(element_name, xml) do
{_, pattern} = Regex.compile(
"<#{element_name}>([^<]+)</#{atom_to_binary(element_name)}>")
result = Regex.run(pattern, xml)
case result do
[_all, match] -> {element_name, match}
nil -> {element_name, nil}
end
end

end

### weather_sup.ex

defmodule WeatherSup do
use Supervisor

# convenience method for startup
end

# supervisor callback
def init([]) do
child = [worker(Weather, [], [])]
supervise(child, [{:strategy, :one_for_one}, {:max_restarts, 1},
{:max_seconds, 5}])
end

# Internal functions (none here)

end

## Solution 12-2

Here is a suggested solution for Étude 12-2. Since the bulk of the code is identical to the code in the previous étude, the only code shown here is the revised -export list and the added functions.

### weather.ex

def report(station) do
:gen_server.call(Weather, station)
end

def recent() do
:gen_server.cast(__MODULE__, "")
end

## Solution 12-3

Here is a suggested solution for Étude 12-3. Since the bulk of the code is identical to the previous étude, the only code shown here is the added and revised code.

# convenience method for startup
end

def connect(other_node) do
:pong ->
IO.puts("Connected to server.")
:ok
:pang ->
IO.puts("Could not connect.")
:error
end
end

def report(station) do
:gen_server.call({:global, __MODULE__}, station)
end

def recent() do
result = :gen_server.call({:global, __MODULE__}, :recent)
IO.puts("Recently visited: #{inspect(result)}")
end

## Solution 12-4

Here is a suggested solution for Étude 12-4.

### chatroom.ex

defmodule Chatroom do
use GenServer

# convenience method for startup
end

# callbacks for GenServer.Behaviour
def init([]) do
{:ok, []}
end

# A login request gets a user name and node (server).
# Check that user name and server are unique; if they are,
# add that person's user name, server, and pid to the state.

def handle_call({:login, user, server}, from, state) do
{pid, _reference} = from
key = {user, server}
IO.puts("#{user} #{server} logging in from #{inspect(pid)}")
if List.keymember?(state, key, 0) do
new_state = state
else
new_state = [{key,pid} | state]
reply = {:ok, "#{user}@#{server} logged in."}
end
end

# If a person isn't logged in, note error;
# otherwise, delete person from the state.

def handle_call(:logout, from, state) do
{pid, _reference} = from
result = List.keyfind(state, pid, 1)
case result do
nil ->
reply = {:error, "Not logged in."}
new_state = state
{{user, server}, _pid} ->
new_state = List.keydelete(state, pid, 1)
reply = {:ok, "#{user}@#{server} logged out."}
end
end

# Send a message to all other participants.
# First, find sender's name and node. Then
# go through the list of participants and send each of them
# the message via GenServer.cast.
#
# Don't send a message to the originator (though if this were
# connected to a GUI, it would be useful to do so), as the
# originator wants to see the message she has typed in a text area
# as well as in the chat window.

def handle_call({:say, text}, from, state) do
{from_pid, _ref} = from

# get sender's name and server
person = List.keyfind(state, from_pid, 1)

case person do
{{from_user, from_server}, _pid} ->
Enum.each(state, fn(item) ->
{{_user, _server}, pid} = item
if pid != from_pid do
GenServer.cast(pid, {:message,
{from_user, from_server}, text})
end
end)
nil ->
reply = "Unknown sender pid #{inspect(from_pid)}"
end
end

# Return a list of all the users and their servers

def handle_call(:users, _from, state) do
reply = for {{name, server}, _pid} <- state, do:
{name,server}
end

# Get the profile of a person at a given server

def handle_call({:who, person, server}, _from, state) do
case List.keyfind(state, {person, server}, 0) do
{{_u, _s}, pid}  ->
nil ->
reply = "Cannot find #{person} #{server}"
end
end

# Catchall to handle any errant calls to the server.

def handle_call(item, from, state) do
IO.puts("Unknown #{inspect(item)} from #{inspect(from)}")
end

def handle_cast(_msg, state) do
end

def handle_info(_info, state) do
end

def terminate(_reason, _state) do
{:ok}
end

def code_change(_old_version, state, _extra) do
{:ok, state}
end
end

### person.ex

defmodule Person do
use GenServer
#  require Record

#  defmodule State do
#    Record.defrecord :state, [server: nil, profile: nil]
#  end

# convenience method for startup
end

# callbacks for GenServer.Behaviour
def init([chatroom_server]) do
{:ok, %{server: {Chatroom, chatroom_server},
profile: HashDict.new}}
end

# The server is asked to either:
# a) return the chat host name from the state,
# b) return the user profile
# c) update the user profile
# d) log a user in
# e) send a message to all people in chat room
# f) log a user out

def handle_call(:get_chat_server, _from, state) do
end

def handle_call(:get_profile, _from, state) do
end

def handle_call({:set_profile, key,value}, _from, state) do
new_profile = HashDict.put(state.profile, key, value)
end

def handle_call({:login, user_name}, _from, state) do
end

def handle_call({:say, text}, _from, state) do
end

def handle_call(:logout, _from, state) do
end

def handle_call(item, from, state) do
IO.puts("Unknown message #{inspect(item)} from #{inspect(from)}")
{:reply, "unknown msg to person", state}
end

def handle_cast({:message, {user, server}, text}, state) do
IO.puts("#{user} (#{server}) says: #{text}")
end

def handle_cast(_msg, state) do
end

def handle_info(_info, state) do
end

def terminate(_reason, _state) do
{:ok}
end

def code_change(_old_version, state, _extra) do
{:ok, state}
end

# Internal convenience functions
def chat_server() do
GenServer.call(__MODULE__, :get_chat_server)
end

# These requests can go straight to the chat server,
# because it doesn't care who they're from.

def users do
GenServer.call(chat_server, :users)
end

def who(user_name, user_node) do
GenServer.call(chat_server, {:who, user_name, user_node})
end

#
# Forward these requests to the person server,
# because they have to send the request to the chat room,
# not the shell!
#
end

end

def logout do
GenServer.call(__MODULE__, :logout)
end

def say(text) do
GenServer.call(__MODULE__, {:say, text})
end

#
# This request is local to the Person server.
#
def set_profile(key, value) do
GenServer.call(__MODULE__, {:set_profile, key, value})
end
end

## Solution 13-1

Here is a suggested solution for Étude 13-1.

### atomic_maker.ex

defmodule AtomicMaker do
defmacro create_functions(element_list) do
Enum.map element_list, fn {symbol, weight} ->
quote do
def unquote(symbol)() do
unquote(weight)
end
end
end
end
end

### atomic.ex

defmodule Atomic do
require AtomicMaker

AtomicMaker.create_functions([{:h, 1.008}, {:he, 4.003},
{:li, 6.94}, {:be, 9.012}, {:b, 10.81}, {:c, 12.011},
{:n, 14.007}, {:o, 15.999}, {:f, 18.998}, {:ne, 20.178},
{:na, 22.990}, {:mg, 24.305}, {:al, 26.981}, {:si, 28.085},
{:p, 30.974}, {:s, 32.06}, {:cl, 35.45}, {:ar, 39.948},
{:k, 39.098}, {:ca, 40.078}, {:sc, 44.956}, {:ti, 47.867},
{:v, 50.942}, {:cr, 51.996}, {:mn, 54.938}, {:fe, 55.845}])
end

## Solution 13-2

Here is a suggested solution for Étude 13-2.

### duration.ex

defmodule Duration do

quote do
t_sec = rem(unquote(s1) + unquote(s2), 60)
t_min = div(unquote(s1) + unquote(s2), 60)
{unquote(m1) + unquote(m2) + t_min, t_sec}
end
end

end

## Solution 13-3

Here is a suggested solution for Étude 13-3.

### duration.ex

defmodule Duration do

defmacro {m1,s1} + {m2, s2} do
quote do
t_sec = rem(unquote(s1) + unquote(s2), 60)
t_min = div(unquote(s1) + unquote(s2), 60)
{unquote(m1) + unquote(m2) + t_min, t_sec}
end
end

defmacro a + b do
quote do
unquote(a) + unquote(b)
end
end
end