Merge maths/fibonacci.py and maths/fibonacci_sequence_recursion.py (#5738)

* Rewrite parts of Vector and Matrix methods

* Refactor determinant method and add unit tests

Refactor determinant method to create separate minor and cofactor
methods.
Add respective unit tests for new methods.
Rename methods using snake case to follow Python naming conventions.

* Reorganize Vector and Matrix methods

* Update linear_algebra/README.md

Co-authored-by: John Law <johnlaw.po@gmail.com>

* Fix punctuation and wording

* Apply suggestions from code review

Co-authored-by: John Law <johnlaw.po@gmail.com>

* Deduplicate euclidean length method for Vector

* Add more unit tests for Euclidean length method

* Fix bug in unit test for euclidean_length

* Remove old comments for magnitude method

* Rewrite maths/fibonacci.py

* Rewrite timer and add unit tests

* Fix typos in fib_binet unit tests

* Fix typos in fib_binet unit tests

* Clean main method

* Merge fibonacci.py and fibonacci_sequence_recursion.py

* Fix fib_binet unit test

Co-authored-by: John Law <johnlaw.po@gmail.com>
This commit is contained in:
Tianyi Zheng 2021-11-01 06:25:40 +00:00 committed by GitHub
parent 99cf2cc1c5
commit 06ab650e08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 152 deletions

View File

@ -1,130 +1,130 @@
# fibonacci.py # fibonacci.py
""" """
1. Calculates the iterative fibonacci sequence Calculates the Fibonacci sequence using iteration, recursion, and a simplified
form of Binet's formula
2. Calculates the fibonacci sequence with a formula NOTE 1: the iterative and recursive functions are more accurate than the Binet's
an = [ Phin - (phi)n ]/Sqrt[5] formula function because the iterative function doesn't use floats
reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts.
<http://www.math.hmc.edu/funfacts> 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
""" """
import functools
import math
import time
from decimal import Decimal, getcontext
getcontext().prec = 100 from math import sqrt
from time import time
def timer_decorator(func): def time_func(func, *args, **kwargs):
@functools.wraps(func)
def timer_wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
if int(end - start) > 0:
print(f"Run time for {func.__name__}: {(end - start):0.2f}s")
else:
print(f"Run time for {func.__name__}: {(end - start)*1000:0.2f}ms")
return func(*args, **kwargs)
return timer_wrapper
# define Python user-defined exceptions
class Error(Exception):
"""Base class for other exceptions"""
class ValueTooLargeError(Error):
"""Raised when the input value is too large"""
class ValueTooSmallError(Error):
"""Raised when the input value is not greater than one"""
class ValueLessThanZero(Error):
"""Raised when the input value is less than zero"""
def _check_number_input(n, min_thresh, max_thresh=None):
""" """
:param n: single integer Times the execution of a function with parameters
:type n: int
:param min_thresh: min threshold, single integer
:type min_thresh: int
:param max_thresh: max threshold, single integer
:type max_thresh: int
:return: boolean
""" """
try: start = time()
if n >= min_thresh and max_thresh is None: output = func(*args, **kwargs)
return True end = time()
elif min_thresh <= n <= max_thresh: if int(end - start) > 0:
return True print(f"{func.__name__} runtime: {(end - start):0.4f} s")
elif n < 0: else:
raise ValueLessThanZero print(f"{func.__name__} runtime: {(end - start) * 1000:0.4f} ms")
elif n < min_thresh: return output
raise ValueTooSmallError
elif n > max_thresh:
raise ValueTooLargeError
except ValueLessThanZero:
print("Incorrect Input: number must not be less than 0")
except ValueTooSmallError:
print(
f"Incorrect Input: input number must be > {min_thresh} for the recursive "
"calculation"
)
except ValueTooLargeError:
print(
f"Incorrect Input: input number must be < {max_thresh} for the recursive "
"calculation"
)
return False
@timer_decorator def fib_iterative(n: int) -> list[int]:
def fib_iterative(n):
""" """
:param n: calculate Fibonacci to the nth integer Calculates the first n (0-indexed) Fibonacci numbers using iteration
:type n:int >>> fib_iterative(0)
:return: Fibonacci sequence as a list [0]
>>> fib_iterative(1)
[0, 1]
>>> fib_iterative(5)
[0, 1, 1, 2, 3, 5]
>>> fib_iterative(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
""" """
n = int(n) if n < 0:
if _check_number_input(n, 2): raise Exception("n is negative")
seq_out = [0, 1] if n == 0:
a, b = 0, 1 return [0]
for _ in range(n - len(seq_out)): fib = [0, 1]
a, b = b, a + b for _ in range(n - 1):
seq_out.append(b) fib.append(fib[-1] + fib[-2])
return seq_out return fib
@timer_decorator def fib_recursive(n: int) -> list[int]:
def fib_formula(n):
""" """
:param n: calculate Fibonacci to the nth integer Calculates the first n (0-indexed) Fibonacci numbers using recursion
:type n:int >>> fib_iterative(0)
:return: Fibonacci sequence as a list [0]
>>> fib_iterative(1)
[0, 1]
>>> fib_iterative(5)
[0, 1, 1, 2, 3, 5]
>>> fib_iterative(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> fib_iterative(-1)
Traceback (most recent call last):
...
Exception: n is negative
""" """
seq_out = [0, 1]
n = int(n) def fib_recursive_term(i: int) -> int:
if _check_number_input(n, 2, 1000000): """
sqrt = Decimal(math.sqrt(5)) Calculates the i-th (0-indexed) Fibonacci number using recursion
phi_1 = Decimal(1 + sqrt) / Decimal(2) """
phi_2 = Decimal(1 - sqrt) / Decimal(2) if i < 0:
for i in range(2, n): raise Exception("n is negative")
temp_out = ((phi_1 ** Decimal(i)) - (phi_2 ** Decimal(i))) * ( if i < 2:
Decimal(sqrt) ** Decimal(-1) return i
) return fib_recursive_term(i - 1) + fib_recursive_term(i - 2)
seq_out.append(int(temp_out))
return seq_out if n < 0:
raise Exception("n is negative")
return [fib_recursive_term(i) for i in range(n + 1)]
def fib_binet(n: int) -> list[int]:
"""
Calculates the first n (0-indexed) Fibonacci numbers using a simplified form
of Binet's formula:
https://en.m.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding
NOTE 1: this function diverges from fib_iterative at around n = 71, likely
due to compounding floating-point arithmetic errors
NOTE 2: this function overflows on n >= 1475 because of the size limitations
of Python floats
>>> fib_binet(0)
[0]
>>> fib_binet(1)
[0, 1]
>>> fib_binet(5)
[0, 1, 1, 2, 3, 5]
>>> fib_binet(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> fib_binet(-1)
Traceback (most recent call last):
...
Exception: n is negative
>>> fib_binet(1475)
Traceback (most recent call last):
...
Exception: n is too large
"""
if n < 0:
raise Exception("n is negative")
if n >= 1475:
raise Exception("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__": if __name__ == "__main__":
num = 20 num = 20
# print(f'{fib_recursive(num)}\n') time_func(fib_iterative, num)
# print(f'{fib_iterative(num)}\n') time_func(fib_recursive, num)
# print(f'{fib_formula(num)}\n') time_func(fib_binet, num)
fib_iterative(num)
fib_formula(num)

View File

@ -1,22 +0,0 @@
# Fibonacci Sequence Using Recursion
def recur_fibo(n: int) -> int:
"""
>>> [recur_fibo(i) for i in range(12)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
"""
return n if n <= 1 else recur_fibo(n - 1) + recur_fibo(n - 2)
def main() -> None:
limit = int(input("How many terms to include in fibonacci series: "))
if limit > 0:
print(f"The first {limit} terms of the fibonacci series are as follows:")
print([recur_fibo(n) for n in range(limit)])
else:
print("Please enter a positive integer: ")
if __name__ == "__main__":
main()