1
- # fibonacci.py
2
1
"""
3
2
Calculates the Fibonacci sequence using iteration, recursion, memoization,
4
3
and a simplified form of Binet's formula
9
8
NOTE 2: the Binet's formula function is much more limited in the size of inputs
10
9
that it can handle due to the size limitations of Python floats
11
10
12
- RESULTS: (n = 20)
13
- fib_iterative runtime: 0.0055 ms
14
- fib_recursive runtime: 6.5627 ms
15
- fib_memoization runtime: 0.0107 ms
16
- fib_binet runtime: 0.0174 ms
11
+ See benchmark numbers in __main__ for performance comparisons/
12
+ https://en.wikipedia.org/wiki/Fibonacci_number for more information
17
13
"""
18
14
19
15
import functools
16
+ from collections .abc import Iterator
20
17
from math import sqrt
21
18
from time import time
22
19
@@ -35,6 +32,31 @@ def time_func(func, *args, **kwargs):
35
32
return output
36
33
37
34
35
+ def fib_iterative_yield (n : int ) -> Iterator [int ]:
36
+ """
37
+ Calculates the first n (1-indexed) Fibonacci numbers using iteration with yield
38
+ >>> list(fib_iterative_yield(0))
39
+ [0]
40
+ >>> tuple(fib_iterative_yield(1))
41
+ (0, 1)
42
+ >>> tuple(fib_iterative_yield(5))
43
+ (0, 1, 1, 2, 3, 5)
44
+ >>> tuple(fib_iterative_yield(10))
45
+ (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
46
+ >>> tuple(fib_iterative_yield(-1))
47
+ Traceback (most recent call last):
48
+ ...
49
+ ValueError: n is negative
50
+ """
51
+ if n < 0 :
52
+ raise ValueError ("n is negative" )
53
+ a , b = 0 , 1
54
+ yield a
55
+ for _ in range (n ):
56
+ yield b
57
+ a , b = b , a + b
58
+
59
+
38
60
def fib_iterative (n : int ) -> list [int ]:
39
61
"""
40
62
Calculates the first n (0-indexed) Fibonacci numbers using iteration
@@ -49,10 +71,10 @@ def fib_iterative(n: int) -> list[int]:
49
71
>>> fib_iterative(-1)
50
72
Traceback (most recent call last):
51
73
...
52
- Exception : n is negative
74
+ ValueError : n is negative
53
75
"""
54
76
if n < 0 :
55
- raise Exception ("n is negative" )
77
+ raise ValueError ("n is negative" )
56
78
if n == 0 :
57
79
return [0 ]
58
80
fib = [0 , 1 ]
@@ -75,7 +97,7 @@ def fib_recursive(n: int) -> list[int]:
75
97
>>> fib_iterative(-1)
76
98
Traceback (most recent call last):
77
99
...
78
- Exception : n is negative
100
+ ValueError : n is negative
79
101
"""
80
102
81
103
def fib_recursive_term (i : int ) -> int :
@@ -95,13 +117,13 @@ def fib_recursive_term(i: int) -> int:
95
117
Exception: n is negative
96
118
"""
97
119
if i < 0 :
98
- raise Exception ("n is negative" )
120
+ raise ValueError ("n is negative" )
99
121
if i < 2 :
100
122
return i
101
123
return fib_recursive_term (i - 1 ) + fib_recursive_term (i - 2 )
102
124
103
125
if n < 0 :
104
- raise Exception ("n is negative" )
126
+ raise ValueError ("n is negative" )
105
127
return [fib_recursive_term (i ) for i in range (n + 1 )]
106
128
107
129
@@ -119,7 +141,7 @@ def fib_recursive_cached(n: int) -> list[int]:
119
141
>>> fib_iterative(-1)
120
142
Traceback (most recent call last):
121
143
...
122
- Exception : n is negative
144
+ ValueError : n is negative
123
145
"""
124
146
125
147
@functools .cache
@@ -128,13 +150,13 @@ def fib_recursive_term(i: int) -> int:
128
150
Calculates the i-th (0-indexed) Fibonacci number using recursion
129
151
"""
130
152
if i < 0 :
131
- raise Exception ("n is negative" )
153
+ raise ValueError ("n is negative" )
132
154
if i < 2 :
133
155
return i
134
156
return fib_recursive_term (i - 1 ) + fib_recursive_term (i - 2 )
135
157
136
158
if n < 0 :
137
- raise Exception ("n is negative" )
159
+ raise ValueError ("n is negative" )
138
160
return [fib_recursive_term (i ) for i in range (n + 1 )]
139
161
140
162
@@ -152,10 +174,10 @@ def fib_memoization(n: int) -> list[int]:
152
174
>>> fib_iterative(-1)
153
175
Traceback (most recent call last):
154
176
...
155
- Exception : n is negative
177
+ ValueError : n is negative
156
178
"""
157
179
if n < 0 :
158
- raise Exception ("n is negative" )
180
+ raise ValueError ("n is negative" )
159
181
# Cache must be outside recursuive function
160
182
# other it will reset every time it calls itself.
161
183
cache : dict [int , int ] = {0 : 0 , 1 : 1 , 2 : 1 } # Prefilled cache
@@ -193,29 +215,30 @@ def fib_binet(n: int) -> list[int]:
193
215
>>> fib_binet(-1)
194
216
Traceback (most recent call last):
195
217
...
196
- Exception : n is negative
218
+ ValueError : n is negative
197
219
>>> fib_binet(1475)
198
220
Traceback (most recent call last):
199
221
...
200
- Exception : n is too large
222
+ ValueError : n is too large
201
223
"""
202
224
if n < 0 :
203
- raise Exception ("n is negative" )
225
+ raise ValueError ("n is negative" )
204
226
if n >= 1475 :
205
- raise Exception ("n is too large" )
227
+ raise ValueError ("n is too large" )
206
228
sqrt_5 = sqrt (5 )
207
229
phi = (1 + sqrt_5 ) / 2
208
230
return [round (phi ** i / sqrt_5 ) for i in range (n + 1 )]
209
231
210
232
211
233
if __name__ == "__main__" :
212
- import doctest
213
-
214
- doctest .testmod ()
234
+ from doctest import testmod
215
235
236
+ testmod ()
237
+ # Time on an M1 MacBook Pro -- Fastest to slowest
216
238
num = 30
217
- time_func (fib_iterative , num )
218
- time_func (fib_recursive , num ) # Around 3s runtime
219
- time_func (fib_recursive_cached , num ) # Around 0ms runtime
220
- time_func (fib_memoization , num )
221
- time_func (fib_binet , num )
239
+ time_func (fib_iterative_yield , num ) # 0.0012 ms
240
+ time_func (fib_iterative , num ) # 0.0031 ms
241
+ time_func (fib_binet , num ) # 0.0062 ms
242
+ time_func (fib_memoization , num ) # 0.0100 ms
243
+ time_func (fib_recursive_cached , num ) # 0.0153 ms
244
+ time_func (fib_recursive , num ) # 257.0910 ms
0 commit comments