Skip to content

add a yield method to fibonaci #10826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 29, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 51 additions & 28 deletions maths/fibonacci.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# fibonacci.py
"""
Calculates the Fibonacci sequence using iteration, recursion, memoization,
and a simplified form of Binet's formula
Expand All @@ -9,14 +8,12 @@
NOTE 2: the Binet's formula function is much more limited in the size of inputs
that it can handle due to the size limitations of Python floats

RESULTS: (n = 20)
fib_iterative runtime: 0.0055 ms
fib_recursive runtime: 6.5627 ms
fib_memoization runtime: 0.0107 ms
fib_binet runtime: 0.0174 ms
See benchmark numbers in __main__ for performance comparisons/
https://en.wikipedia.org/wiki/Fibonacci_number for more information
"""

import functools
from collections.abc import Iterator
from math import sqrt
from time import time

Expand All @@ -35,6 +32,31 @@ def time_func(func, *args, **kwargs):
return output


def fib_iterative_yield(n: int) -> Iterator[int]:
"""
Calculates the first n (1-indexed) Fibonacci numbers using iteration with yield
>>> list(fib_iterative_yield(0))
[0]
>>> tuple(fib_iterative_yield(1))
(0, 1)
>>> tuple(fib_iterative_yield(5))
(0, 1, 1, 2, 3, 5)
>>> tuple(fib_iterative_yield(10))
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
>>> tuple(fib_iterative_yield(-1))
Traceback (most recent call last):
...
ValueError: n is negative
"""
if n < 0:
raise ValueError("n is negative")
a, b = 0, 1
yield a
for _ in range(n):
yield b
a, b = b, a + b


def fib_iterative(n: int) -> list[int]:
"""
Calculates the first n (0-indexed) Fibonacci numbers using iteration
Expand All @@ -49,10 +71,10 @@ def fib_iterative(n: int) -> list[int]:
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
ValueError: n is negative
"""
if n < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
if n == 0:
return [0]
fib = [0, 1]
Expand All @@ -75,7 +97,7 @@ def fib_recursive(n: int) -> list[int]:
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
ValueError: n is negative
"""

def fib_recursive_term(i: int) -> int:
Expand All @@ -95,13 +117,13 @@ def fib_recursive_term(i: int) -> int:
Exception: n is negative
"""
if i < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
if i < 2:
return i
return fib_recursive_term(i - 1) + fib_recursive_term(i - 2)

if n < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
return [fib_recursive_term(i) for i in range(n + 1)]


Expand All @@ -119,7 +141,7 @@ def fib_recursive_cached(n: int) -> list[int]:
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
ValueError: n is negative
"""

@functools.cache
Expand All @@ -128,13 +150,13 @@ def fib_recursive_term(i: int) -> int:
Calculates the i-th (0-indexed) Fibonacci number using recursion
"""
if i < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
if i < 2:
return i
return fib_recursive_term(i - 1) + fib_recursive_term(i - 2)

if n < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
return [fib_recursive_term(i) for i in range(n + 1)]


Expand All @@ -152,10 +174,10 @@ def fib_memoization(n: int) -> list[int]:
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
ValueError: n is negative
"""
if n < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
# Cache must be outside recursuive function
# other it will reset every time it calls itself.
cache: dict[int, int] = {0: 0, 1: 1, 2: 1} # Prefilled cache
Expand Down Expand Up @@ -193,29 +215,30 @@ def fib_binet(n: int) -> list[int]:
>>> fib_binet(-1)
Traceback (most recent call last):
...
Exception: n is negative
ValueError: n is negative
>>> fib_binet(1475)
Traceback (most recent call last):
...
Exception: n is too large
ValueError: n is too large
"""
if n < 0:
raise Exception("n is negative")
raise ValueError("n is negative")
if n >= 1475:
raise Exception("n is too large")
raise ValueError("n is too large")
sqrt_5 = sqrt(5)
phi = (1 + sqrt_5) / 2
return [round(phi**i / sqrt_5) for i in range(n + 1)]


if __name__ == "__main__":
import doctest

doctest.testmod()
from doctest import testmod

testmod()
# Time on an M1 MacBook Pro -- Fastest to slowest
num = 30
time_func(fib_iterative, num)
time_func(fib_recursive, num) # Around 3s runtime
time_func(fib_recursive_cached, num) # Around 0ms runtime
time_func(fib_memoization, num)
time_func(fib_binet, num)
time_func(fib_iterative_yield, num) # 0.0012 ms
time_func(fib_iterative, num) # 0.0031 ms
time_func(fib_binet, num) # 0.0062 ms
time_func(fib_memoization, num) # 0.0100 ms
time_func(fib_recursive_cached, num) # 0.0153 ms
time_func(fib_recursive, num) # 257.0910 ms