fib - Comp215 - Rice University

<.
P
+
@:
#/
!"
-
0
9
,
54
3%#52
2
)4
%
E@M<I
K
@
J
Comp215: More Recursion
3%#52
)4
9
xkcd.com/1557
Copyright Ⓒ 2015, Dan S. Wallach. All rights reserved.
<.
P
+
@:
#/
!"
-
0
%2
,
Dan S. Wallach (Rice University)
54
E @ M <IJ
@K
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2)
fib(1)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2) fib(1)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2) fib(1)
fib(1) fib(0)
fib(2)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2) fib(1)
fib(1) fib(0)
fib(2)
fib(1)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
fib(2)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
fib(2)
fib(1)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
fib(2) fib(1)
fib(1) fib(0)
Traditional, simple recursion
class Fibonacci {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return fib(n-1) + fib(n-2);
}
}
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2) fib(1)
fib(1) fib(0)
fib(2) fib(1)
fib(1) fib(0)
fib(1) fib(0)
Runtime is exponential in n, clearly not the way to go.
Should we use mutation?
class Fibonacci2 {
public static int fib(int n) {
if (n == 0 || n == 1) return 1;
int p1 = 1;
int p2 = 1;
for (int i = 2; i <= n; i++) {
int oldP1 = p1;
p1 = p2;
p2 = oldP1 + p2;
}
return p2;
}
}
Runtime is O(n), so it’s efficient, but is it correct?
No mutation!
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
helper(2,3,3,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
helper(2,3,3,5)
helper(3,5,4,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
helper(2,3,3,5)
helper(3,5,4,5)
helper(5,8,5,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
helper(2,3,3,5)
helper(3,5,4,5)
helper(5,8,5,5)
Recursive Fibonacci, linear runtime
class Fibonacci3 {
public static int fib(int n) {
if(n==0 || n==1) return 1;
return helper(1, 1, 1, n);
}
private static int
helper(int p1, int p2, int current, int goal) {
if(current == goal) return p2;
return helper(p2, p1+p2, current+1, goal);
}
}
fib(5)
helper(1,1,1,5)
helper(1,2,2,5)
helper(2,3,3,5)
helper(3,5,4,5)
helper(5,8,5,5)
This is tail recursion: helper returns a call to itself,
no need to come back and do more computation.
Why tail-recursion instead of a loop?
Easier to convince yourself it’s correct
Awkward juggling of variables as you change them
Versus all values marching in lock step
In functional programming languages not called Java, this is efficient
“Tail call optimization” works well, but it’s not in the Java virtual machine
After 1000’s to 10000’s of Java stack frames, you’ll run out of memory
Footnote: Java9 might have tail call optimization. IBM’s J9 JVM apparently has it.
Many languages that run on the JVM (Scala, Ceylon, Kotlin, etc.) have it.
So what should we do in Java?
First, write it in a dumb-but-correct way. Build test cases.
Next, if necessary, rewrite with tail-recursion. Verify test cases.
Next, if necessary, rewrite as a loop. Verify test cases.
fold-right, revisited
Fold-right: 1 + (2 + (3 + (4 + (5 + (6 + (7 + 8))))))
class GList<T> {
public T foldr(BinaryOperator<T> operator, T accumulator) {
return operator.apply(value,
tailList.foldr(operator, accumulator));
}
}
class Empty<T> extends GList<T> {
public T foldl(BinaryOperator<T> operator, T accumulator) {
return accumulator;
}
}
fold-right, revisited
Fold-right: 1 + (2 + (3 + (4 + (5 + (6 + (7 + 8))))))
class GList<T> {
public T foldr(BinaryOperator<T> operator, T accumulator) {
return operator.apply(value,
tailList.foldr(operator, accumulator));
}
}
Not tail-recursive. apply can’t happen until foldr returns.
class Empty<T> extends GList<T> {
public T foldl(BinaryOperator<T> operator, T accumulator) {
return accumulator;
}
}
fold-left, revisited
Fold-left: ((((((1+2) + 3) + 4) + 5) + 6) + 7) + 8
class GList<T> {
public T foldl(BinaryOperator<T> operator, T accumulator) {
return tailList.foldl(operator,
operator.apply(accumulator, value));
}
}
class Empty<T> extends GList<T> {
public T foldr(BinaryOperator<T> operator, T accumulator) {
return accumulator;
}
}
fold-left, revisited
Fold-left: ((((((1+2) + 3) + 4) + 5) + 6) + 7) + 8
class GList<T> {
public T foldl(BinaryOperator<T> operator, T accumulator) {
return tailList.foldl(operator,
operator.apply(accumulator, value));
}
}
Tail-recursive! apply happens first, then recursion.
class Empty<T> extends GList<T> {
public T foldr(BinaryOperator<T> operator, T accumulator) {
return accumulator;
}
}
Solving the problem in Java
foldl can be rewritten as a loop; foldr cannot.
So, yes, we’ll use mutation, but only to do what the tail-recursive code did.
And we’ll verify our test cases all along.
(And clever programmers will use foldl when possible, since we’ll promise
that it will be “fast”.)
Live coding
Rewriting foldl to use iteration
Rewriting other list methods to use foldl
Reversing a list with a helper function
Generalizing foldl from using a binary operator to a binary function
Reversing a list with foldl