-
n! = n × (n-1) × (n-2) × ... × 2 × 1
^
^
Compute this product FIRST
and call result A
-
= A × (n-2) × ... × 2 × 1
^
^
Compute this product NEXT
and call result A
-
= A × ... × 2 × 1
.
. (etc.)
.
-
= A × 1
-
= A
-
= n!
Recursive:
- Putting a main problem on hold while a
subproblem is solved.
- Converting the main problem into a
smaller problem of the same type.
- Problem shifts from n! to (n-1)!
Iterative:
- Progressively reducing the main
problem.
- Converting main problem into another
problem which gives the same result.
- Problem shifts from
n × (n-1) × (n-2) × ... × 2 × 1
to
A × (n-2) × ... × 2 × 1
where A = n × (n-1)
There are two values changing here (
A and
B), not just one as
in
factorial (
n).
So the process isn't
exactly factorial.
But we are always computing the product of
A and
B!, so call
the procedure
factorial-product:
-
(define factorial-product
(lambda (a b) ; compute a × b!
(if ________ ; When does the process stop?
________
________ )))
-
(define factorial-product
(lambda (a b) ; compute a × b!
(if (= b 0)
________ ; What do we return at the end?
________ )))
-
(define factorial-product
(lambda (a b) ; compute a × b!
(if (= b 0)
a
________ ; What do we compute at each non-final step? )))
-
(define factorial-product
(lambda (a b) ; compute a × b!
(if (= b 0)
a
(factorial-product (* a b) (- b 1)))))
-
factorial-product requires the argument a.
Q: Since it computes a × b!, what special case allows us
to compute b!?
-
A: a = 1.
So to compute the factorial of 4, call:
(factorial-product 1 4) => 24
Q: How can we define factorial with just one argument, as in
our recursive version?
-
A:
Make factorial-product into a "helper" procedure:
(define factorial
(lambda (n)
(factorial-product 1 n)))
(factorial 4) => 24
In this case factorial is the driver procedure, but
factorial-product does all the work, iteratively.
Recall the recursive procedure for making paper chains
(see
The Recursive Version in the menu).
Here is an
Iterative Version:
Note that the subproblem (linking
n-1 links onto a chain of length
1) is not a smaller
version of the main problem (making a chain of length
n).
Suppose you are asked to make a paper chain of length 4.
According to the process description,
Step 1a
directs to create a chain of
length 1:
Step 2a
directs to add 3 links onto the chain.
k = 3.
According to the process description,
Step 1b
directs to check if
k = 0. False.
Step 2b
directs to add one link to the current chain:
Then add two links to the chain.
k = 2.
According to the process description,
Step 1b
directs to check if
k = 0. False.
Step 2b
directs to add one link to the current chain:
Then add one link to the chain.
k = 1.
According to the process description,
Step 1b
directs to check if
k = 0. False.
Step 2b
directs to add one link to the current chain:
Now add zero links to the chain.
k = 0. Step 1b
stops the process.
To prove that
factorial-product maintains
the invariant, use mathematical induction:
- Objective: Prove that (factorial-product a b) terminates
with the value a × b! for any nonnegative
integer b.
- Base Case: When b = 0, factorial-product gives a.
Since a × b! = a × 0! = a, this is correct.
- Inductive Step:
Assume (factorial-product i j) gives i × j!
for j = b-1 (Inductive Hypothesis IH).
- Consider (factorial-product a b). It computes:
(factorial-product (* a b) (- b 1))
- Since this satisfies the IH, it computes:
(a × b) × (b-1)! =
-
a × (b × (b-1)!) =
-
a × b!
-
> (num-digits (fermat-number 7))
39
> (num-digits (fermat-number 8))
78
> (num-digits (fermat-number 9))
155
> (num-digits (fermat-number 10))
309
> (num-digits (fermat-number 11))
617
> (num-digits (fermat-number 12))
1234
> (num-digits (fermat-number 13))
2467
> (num-digits (fermat-number 14))
4933
Q: What is the pattern?
- A: The number of digits about doubles with each Fermat number
-
Recall that the 24th Fermat number is the first for which the primality is
not known
Q: About how many digits are in the 24th Fermat number?
-
You could try:
> (num-digits (fermat-number 24))
but you may run out of time or memory.
-
Strategy: Beginning with 1, test to see if the integers less than or equal to
n divide n, adding them to a sum if they do. For n = 6:
Integers to test Sum so far
---------------- ----------
1 2 3 4 5 6 0
-
2 3 4 5 6 1
-
3 4 5 6 3
-
4 5 6 6
-
5 6 6
-
6 6
-
12
-
At each step, there is a range low...n of
integers to test and an addend in which we accumulate the sum.
We will have the job of remembering the current low and addend values
done by an internal procedure sum-from-plus.
sum-from-plus will benefit from a helper predicate divides?
that tells whether a number evenly divides another.
Example:
Find | √90 (= 9.486832981) |
Solve | x2 = 90 or f(x) = x2 - 90 |
-
Iterative Approach: Improve a solution xn by computing xn+1 = xn - f(xn)/f'(xn)
-
Need f'(x) = 2x
-
Starting Point (initial guess): x0
= 9 (could be anything)
-
x1 = 9 - (92 - 90)/2 × 9 = 9.5
-
x2 = 9.5 - (9.52 - 90)/2 × 9.5 = 9.486842105.5
-
x3 = 9.486832981
In this case "good enough" is governed by the number of iterations.
We can use a similar approach to approximate square root using Newton's method.
Recall that we improve a solution
xn by computing
xn+1 = xn - f(xn)/f'(xn).
We will decide if an approximation is good enough by the number of
iterations we have tried.
So we will need to provide a
loop-limit that
the
good-enough? predicate will use:
When we try approximations we will need to keep track of the
iterations.
So we add the parameter
iterations
to
find-approximation-from and we increment it each time through:
To approximate square root of
n, we also provide
a
starting-point and
loop-limit:
> (sqrt 90)
9.486832980505138
> (approximate-square-root 90.0 9 0)
9
-
> (approximate-square-root 90.0 9 1)
9.5
-
> (approximate-square-root 90.0 9 2)
9.486842105263158
-
> (approximate-square-root 90.0 9 3)
9.486832980509526
-
> (approximate-square-root 90.0 9 4)
9.486832980505138
There are three Scheme language constructs that act as logical
operators.
Logical operators work on and return the truth values
true and
false. Summary:
- (not ... ) is a negation operator, returning the opposite of the
truth value of its argument.
- (or ... ) returns true if any of its arguments
does, false otherwise.
- (and ... ) returns true if all of its arguments do,
false otherwise
Recall that in Scheme the truth value
false is represented as
#f.
For the purposes of
not,
or, and
and, the truth value
true is represented as any non-
#f value (including
#t).
- (not expr) evaluates to #t if expr
evaluates to #f and to #f if expr
evaluates to non-#f
- (or expr1 expr2 ... exprn) returns the
first non-#f value of an argument, or #f if no
argument evaluates to a non-#f value
- (and expr1 expr2 ... exprn) returns
#f if any of its arguments evaluate to #f, and the value of the
last argument if all arguments evaluate to non-#f values.
Note that in the case of or and and, n can be
any nonnegative integer, that is, there can be as few as zero arguments.
-
> (not (= 3 4)) ⇒
-
> (not (= 3 4)) ⇒ #t
> (not (= 3 3)) ⇒
-
> (not (= 3 3)) ⇒ #f
> (or (= 3 4) (= 3 3)) ⇒
-
> (or (= 3 4) (= 3 3)) ⇒ #t
> (or (= 3 4) (= 4 5)) ⇒
-
> (or (= 3 4) (= 4 5)) ⇒ #f
> (or 13) ⇒
-
> (or 13) ⇒ 13
> (or) ⇒
-
> (or) ⇒ #f
> (and (= 3 3) (= 4 4)) ⇒
-
> (and (= 3 3) (= 4 4)) ⇒ #t
> (and (= 3 3) (= 4 4) (= 4 5)) ⇒
-
> (and (= 3 3) (= 4 4) (= 4 5)) ⇒ #f
> (and (= 3 3) (= 4 4) 13) ⇒
-
> (and (= 3 3) (= 4 4) 13) ⇒ 13
> (and (= 3 3) 13 (= 4 4)) ⇒
-
> (and (= 3 3) 13 (= 4 4)) ⇒ #t
> (and) ⇒
-
> (and) ⇒ #t
You can use
and and
or as short-hand for certain usages
of
if:
-
(and e1 e2) ≡ (if ____ ____ ____)
-
(and e1 e2) ≡ (if e1 e2 #f)
(or e1 e2) ≡ (if ____ ____ ____)
-
(or e1 e2) ≡ (if e1 e1 e2)