If I wanted to calculate the time complexity on these two functions, how would I go about it/ can someone help explain time complexity - python-3.x

def _factorial(n):
def _generator_(n):
y=np.multiply(n,(n-1))
for i in reversed(np.arange(1,n)):
y = np.multiply(y,(int(i)-1))
if i == 2:
yield y
if n <= 0:
n = np.multiply(n,-1)
for i in _generator_(n):
return i
def _permutation(total_objects,num_objects_selected):
def _generator_(total_objects,num_objects_selected):
while True:
yield np.divide(_factorial(total_objects),_factorial(total_objects-num_objects_selected))
for x in _generator_(total_objects,num_objects_selected):
return x
I just need help understanding time complexity using these two functions that I made as examples.

Related

Huge difference in process timing of functions (timit, time)

For some didactical purposes, I want to measure the timing of some functions (slightly more complex ones, than the ones shown) and discuss later on the big O scaling behaviour. But I have problems with the reliability of the numbers produced:
My code:
import time
import numpy as np
def run_time(fun, runs):
times = []
for i in range(runs):
t0 = time.clock()
fun()
t1 = time.clock() - t0
times.append(t1)
return np.mean(times), np.std(times)#, times
def fact0(n):
product = 1
for i in range(n):
product = product * (i+1)
return product
def fact1(n):
if n == 0:
return 1
else:
return n * fact1(n-1)
print(run_time(lambda: fact0(500), 100000))
print(run_time(lambda: fact1(500), 100000))
and I usually get something like:
(0.000186065330000082, 5.08689027009196e-05)
(0.0002853808799999845, 8.285739309454826e-05)
So the std larger than the mean. That's awful for me. Furthermore I expected fact0() to be way faster than fact1() due to no recursion.
If I now use timeit:
import timeit
mysetup = ""
mycode = '''
def fact1(n):
if n == 0:
return 1
else:
return n * fact1(n-1)
fact1(500)
'''
print(timeit.timeit(setup = mysetup, stmt = mycode, number = 100000)/100000)
I'll get something almost an order of magnt. smaller for mean:
2.463713497854769e-07
After the below discussed corrections it is:
0.00028513264190871266
Which is in great accordance to the run_time version.
So my question is how do I time functions appropriately? Why the huge difference between my two methods to get the timing? Any advices? I would love to stick to "time.clock()" and I don't want to use (again for didactical reasons) cProfile or even more complex modules.
I am grateful for any comments...
(edited due to comments by #Carcigenicate)
You never call the function in mycode, so you're only timing how long it takes the function to be defined (which I would expect to be quite fast).
You need to call the function:
import timeit
mycode = '''
def fact1(n):
if n == 0:
return 1
else:
return n * fact1(n-1)
fact1(100000)
'''
print(timeit.timeit(stmt=mycode, number=100000) / 100000)
Really though, this is suboptimal, since you're including in the timing the definition. I'd change this up and just pass a function:
def fact1(n):
if n == 0:
return 1
else:
return n * fact1(n-1)
print(timeit.timeit(lambda: fact1(100000), number=100000) / 100000)

how to improve computation speed when finding max in a large list? excluding Numpy

consider a game with an array(=num) containing some integers. I can take any integer and remove it from the array and add half of that number (rounded up) back to the array.i can do it for a fixed number of moves(=k).
the challenge is to minimize the sum of the final array.
my problem is that the test cases fail when dealing with large arrays :(
what is the efficient computation way to overcome this?
my first step for the challenge is taking max(num) and replace it with the result of ceil(max(num)/2) for k times.
another option is using sort(reverse) in every loop and replace the last value.
I've played with different sorting algos, read here and try bisect module they are all very new to me and didn't overcome the test-cases threshold, so i hope someone here can provide a helping hand for a newbie.
def minSum(num, k):
for i in range(k):
num.sort(reverse=True)
num.insert(0, math.ceil(num[0] / 2))
num.remove(num[1])
return sum(num)
minSum([10,20,7],4)
>>> 14
First off, inserting at the beginning of a Python list is much slower than inserting at the end, since every element has to be moved. Also, there's absolutely no reason to do so in the first place. You could just sort without reverse=True, then use pop to remove and return the last item and bisect.insort to put it back in at the right place without needing to re-sort the whole list.
from bisect import insort
from math import ceil
def min_sum(num, k):
num.sort()
for i in range(k):
largest = num.pop()
insort(num, ceil(largest/2))
return sum(num)
This should already be significantly faster than your original approach. However, in the worst case this is still O(n lg n) for the sort and O(k*n) for the processing; if your input is constructed such that halving each successive largest element makes it the new smallest element, you'll end up inserting it at the start which incurs an O(n) memory movement.
We can do better by using a priority queue approach, implemented in Python by the heapq library. You can heapify a list in linear time, and then use heapreplace to remove and replace the largest element successively. A slight awkwardness here is that heapq only implements a min-heap, so we'll need an extra pass to negate our input list at the beginning. One bonus side-effect is that since we now need to round down instead of up, we can just use integer division instead of math.ceil.
from heapq import heapify, heapreplace
def min_sum(num, k):
for i in range(len(num)):
num[i] = -num[i]
heapify(num)
for i in range(k):
largest = num[0]
heapreplace(num, largest // 2)
return -sum(num)
This way the initial list negation and heapification takes O(n), and then processing is only O(k lg n) since each heapreplace is a lg n operation.
To add some timing to all of the algorithms, it looks like #tzaman's heapq algorithm is by far the fastest. And it gives the same answer. Simply modifying the sort to not reversed does not give much speedup.
import random
import time
from bisect import insort
from heapq import heapify, heapreplace
from math import ceil
def makelist(num_elements):
mylist = range(int(num_elements))
mylist = list(mylist)
for i in mylist:
mylist[i] = int(random.random()*100)
return mylist
def minSum(num, k):
for i in range(k):
num.sort(reverse=True)
num.insert(0, ceil(num[0] / 2))
num.remove(num[1])
return sum(num)
def minSum2(num, k):
last_idx = len(num) - 1
for i in range(k):
num.sort()
num[last_idx] = ceil(num[last_idx] / 2)
return sum(num)
def min_sum(num, k):
num.sort()
for i in range(k):
largest = num.pop()
insort(num, ceil(largest/2))
return sum(num)
def min_heap(num, k):
for i in range(len(num)):
num[i] = -num[i]
heapify(num)
for i in range(k):
largest = num[0]
heapreplace(num, largest // 2)
return -sum(num)
if __name__ == '__main__':
mylist = makelist(1e4)
k = len(mylist) + 1
t0 = time.time()
# we have to make a copy of mylist for all of the functions
# otherwise mylist will be modified
print('minSum: ', minSum(mylist.copy(), k))
t1 = time.time()
print('minSum2: ', minSum2(mylist.copy(), k))
t2 = time.time()
print('min_sum: ', min_sum(mylist.copy(), k))
t3 = time.time()
print('min_heap: ', min_heap(mylist.copy(), k))
t4 = time.time()
print()
print('time for each method for k = %.0e: ' % k)
print('minSum: %f sec' % (t1-t0))
print('minSum2: %f sec' % (t2-t1))
print('min_sum: %f sec' % (t3-t2))
print('min_heap: %f sec' % (t4-t3))
and here is console output:
minSum: 205438
minSum2: 205438
min_sum: 205438
min_heap: 205438
time for each method for k = 1e+04:
minSum: 2.386861 sec
minSum2: 2.199656 sec
min_sum: 0.046802 sec
min_heap: 0.015600 sec
------------------
(program exited with code: 0)
Press any key to continue . . .

How yield works in Inorder traversal with recursive generator?

I am learning about recursive generators and found this program on web.
I undertand the recursive version of In-Order traversal but i am having trouble understanding recursive generator.
Specifically i am not able to understand
why 'yield x' is written in for loop?
Why 'yield x' is not collected in final list?
I have tried to dubug the generator and added the watch but i found that recursive call execute multiple time 'yield x' and it's not collected in final result.
class Tree:
def __init__(self, label, left=None, right=None):
self.label = label
self.left = left
self.right = right
def __repr__(self, level=0, indent=" "):
s = level*indent + repr(self.label)
if self.left:
s = s + "\\n" + self.left.__repr__(level+1, indent)
if self.right:
s = s + "\\n" + self.right.__repr__(level+1, indent)
return s
def __iter__(self):
return inorder(self)
def tree(list):
n = len(list)
if n == 0:
return []
i = n // 2
return Tree(list[i], tree(list[:i]), tree(list[i + 1:]))
# Recursive Generator
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Role of yield x inside for loop.
Maybe what you don't understand is how the generator works? The generator is different from the iterator, it is not directly calculating the worthy collection, it is dynamically getting all the values. If the result of inorder(t.left) is not traversed by a for loop, then yield inorder(t.left) will return the entire generator created for t.left. Because your entire inorder function is a generator.
Unfortunately, I have not been able to find a specific description document. This is a description based on my experience. Others are welcome to make corrections to my opinion. If someone can provide a specific official description, welcome to add
Not sure exactly what you're looking for, but perhaps it is something along these lines:
class Tree:
def __init__(self, label, left=None, right=None):
self.label = label
self.left = left
self.right = right
def __repr__(self, level=0, indent=" "):
s = level*indent + repr(self.label)
if self.left:
s = s + "\n" + self.left.__repr__(level+1, indent)
if self.right:
s = s + "\n" + self.right.__repr__(level+1, indent)
return s
def __iter__(self):
return inorder(self)
def tree(list):
n = len(list)
if n == 0:
return []
i = n // 2
return Tree(list[i], tree(list[:i]), tree(list[i + 1:]))
def inorder(t):
if t:
for x in t.left:
yield x
yield t.label
for x in t.right:
yield x
t = tree("ABCDEFG")
[print(i.label) for i in t]
which outputs:
A
B
C
D
E
F
G
With that code you could instead:
[print('----------\n', i) for i in t]
which outputs each hierarchical node in the tree, from A to G.
Edit: If you're asking how generator works, perhaps this example could be enlightening:
>>> def list2gen(lst):
... for elem in lst:
... yield str(elem) + '_'
...
>>> print(list2gen([1,2,'7',-4]))
<generator object list2gen at 0x000001B0DEF8B0C0>
>>> print(list(list2gen([1,2,'7',-4])))
['1_', '2_', '7_', '-4_']
If your debugger breaks multiple times at a yield, but those elements never materialize in the resulting generator, you'd have to attribute that to bugs in the debugger. I've not used one for Python in more than a decade; they used to be notoriously bug-infested. In Python the paradigm is "test is king," and "avoid manual debugging," but I disagree with that. (The only reason I don't is lack of great IDE's and debuggers.)

Dynamic Programming Primitive calculator code optimization

I am currently doing coursera course on algorithms. I have successfully completed this assignment. All test cases passed. My code looks messy and I want to know if there is any thing availiable in Python which can help run my code faster. Thanks
The problem statement is as follows: You are given a primitive calculator that can perform the following three operations with
the current number 𝑥: multiply 𝑥 by 2, multiply 𝑥 by 3, or add 1 to 𝑥. Your goal is given a
positive integer 𝑛, find the minimum number of operations needed to obtain the number 𝑛
starting from the number 1.
# Uses python3
import sys
def optimal_sequence(m):
a=[0,0]
for i in range(2,m+1):
if i%3==0 and i%2==0:
a.append(min(a[i//2],a[i//3],a[i-1])+1)
elif i%3==0:
a.append(min(a[i//3],a[i-1])+1)
elif i%2==0:
a.append(min(a[i//2],a[i-1])+1)
else:
a.append((a[i-1])+1)
return backtrack(a,m)
def backtrack(a,m):
result=[]
result.append(m)
current = m
for i in range(a[-1],0,-1):
if current%3==0 and a[current//3]==(i-1):
current=current//3
result.append(current)
elif current%2==0 and a[current//2]==(i-1):
current = current//2
result.append(current)
elif a[current-1]==(i-1):
current = current-1
result.append(current)
return result
n = int(input())
if n == 1:
print(0)
print(1)
sys.exit(0)
a= (optimal_sequence(n))
print(len(a)-1)
for x in reversed(a):
print(x,end=" ")
I would use a breadth first search for number 1 starting from number n. Keep track of the numbers that were visited, so that the search backtracks on already visited numbers. For visited numbers remember which is the number you "came from" to reach it, i.e. the next number in the shortest path to n.
In my tests this code runs faster than yours:
from collections import deque
def findOperations(n):
# Perform a BFS
successor = {} # map number to next number in shortest path
queue = deque() # queue with number pairs (curr, next)
queue.append((n,None)) # start at n
while True:
curr, succ = queue.popleft()
if not curr in successor:
successor[curr] = succ
if curr == 1:
break
if curr%3 == 0: queue.append((curr//3, curr))
if curr%2 == 0: queue.append((curr//2, curr))
queue.append((curr-1, curr))
# Create list from successor chain
result = []
i = 1
while i:
result.append(i)
i = successor[i]
return result
Call this function with argument n:
findOperations(n)
It returns a list.

Prime Factor Program Always Returning 3

Diving into coding for the first time and I'm trying to tackle the third Project Euler problem (finding the largest prime factor of 600851475143) and I want to write a function that simply returns the prime factors before I determine the largest one.
I cobbled together some shoddily written Python code below. It finds the factor of any number just fine but for some reason, the prime factor function always returns 3. Is there something I'm missing? Here's the code:
def factorize(j):
factors = []
print("Finding factors...")
for i in range(1, j+1):
if j % i == 0:
factors.append(i)
print("Done!")
print(factors)
return factors
def prime(n):
primes = []
for factor in n:
for p in range(1, factor+1):
for i in range (2, p):
if p % i == 0:
break
else:
primes.append(p)
print(primes)
return primes
print("Number to factor: ")
num = int(input())
num = factorize(num)
print("Now to find the primes...")
prime(num)
Thanks again for your help!
You put a return statement deep inside the nested loops of prime, so none of those loops completes an iteration.

Resources