Exponents / Iteration vs Recursion
""" Exponents are calculated by multiplying a number by itself.
res = x^n
Iterative approach uses a loop that repeatedly multiplies a number by itself:
res = res * x
Recursive approach uses the power rule:
a^n = a^(n-1) * a^1
For most of the tasks, an iterative approach, with loops that repeat a task,
is usually better than recursion.
"""
def pow_interative(x, n):
res = x
for i in range(1, n):
res = res * x
return res
def pow_recursive(x, n):
if n == 0:
return 1 # Base case
res = pow_recursive(x, n-1) * x
return res
assert pow_interative(2, 2) == 4
assert pow_recursive(3, 3) == 27
print("2^2 =", pow_interative(2,2))
print("3^3 =", pow_recursive(3,3))
# Time
import time
t1 = time.time(); result = pow_interative(3, 600)
t2 = time.time(); result = pow_recursive(3, 600)
t4 = time.time(); result = pow(3, 600)
print("Iterative 3^600: ", time.time() - t1, 's')
print("Recursive 3^600: ", time.time() - t2, 'sec')
print("Native pow 3^600: ", time.time() - t4, 's')
"""
2^2 = 4
3^3 = 27
Iterative 3^600: 0.0007915496826171875 s
Recursive 3^600: 0.0007688999176025391 sec
Native pow 3^600: 4.6253204345703125e-05 s
"""
Factorial / Iteration vs Recursion
""" A factorial, denoted by an exclamation point (n!), is the product of
all positive integers less than or equal to a given non-negative integer n
4! = 4x3x2x1
Iterrative, multiplies intergers 1 to n in a loop,
1 frame object (function call)
Recursive, uses neighbours 5! = 5 * 4!
5 frame objects
"""
def factorial_iterative(n):
p = 1
for i in range(1, n+1):
p = p * i
return p
def factorial_recursive(n):
if n == 1:
return 1
return n * factorial_recursive(n-1)
assert factorial_iterative(4) == 24
assert factorial_recursive(5) == 5 * factorial_recursive(4)
print("4! =", factorial_iterative(4))
print("5! =", factorial_recursive(5))
# Limits
n = factorial_iterative(30000)
print(f"Iterative factorial 30.000 iterations:", len(str(n)))
# Recursive approach limit (< 3000)
try:
n = factorial_recursive(3000)
except RecursionError as e:
print(f"Recursive factorial 3.000 iterations limit reached!")
print(f"RecursionError: {e}")
"""
4! = 24
5! = 120
Iterative factorial 30.000 iterations: 121288
Recursive factorial 3.000 iterations limit reached!
RecursionError: maximum recursion depth exceeded in comparison
"""
Fibonacci / Iteration vs Recursion
Every number is the sum of previous two numbers.
""" In Fibonacci sequence every number is the sum of previous two numbers.
0, 1, 1, 2, 3, 5, 8, 13, 21, ...
Iterrative approach, a loop and two variables (a, b)
The program needs to keep track only of the latest two numbers.
0 1 1 0 1 1 2 0 1 1 2 3 0 1 1 2 3 5 ...
a b a+b a b a+b a b a+b a b a+b
The recursive algorithm is much slower than the iterative, it repeats same calculation.
For example, in fibonacci(5) the fibonacci(2) is called four times!
"""
def fibonacci_iterative(n):
a, b = 0, 1
for i in range(1, n):
nextb = a + b
a = b
b = nextb
# a, b = b, a + b # oneline
return b
def fibonacci_recursive(n):
if n <= 2:
return 1
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
# Tests
assert fibonacci_iterative(1) == 1
assert fibonacci_iterative(3) == 2
assert fibonacci_iterative(4) == 3
assert fibonacci_iterative(5) == 5
assert fibonacci_recursive(6) == 8
assert fibonacci_recursive(7) == 13
#Output
print("The third Fibonnaci number is:", fibonacci_iterative(3))
print("The forth Fibonnaci number is:", fibonacci_iterative(4))
print("The fifth Fibonnaci number is:", fibonacci_iterative(5))
# Time
import time
print("\nThe recursive algorithm is much slower than the iterative.")
print("Processing ...")
t1 = time.time(); n1 = fibonacci_iterative(100)
t2 = time.time(); n2 = fibonacci_recursive(36)
print("Iterative: fibonacci(100)", time.time() - t1, 's')
print("Recursive: fibonacci(36) ", time.time() - t2, 's')
"""
The third Fibonnaci number is: 2
The forth Fibonnaci number is: 3
The fifth Fibonnaci number is: 5
The recursive algorithm is much slower than the iterative.
Processing ...
Iterative: fibonacci(100) 2.685002088546753 s
Recursive: fibonacci(36) 2.6850242614746094 s
"""
Logarithm
Use binary search (divide and conquer) to compute logarithm.
import math
def logarithm(x, base):
if x == 1:
return 0
left = 0
right = x
epsilon = 1e-7 # small value (precision of the result)
m = (right + left) // 2 # middle
while right - left > epsilon:
if x <= base**m:
right = m
else:
left = m
m = (left + right) / 2
return round(m, 7)
print(logarithm(8, 2))
print(logarithm(625, 5))
print(logarithm(1000, 10))
assert logarithm(8, 2) == 3 == math.log(8, 2)
assert logarithm(625, 5) == 4 == math.log(625, 5)
assert logarithm(1000, 10) == 3 == round(math.log(1000, 10))
Questions and answers
Which one represents the multiplication of a number by itself?
- a) exponent
- b) factorial
Which one multiply a number by smaller numbers?
- a) fibonacci
- b) factorial