diff --git a/maths/fibonacci.py b/maths/fibonacci.py index e65190354..b009ea9df 100644 --- a/maths/fibonacci.py +++ b/maths/fibonacci.py @@ -1,130 +1,130 @@ -# fibonacci.py -""" -1. Calculates the iterative fibonacci sequence - -2. Calculates the fibonacci sequence with a formula - an = [ Phin - (phi)n ]/Sqrt[5] - reference-->Su, Francis E., et al. "Fibonacci Number Formula." Math Fun Facts. - -""" -import functools -import math -import time -from decimal import Decimal, getcontext - -getcontext().prec = 100 - - -def timer_decorator(func): - @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 - :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: - if n >= min_thresh and max_thresh is None: - return True - elif min_thresh <= n <= max_thresh: - return True - elif n < 0: - raise ValueLessThanZero - elif n < min_thresh: - 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): - """ - :param n: calculate Fibonacci to the nth integer - :type n:int - :return: Fibonacci sequence as a list - """ - n = int(n) - if _check_number_input(n, 2): - seq_out = [0, 1] - a, b = 0, 1 - for _ in range(n - len(seq_out)): - a, b = b, a + b - seq_out.append(b) - return seq_out - - -@timer_decorator -def fib_formula(n): - """ - :param n: calculate Fibonacci to the nth integer - :type n:int - :return: Fibonacci sequence as a list - """ - seq_out = [0, 1] - n = int(n) - if _check_number_input(n, 2, 1000000): - sqrt = Decimal(math.sqrt(5)) - phi_1 = Decimal(1 + sqrt) / Decimal(2) - phi_2 = Decimal(1 - sqrt) / Decimal(2) - for i in range(2, n): - temp_out = ((phi_1 ** Decimal(i)) - (phi_2 ** Decimal(i))) * ( - Decimal(sqrt) ** Decimal(-1) - ) - seq_out.append(int(temp_out)) - return seq_out - - -if __name__ == "__main__": - num = 20 - # print(f'{fib_recursive(num)}\n') - # print(f'{fib_iterative(num)}\n') - # print(f'{fib_formula(num)}\n') - fib_iterative(num) - fib_formula(num) +# fibonacci.py +""" +Calculates the Fibonacci sequence using iteration, recursion, and a simplified +form of Binet's formula + +NOTE 1: the iterative and recursive functions are more accurate than the Binet's +formula function because the iterative function doesn't use 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 +""" + +from math import sqrt +from time import time + + +def time_func(func, *args, **kwargs): + """ + Times the execution of a function with parameters + """ + start = time() + output = func(*args, **kwargs) + end = time() + if int(end - start) > 0: + print(f"{func.__name__} runtime: {(end - start):0.4f} s") + else: + print(f"{func.__name__} runtime: {(end - start) * 1000:0.4f} ms") + return output + + +def fib_iterative(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using iteration + >>> fib_iterative(0) + [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 + """ + if n < 0: + raise Exception("n is negative") + if n == 0: + return [0] + fib = [0, 1] + for _ in range(n - 1): + fib.append(fib[-1] + fib[-2]) + return fib + + +def fib_recursive(n: int) -> list[int]: + """ + Calculates the first n (0-indexed) Fibonacci numbers using recursion + >>> fib_iterative(0) + [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 + """ + + 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") + if i < 2: + return i + return fib_recursive_term(i - 1) + fib_recursive_term(i - 2) + + 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__": + num = 20 + time_func(fib_iterative, num) + time_func(fib_recursive, num) + time_func(fib_binet, num) diff --git a/maths/fibonacci_sequence_recursion.py b/maths/fibonacci_sequence_recursion.py deleted file mode 100644 index 794b9fc0b..000000000 --- a/maths/fibonacci_sequence_recursion.py +++ /dev/null @@ -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()