#!/usr/bin/env python
# coding: utf-8
# # Memoization, Code Optimization and Time Complexity
# - based on the Latin word memorandum, meaning "to be remembered"
# - similar to word memorization, its a technique used in coding to improve program runtime by memorizing intermediate solutions
# - if an intermediate results (from some computations) are used over and again, the results can be remembered/stored to avoid unnecessary repeating calculations
# - using dict type datastructure, one can memoize intermediate results from functions esp. in recurisive solutions
# ## naive recursive fib function
# In[41]:
count = 0
def fib(n):
global count
count += 1
if n <= 1:
return 1
f = fib(n-1) + fib(n-2)
return f
n=30 #40, 50? takes a while
print("fib at {}th position = {}".format(n, fib(n)))
print("fib function count = {}".format(count))
# ## theoritical computational complexity
# - Worst case Big-Oh Time Complexity: O(n) = time to calculate Fib(n-1) + Fib(n-2) + time to add them: O(1)
# - T(n) = T(n-1) + T(n-2) + O(1)
# - T(n) = O(2n-1) + O(2n-2) + O(1)
# - T(n) = O(2n)
# - Space Complexity = O(n) due to call stack
# ## finding actual time - timeit
# - timeit - measures execution time of small code snippets
# - timeit.timeit(stmt='pass', setup='pass', timer=[default timer], number=1000000, globals=None)
# - returns time in seconds
# In[42]:
import timeit
help(timeit)
# In[43]:
#print(globals())
import timeit
print(timeit.timeit('fib(30)', number=1, globals=globals()))
# ## memoized recursive fib function
# In[44]:
memo = {}
count = 0
def MemoizedFib(n):
global memo, count
count += 1
if n <= 1:
return 1
if n in memo:
return memo[n]
memo[n] = MemoizedFib(n-1) + MemoizedFib(n-2)
return memo[n]
n=60 #try 40, 50, 60
print("fib at {}th position = {}".format(n, MemoizedFib(n)))
print("fib function count = {}".format(count))
# In[45]:
import timeit
print(timeit.timeit('MemoizedFib(1000)', number=1, globals=globals()))
# ## computational complexity of memoized fib
# - Time Complexity - O(n)
# - Space Complexity - O(n)
# ## exercise
# - Write a program that finds factorials of a bunch of positive integer numbers. Would memoization improve time complexity of the program?
# In[46]:
# find the factorials of the first 20 positive numbers
# optimize the program so it finds the factorials in the
# shortest time possible
nums = list(range(1, 21))
print(nums)
# # Python code optimization
# - https://wiki.python.org/moin/PythonSpeed/PerformanceTips
# ## Summary
#
# - use functions and local variables instead of globals, eventhough function calls are relatively high
# - avoid dot . member access specifier
# - user built-in list.sort and sorted with key using itemgetter function if required
# - avoid string concatenation; use "".join(somelist) instead
# - use list comprehension and map() instead of loops
# - while working with dict (esp. initializing), use try catch instead of key test
# - use defaultdict class from collections
# - doing stuff less often - change sys.setswitchinterval(sys.maxsize) to as high as possible if not running threads and catching signals (default is 100?)
# - use addition and subtraction instead of multiplicaiton and divisions
# In[47]:
import sys
sys.getswitchinterval()
# In[48]:
sys.maxsize
# In[49]:
import timeit
# In[50]:
code = '''
x = 47
x = x * 2
'''
timeit.timeit(stmt=code)
# In[51]:
code = '''
x = 47
x = x << 1
'''
timeit.timeit(stmt=code)
# In[38]:
code = '''
x = 47
x = x + x
'''
timeit.timeit(stmt=code)
# ## Time complexity
# - Time complexity of various Python built-in data sturctures and functions
# - https://wiki.python.org/moin/TimeComplexity
# In[ ]: