chapter 3 programming with recursion - uppsala · pdf filech.3: programming with recursion...
TRANSCRIPT
Ch.3: Programming with Recursion Plan
Chapter 3
Programming with Recursion
1. Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2
2. Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5
3. Construction methodology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.13
4. Forms of recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.15
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.1
Ch.3: Programming with Recursion 3.1. Examples
3.1. Examples
Factorial (revisited from Section 2.13)
Program (fact.sml)
fun fact n =
if n < 0 then error "fact: negative argument"
else if n = 0 then 1
else n ∗ fact (n−1)
Useless test of the error case at each recursive call!
Hence we introduce an auxiliary function,
and can then use pattern matching in its declaration:
fun factAux 0 = 1
| factAux n = n ∗ factAux (n−1)
fun fact1 n =
if n < 0 then error "fact1: negative argument"
else factAux n
In fact1: pre-condition verification (defensive programming)
In factAux: no pre-condition verification
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.2
Ch.3: Programming with Recursion 3.1. Examples
Exponentiation
Specification
function expo x n
TYPE: real → int → real
PRE: n ≥ 0
POST: xn
Construction
Error case: n < 0: produce an error message
Base case: n = 0 : return 1
General case: n > 0 :
return xn = x ∗ xn−1 = x ∗ expo x (n−1)
Program (expo.sml)
fun expoAux x 0 = 1.0
| expoAux x n = x ∗ expoAux x (n−1)
fun expo x n =
if n < 0 then error "expo: negative argument"
else expoAux x n
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.3
Ch.3: Programming with Recursion 3.1. Examples
Triangle
Specification
function triangle a b
TYPE: int → int → int
PRE: (none)
POST:∑
a ≤ i ≤ b
i
Construction
Base case: a > b : return 0
General case: a ≤ b :
return∑
a≤i≤b
i = a +∑
a+1≤i≤b
i = a + triangle (a+1) b
Program (triangle.sml)
fun triangle a b =
if a > b then 0
else a + triangle (a+1) b
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.4
Ch.3: Programming with Recursion 3.2. Correctness
3.2. Correctness
How do we know that a recursive program computes what we want?
Lets try a very simple program.
fun f n =
if n = 0 then 0
else 1 + f(n-1)
What does f compute?
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.5
Ch.3: Programming with Recursion 3.2. Correctness
Correctness (cont)
What does this function compute?
fun f n =
if n = 0 then 0
else 1 + f(n-1)
Answer:
f n = n, if n>=0
Seems reasonable, but how do we prove it?
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.6
Ch.3: Programming with Recursion 3.2. Correctness
Proof by induction
For all n >= 0, f(n) = n.
Base case: n = 0.
f(n) = 0 = n follows immediately.
Induction step:
Hypothesis: f(k) = k, for all k such that 0 <= k < n
We have
f(n) = 1 + f(n − 1), by definition
1 + f(n − 1) = 1 + (n − 1), by induction hypothesis
1 + (n − 1) = n, by algebraic transformations
Thus f(n) = n
It follows that f(n) = n, for all n >= 0.
Question: What can we say about f(−1)?
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.7
Ch.3: Programming with Recursion 3.2. Correctness
Another example, slightly harder
What does this function compute?
fun g(0) = 0
| g(n) = n + g(n)
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.8
Ch.3: Programming with Recursion 3.2. Correctness
Another example (cont)
What does this function compute?
fun g(0) = 0
| g(n) = n + g(n)
Answer: g(n) = (n ∗ n + n)/2
Proof by induction.
For all n >= 0, g(n) = (n ∗ n + n)/2
Base case:
n = 0.
g(n) = (n ∗ n + n)/2 by definition
(n ∗ n + n)/2 = (0 ∗ 0 + 0)/2 = 0
Induction step
Hypothesis: g(k) = (k ∗ k + k)/2, for all k such that 0 <= k < n
We have
g(n) = n + g(n − 1), by definition
= n + ((n − 1) ∗ (n − 1) + (n − 1))/2, by induction hypothesis
= n + (n ∗ n − 2 ∗ n + 1 + n − 1)/2, by algebraic transformations
= n + (n ∗ n − n)/2 − n, simplification
= (n ∗ n + n)/2
Thus g(n) = (n ∗ n + n)/2
It follows that g(n) = (n ∗ n + n)/2, for all n >= 0.
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.9
Ch.3: Programming with Recursion 3.2. Correctness
If you think these examples where too simple
Exercises:
• Prove that the factorial given earlier produces the intended results.
• Prove the same for the gcd function.
What do the functions below compute? Make an educated guess (try on
an SML system if you like) and show by induction that your guess was
correct.
fun h(n) = if n = 0 then 0 else h(n-1)+2*n-1;
fun m(a,b) = if a = 0 then 0 else b+m(a-1,b);
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.10
Ch.3: Programming with Recursion 3.2. Correctness
Correctness of functional programs
Suppose we have a recursive function
fun f(x) = ... f(..) ...
and we want to show that it satisfies some property P (x, f(x))
Solution:
• Show that the function terminates (for all values of x that we are
interested in)
• Assume that all recursive calls satisfy the property P (. . . , f(. . .)).
Show P (x, f(x)).
Then we have shown that P (x, f(x)) holds for all values of x we are
interested in. (This is just an induction proof in disguise.)
(By the way, showing that if an answer is returned it will be correct is
also known as partial correctness.)
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.11
Ch.3: Programming with Recursion 3.2. Correctness
Example (factorial):
fun fac(x) =
if x = 0
then 1
else x*fac(x-1)
Assume that fac(x) terminates for all x >= 0. Show that
P (x, fac(x)) <=> fac(x) = 1 ∗ 2 ∗ . . . ∗ x holds.
Assuming property holds for recursive calls...
By the definition of the function fac we have
fac(x) = if x = 1 then 0 else x ∗ (1 ∗ 2 ∗ . . . ∗ (x − 1))
For x = 0 the property is obvious. For x <> 0 we prove the property by
algebraic manipulations.
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.12
Ch.3: Programming with Recursion 3.3. Construction methodology
3.3. Construction methodology
Objective
Construction of an SML program computing the function:
f(x) : D → R
given its specification S
Methodology
1. Case analysis
Recognize
• base case(s), and
• recursive case(s).
2. Partial correctness
Check that the base case returns correct result.
Show that the recursive case gives correct result, assuming that all
recursive calls give correct result.
3. Termination
Choose a variant.
A variant is an integer-valued expression on the input parameters
together with a lower bound b so that
• for any recursive call, the variant is ≥ b, and
• for any recursive call, the variant of the recursive call is strictly less
than the current call.
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.13
Ch.3: Programming with Recursion 3.4. Tips and hints
3.4. Tips and hints
Superfluous or overlapping cases
Try to keep your code short and compact. Avoid redundant cases!
fun fact n =
if n = 0 then 1
else if n = 1 then 1
else n ∗ fact (n−1)
Variants
Variants are usually simple; an integer given by a parameter or the size
of some input data. But watch out for the difficult cases!
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.14
Ch.3: Programming with Recursion 3.5. Forms of recursion
3.5. Forms of recursion
Up to now:
• One recursive call
• Some variant is decremented by one
That is: simple recursion
(construction process by simple induction)
Forms of recursion
• Simple recursion
• Complete recursion
• Multiple recursion
• Mutual recursion
• Nested recursion
• Recursion on a generalised problem
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.15
Ch.3: Programming with Recursion 3.5. Forms of recursion
Complete recursion
Example 1: integer division (quotient and remainder)
Specification
function intDiv a b
TYPE: int → int → (int ∗ int)
PRE: a ≥ 0 and b > 0
POST: (q,r) such that a = q ∗ b + r and 0 ≤ r < b
Construction
Variant: a
Error case: a < 0 or b ≤ 0 : produce an error message
Base case: a < b : since a = 0 ∗ b + a, return (0,a)
This covers more than the minimal value of a (namely 0)!
General case: a ≥ b :
since a = q ∗ b + r iff (a−b) = (q−1) ∗ b + r,
the call intDiv (a−b) b will give q−1 and r
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.16
Ch.3: Programming with Recursion 3.5. Forms of recursion
Program (intDiv.sml)
fun intDivAux a b =
if a < b then (0,a)
else let val (q1,r1) = intDivAux (a−b) b
in (q1+1,r1)
end
fun intDiv a b =
if a < 0 orelse b <= 0 then error "intDiv: invalid argument"
else intDivAux a b
Necessity of the induction hypothesis not only for a−1,
but actually for all values less than a: complete induction!
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.17
Ch.3: Programming with Recursion 3.5. Forms of recursion
Example 2: exponentiation (revisited from Section 3.1)
Specification
function fastExpo x n
TYPE: real → int → real
PRE: n ≥ 0
POST: xn
Construction
Variant: n
Error case: n < 0: produce an error message
Base case: n = 0 : return 1
General case: n > 0 :
if n is even, then return xn div 2 ∗ xn div 2
otherwise, return x ∗ xn div 2 ∗ xn div 2
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.18
Ch.3: Programming with Recursion 3.5. Forms of recursion
Program (expo.sml)
fun fastExpo x n =
let
fun fastExpoAux x 0 = 1.0
| fastExpoAux x n =
let val r = fastExpoAux x (n div 2)
in if even n then r ∗ r
else x ∗ r ∗ r
end
in
if n < 0 then error "fastExpo: negative argument"
else fastExpoAux x n
end
Complete recursion,
but the size of the input is divided by 2 each time!
Complexity
Let C(n) be the number of multiplications made
(in the worst case) by fastExpo:
C(0) = 0
C(n) = C(n div 2) + 2 for n > 0
One can show that C(n) = O(log n)
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.19
Ch.3: Programming with Recursion 3.5. Forms of recursion
Multiple recursion
Example: the Fibonacci numbers
Definition
fib (0) = 1
fib (1) = 1
fib (n) = fib (n−1) + fib (n−2) for n > 1
Specification
function fib n
TYPE: int → int
PRE: n ≥ 0
POST: fib (n)
Program (fib.sml)
Variant: n
fun fib 0 = 1
| fib 1 = 1
| fib n = fib (n−1) + fib (n−2)
• Double recursion
• Inefficient: multiple recomputations of the same values!
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.20
Ch.3: Programming with Recursion 3.5. Forms of recursion
Mutual recursion
Example: recognising even integers and odd integers
Specification
function even n
TYPE: int → bool
PRE: n ≥ 0
POST: true if n is even
false otherwise
function odd n
TYPE: int → bool
PRE: n ≥ 0
POST: true if n is odd
false otherwise
Program (even.sml)
Variant: n
fun even 0 = true
| even n = odd (n−1)
and odd 0 = false
| odd n = even (n−1)
• Simultaneous declaration of the functions
• Global correctness reasoning
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.21
Ch.3: Programming with Recursion 3.5. Forms of recursion
Nested recursion
and lexicographic order
Example 1: the Ackermann function
Definition
For m, n ≥ 0:
acker (0,m) = m+1
acker (n,0) = acker (n−1, 1) for n > 0
acker (n,m) = acker (n−1, acker (n, m−1)) for n, m > 0
Program (acker.sml)
Variant: the pair (n,m)
fun acker 0 m = m+1
| acker n 0 = acker (n−1) 1
| acker n m = acker (n−1) (acker n (m−1))
where
(n′, m′) <lex (n, m) iff n′ < n or (n′ = n and m′ < m)
This is called a lexicographic order
• The function acker always terminates
• It is not a primitive-recursive function,
so it is impossible to estimate an upper bound for acker n m
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.22
Ch.3: Programming with Recursion 3.5. Forms of recursion
Example 2: Graham’s number, the “largest” number
Definition
Operator ↑n (invented by Donald Knuth):
a ↑1 b = ab
a ↑n b = a ↑n−1 (b ↑n−1 b) for n > 1
Program (graham.sml)
Variant: n
fun opKnuth 1 a b = Math.pow (a,b)
| opKnuth n a b = opKnuth (n−1) a (opKnuth (n−1) b b)
- opKnuth 2 3.0 3.0 ;
val it = 7.62559748499E12 : real
- opKnuth 3 3.0 3.0 ;
! Uncaught exception: Overflow
Graham’s number is opKnuth 63 3.0 3.0
It is in the Guiness Book of Records!
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.23
Ch.3: Programming with Recursion 3.5. Forms of recursion
Recursion on a generalised problem
Example: recognising prime numbers
Specification
function prime n
TYPE: int → bool
PRE: n > 0
POST: true if n is a prime number
false otherwise
Construction
It is impossible to determine whether n is prime
via the reply to the question “is n − 1 prime”?
It seems impossible to directly construct a recursive program
We thus need to find another function:
• that is more general than prime, in the sense
that prime is a particular case of this function
• for which a recursive program can be constructed
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.24
Ch.3: Programming with Recursion 3.5. Forms of recursion
Specification of the generalised function
function divisors n low up
TYPE: int → int → int → bool
PRE: n, low, up ≥ 1
POST: true if n has no divisors in {low, . . . , up}false otherwise
Construction of a program for the generalised function
Variant: up − low + 1, which is the size of {low, . . . , up}
Base case: low > up :
return true because the set {low, . . . , up} is empty
General case: low ≤ up :
if n is divisible by low, then return false
otherwise, return whether n has a divisor in {low+1, . . . , up}
Construction of a program for the original function
The function prime is a particular case of the function divisors,
namely when low is 2 and up is n−1
One can also take up as ⌊√n⌋, and this is more efficient
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.25
Ch.3: Programming with Recursion 3.5. Forms of recursion
Program (prime.sml)
fun divisors n low up =
low > up orelse
(n mod low) <> 0 andalso divisors n (low+1) up
fun prime n =
if n <= 0 then error "prime: nonpositive argument"
else if n = 1 then false
else divisors n 2 (floor (Math.sqrt (real n)))
• The function divisors may be useful for other problems
• The discovery of divisors requires imagination and creativity
• There are some standard methods of generalising problems:
– let the recursive function take more parameters, so that the problem
we want to solve is a special case
– let the recursive function return more information than is required in
the problem statement
– tupling generalisation: replace a parameter by a list of parameters of
the same type
• Sometimes the only way to solve the problem at hand is to consider a
more general problem.
• Often, a generalized problem may be more efficient regarding time
and/or space consumption.
Exercises:
1. Which standard method of generalization did we apply in the
implementation of the primes function?
2. (Harder) Implement a function that computes fib(n) in time
proportional to n.
Hint: You will need to define a function that solves a more general
problem.
Sven-Olof Nystrom/IT Dept/Uppsala University FP 3.26