diff --git a/maths/fibonacci.py b/maths/fibonacci.py index 8cdd6cdb1..927700b04 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -1,4 +1,3 @@ -# fibonacci.py """ Calculates the Fibonacci sequence using iteration, recursion, memoization, and a simplified form of Binet's formula @@ -9,14 +8,12 @@ the Binet's formula function because the Binet formula function uses floats 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 @@ -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 @@ -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] @@ -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: @@ -95,13 +117,13 @@ def fib_recursive(n: int) -> list[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)] @@ -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 @@ -128,13 +150,13 @@ def fib_recursive_cached(n: int) -> list[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)] @@ -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 @@ -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