Note
|
You can learn more about working with logical flow and recursion in Chapter 3 of Erlang Programming, Chapter 3 of Programming Erlang, Sections 2.6 and 2.15 of Erlang and OTP in Action, and Chapters 3 and 5 of Learn You Some Erlang For Great Good!. |
Change the area/3
function that you wrote in
Étude 3-2 so that it uses a case
instead
of pattern matching. Use a guard on the function definition to ensure
that the numeric arguments are both greater than zero.
This is a typical exercise for recursion: finding the greatest common divisor (GCD) of two integers. Instead of giving Euclid’s method, we’ll do this with a method devised by Edsger W. Dijkstra. The neat part about Dijkstra’s method is that you don’t need to do any division to find the result. Here is the method.
To find the GCD of integers M
and N
:
-
If
M
andN
are equal, the result isM
. -
If
M
is greater thanN
, the result is the GCD ofM - N
andN
-
Otherwise
M
must be less thanN
, and the result is the GCD ofM
andN - M
.
Write a function gcd/2
in a module named dijkstra
that implements
the algorithm. This program is a good place to practice Elixir’s cond
construct.
Here is some sample output.
1> c(dijkstra).
{ok,dijkstra}
2> dijkstra:gcd(12, 8).
4
3> dijkstra:gcd(14, 21).
7
4> dijkstra:gcd(125, 46).
1
5> dijkstra:gcd(120, 36).
12
You can also use guards with multiple clauses to solve this étude; a solution for that approach is here. In general, use of multiple clauses with guards is considered more in the spirit of Erlang.
The next two exercises involve writing code to raise a number to an integer power (like 2.53 or 4-2) and finding the nth root of a number, such as the cube root of 1728 or the fifth root of 3.2.
These capabilities already exist with the math:pow/2
function, so you may
wonder why I’m asking you to re-invent the wheel. The reason is not to replace
math:pow/2
, but to experiment with recursion by writing functions that can be
expressed quite nicely that way.
Create a module named powers
(no relation to Francis Gary Powers), and
write a function named raise/2
which takes parameters X
and N
and
returns the value of XN.
Here’s the information you need to know to write the function:
-
Any number to the power 0 equals 1.
-
Any number to the power 1 is that number itself — that stops the recursion.
-
When
N
is positive,XN
is equal toX
timesX(N-1)
— there’s your recursion. -
When
N
is negative,XN
is equal to1.0 / X-N
Note that this function is not tail recursive. Here is some sample output.
1> c(powers).
{ok,powers}
2> powers:raise(5, 1).
5
3> powers:raise(2, 3).
8
4> powers:raise(1.2, 3).
1.728
5> powers:raise(2, 0).
1
6> powers:raise(2, -3).
0.125
Practice the "accumulator trick".
Rewrite the raise/2
function for N
greater than zero so that it
calls a helper function raise/3
. This new function has X
, N
, and
an Accumulator
as its parameters.
Your raise/2
function will return 1 when N
is equal to 0,
and will return 1.0 / raise(X, -N)
when N is less than zero.
When N
is greater than zero, raise/2
will
call raise/3
with arguments X
, N
, and 1 as the Accumulator.
The raise/3
function will return the
Accumulator
when N
equals 0 (this will stop the recursion).
Otherwise, recursively call raise/3
with X
, N - 1
,
and X
times the Accumulator
as its arguments.
The raise/3
function is tail recursive.
In this exercise, you will add a function nth_root/2
to the
powers
module. This new function finds the
nth root of a number, where n is an integer.
For example, nth_root(36, 2)
will calculate
the square root of 36, and nth_root(1.728, 3)
will return the cube
root of 1.728.
The algorithm used here is the Newton-Raphson method for calculating roots. (See http://en.wikipedia.org/wiki/Newton%27s_method for details).
You will need a helper function nth_root/3
, whose parameters
are X
, N
, and an approximation to the result, which we
will call A
. nth_root/3
works as follows:
-
Calculate
F
asAN - X
-
Calculate
Fprime
asN * AN-1
-
Calculate your next approximation (call it
Next
) asA - F / Fprime
-
Calculate the change in value (call it
Change
) as the absolute value ofNext - A
-
If the
Change
is less than some limit (say, 1.0e-8), stop the recursion and returnNext
; that’s as close to the root as you are going to get. -
Otherwise, call the
nth_root/3
function again withX
,N
, andNext
as its arguments.
For your first approximation, use X / 2.0
. Thus, your nth_root/2
function
will simply be this:
nth_root(X, N) -> nth_root(X, N, X / 2.0)
Use io:format
to show each new approximation as you
calculate it. Here is some sample output.
1> c(powers).
{ok,powers}
2> powers:nth_root(27, 3).
Current guess is 13.5
Current guess is 9.049382716049383
Current guess is 6.142823558176272
Current guess is 4.333725614685509
Current guess is 3.3683535855517652
Current guess is 3.038813723595138
Current guess is 3.0004936436555805
Current guess is 3.000000081210202
Current guess is 3.000000000000002
3.0