Let's have the following code:
import time
def rec1(len):
if( len < 2): return 1;
return 2*rec1(len-1);
def rec2(len):
if( len < 2): return 1;
return rec2(len-1) + rec2(len-1);
def callAndReport(len, method):
time1 = time.time()
answer = method(len)
time2 = time.time()
print("{0} with {1}:{2} in {3:.0f} ms".format(len,method.__name__,answer, (time2-time1)*1000))
if __name__ == '__main__':
callAndReport(20,rec1)
callAndReport(20,rec2)
print('')
callAndReport(23,rec1)
callAndReport(23,rec2)
This code produces the following output:
20 with rec1:524288 in 0 ms
20 with rec2:524288 in 642 ms
23 with rec1:4194304 in 0 ms
23 with rec2:4194304 in 4613 ms
Could someone explain the execution time difference? I have few ideas but I would like to be sure.
For completeness, the original problem I had was the method below (which can be easily express as a for loop, but that's not the point here):
def find11s_rec(len):
if len<2: return 0
if len== 2: return 1;
return find11s_rec(len-2)+find11s_rec(len-1)+2**(len-2)
.
That's because while rec1 only uses rec1 once, rec2 uses rec2 twice. And then those inner rec2 calls will each call rec2 twice. The number of function calls will rise exponentially. While rec1 might use x calls, rec2 would use 2^x calls. In computer science terms, rec1 is O(x) while rec2 is O(2^x). In more complicated cases, dangerous recursion might inobvious; so use a debugger to find out what is actually being done.
The complexity of rec1 is O(n) and the complexity of rec2 is O(2^n). That's a big performance difference.
rec2(n) = rec2(n-1) + rec2(n-1)
= (rec2(n-2) + rec2(n-2)) + (rec2(n-2) + rec2(n-2)) = 4 * rec2(n-2)
...
rec2(n) = (2^n)*rec2(1)
= O(2^n)
Related
Here's the Python code:
def is_palindrome(s):
return s == s[::-1]
def longestp(s):
if is_palindrome(s):
return s
maxp = s[0]
for i in range(len(s)-1):
half_length = len(maxp) // 2
start = i - half_length
end = i + half_length
while start >= 0 and end <= len(s)-1:
if is_palindrome(s[start:end+2]):
if len(s[start:end+2]) > len(maxp):
maxp = s[start:end+2]
end += 1
elif is_palindrome(s[start:end+1]):
if len(s[start:end+1]) > len(maxp):
maxp = s[start:end+1]
start -= 1
end += 1
else:
break
return maxp
I initially thought it was O(n^3) because of two nested loops and string slicing, but it turned out to be nearly linear in my tests. Is there any kind of input for which this algorithm is going to be slow?
The algorithm looks as if it needs total time proportional to
integral_0^N x dx = [(x^2)/2]_0^N = (N^2)/2 = O(N^2)
The strings matching ab* should give the worst case behavior.
Here is a piece of code that kind-of demonstrates the worst case behavior experimentally.
The structure is as follows:
Define worstCase function that constructs "bad" strings of length N
Measure time of your function on these strings
Create dataset of log(N) vs. log(time(N))
Fit a line, try to estimate the slope of the line: this is the exponent p in your O(N^p).
Here is the code:
def worstCase(length):
return "a" + "b" * (length - 1)
from time import clock
from math import log
xs = []
ys = []
for n in [4 * int(1000 * 1.2 ** n) for n in range(1, 20)]:
s = worstCase(n)
assert len(s) == n
startTime = clock()
p = longestp(s)
endTime = clock()
assert p == s[1:]
t = endTime - startTime
xs.append(log(n))
ys.append(log(t))
print("%d -> %f" % (n, endTime - startTime))
from numpy import polyfit
exponent, constant = polyfit(xs, ys, 1)
print("Exponent was: %f" % (exponent))
Here is the output (takes a minute or two):
4800 -> 0.057818
5760 -> 0.078123
6908 -> 0.105169
8292 -> 0.145572
9952 -> 0.197657
11940 -> 0.276103
14332 -> 0.382668
17196 -> 0.534682
20636 -> 0.747468
24764 -> 1.048267
29720 -> 1.475469
35664 -> 2.081608
42796 -> 2.939904
51356 -> 4.216063
61628 -> 5.963550
73952 -> 8.691849
88744 -> 12.126039
106492 -> 19.684188
127788 -> 24.942766
Exponent was: 1.867208
It estimates the exponent to be ~1.86, which is closer to 2 than to 3.
It's definitely not linear. Try it with input that contains a lot of palindromes, but isn't a palindrome:
>>> timeit.timeit('longestp(x)', 'x="a"*100000+"b"', globals=globals(), number=1)
5.5123205203562975
>>> timeit.timeit('longestp(x)', 'x="a"*10000+"b"', globals=globals(), number=1)
0.08460151217877865
Slicing and s == s[::-1] have a much better constant factor than interpreted Python code, and you need to make sure the inner loop isn't breaking early. Those effects may have thrown off your attempt to judge time complexity by timing.
I don't think it's O(n^3) either. The nested loops don't interact the way you might intuitively expect, because of the break condition. The inner loop executes O(n) iterations over the whole course of the algorithm, because after a bounded number of iterations, either len(maxp) grows, or the loop breaks. This algorithm looks worst-case O(n^2) to me.
I am optimizing a bottleneck section of my code--iterating on a function a' = f(a), where a and a' are N by 1 vectors, until max(abs(a' - a)) is sufficiently small.
I have put a Numba wrapper on f(a), and got a nice speedup over the most optimized pure NumPy version I was able to produe (cut runtime by about 50%).
I tried writing a C-compatible version of numpy.max(numpy.abs(aprime - a)), but it turns out this is slower! I actually lose back ALL of the gains I got from Numba-fying the first portion of the iteration!
Is there likely to be a way for Numba or Cython to improve upon numpy.max(numpy.abs(aprime - a))? I reproduce my code below for reference, where a is P0 and a' is Pprime:
EDIT: For me, it seems that it is important to "flatten()" the inputs to "maxabs()". When I do this, the performance is no worse than NumPy. Then, when I do a "dry run" of the function outside the timing brackets as JoshAdel suggested, the loop with "maxabs" does slightly better than the loop with numpy.max(numpy.abs()).
from numba import jit
import numpy as np
### Preliminaries, to make the working example fully functional
n = 1200
Gammer = np.exp(-np.random.rand(n,n))
alpher = np.ones((n,1))
xxer = 10000*np.random.rand(n,1)
chii = 6.5
varkappa = 6.5
phi3 = 1.5
A = .5
sig = .2
mmer = np.dot(Gammer,xxer**phi3)
totalprod = A*alpher + (1-A)*mmer
Gammerchii = Gammer**chii
Gammerrats = Gammerchii[:,0].flatten()/Gammerchii[0,:].flatten()
Gammerrats[(Gammerchii[0,:].flatten() == 0) | (Gammerchii[:,0].flatten() == 0)] = 1.
P0 = (Gammerrats*(xxer[0]/totalprod[0])*(totalprod/xxer).flatten())**(1/(1+2*chii))
P0 *= n/np.sum(P0)
### End of preliminaries
### This is the function to produce a' = f(a)
#jit
def Piteration(P0, chii, sig, n, xxer, totalprod, Gammerrats, Gammerchii):
Mac = np.zeros((n,))
Pprime = np.zeros((n,))
themacpow = 1-(1/chii)*(sig/(1-sig))
specialchiipow = 1/(1+2*chii)
Psum = 0.
for i in range(n):
for j in range(n):
Mac[j] += ((P0[i]/P0[j])**chii)*Gammerchii[i,j]*totalprod[j]
for i in range(n):
Pprime[i] = (Gammerrats[i]*(xxer[0]/totalprod[0])*(totalprod[i]/xxer[i])*((Mac[i]/Mac[0])**themacpow))**specialchiipow
Psum += Pprime[i]
Psum = n/Psum
for i in range(n):
Pprime[i] *= Psum
return Pprime
### This is the function to find max(abs(aprime - a))
#jit
def maxabs(vec1,vec2,n):
themax = 0.
curdiff = 0.
for i in range(n):
curdiff = vec1[i] - vec2[i]
if curdiff < 0:
curdiff *= -1
if curdiff > themax:
themax = curdiff
return themax
### This is the main loop
diff = 1000.
while diff > 1e-2:
Pprime = Piteration(P0.flatten(), chii, sig, n, xxer.flatten(), totalprod.flatten(), Gammerrats.flatten(), Gammerchii)
diff = maxabs(P0.flatten(),Pprime.flatten(),n)
P0 = 1.*Pprime
When I time your maxabs function vs np.max(np.abs(vec1 - vec2)) for an array of shape (1200,), the numba version is ~2.6x faster using numba 0.32.0.
When you time the code, make sure you run your function once before you time it so that you don't include the time it takes to jit the code, which you only pay the first time. In general using timeit and running multiple times takes care of this. I'm not sure how you did the timing though since I see almost no difference in using maxabs vs the numpy call, most of the runtime seems to be in the call to Piteration.
I am trying to revamp a function that uses the Pollard Rho method to factor an integer but my attempt at using memoize has had no improvement in being able to factor a specific number (N=7331117) that this function should be able to facotr.
Before attempt:
import fractions
def pollard_Rho(n):
def f(xn):
if xn == 0:
return 2
return f(xn - 1) ** 2 + 1
i = 0
x = f(i)
y = f(f(i))
d = fractions.gcd(abs(x - y), n)
while d == 1:
i = i + 1
d = fractions.gcd(abs(x - y), n)
root1 = d
root2 = n / d
print i + 1
return (root1, root2)
memoize attempt:
def pollard_Rho(n):
class memoize:
def __init__(self, function):
self.function = function
self.memoized = {}
def __call__(self, *args):
try:
return self.memoized[args]
except KeyError:
self.memoized[args] = self.function(*args)
return self.memoized[args]
#memoize
def f(xn):
if xn == 0:
return 2
return f(xn - 1) ** 2 + 1
i = 0
x = f(i)
y = f(f(i))
d = fractions.gcd(abs(x - y), n)
while d == 1:
i = i + 1
d = fractions.gcd(abs(x - y), n)
root1 = d
root2 = n / d
print i + 1
return (root1, root2)
Now neither code produces any errors but both codes also do produce any results.
The output of
print pollard_Rho(7331117)
should be (641, 11437) (I know this because of another factorization function I have written) but what actually happens is the code runs through 3 iterations of the while loop and nothing happens afterwards. Does anyone have any suggestions?
Sorry for the vague question, does anyone have any suggestions on improving the the codes ability to factor in general? Maybe by a method more efficient than a recursive function? 7331116 and 7331118 factor perfectly fine and only 7331117 seems to be a tough nut to crack so far using this method.
Its possible I didn't use memoize right because even with looking at at on of stackoverflow examples I don't really understand how to use it. It seems every single instance of it I came across was drastically different.
It seems like your algorithm does not work for some reason. In order to see what is going on I went to wikipedia site of the algorithm and implemented regular version from there and it worked without a problem. Than I replaced my g function with your recursive version and I got following error
File "rho.py", line 25, in f_fun
return 2 if xn == 0 else f_fun(xn - 1) ** 2 + 1
RecursionError: maximum recursion depth exceeded
It seems like you cannot implement this with a regular recursion. I would suggest to convert your recursion to a fold or a generator.
Here is the code I tried:
https://gist.github.com/huseyinyilmaz/73c1ac42b2a20d24d3b5
UPDATE:
Here is your version with cache, it still have maximum depth problem. (python 2 implementation)
https://gist.github.com/huseyinyilmaz/bb26ac172fbec4c655d3
I am newbie in Python. I'm stuck on doing Problem 15 in Project-Euler in reasonable time. The problem in memoize func. Without memoize all working good, but only for small grids. I've tried to use Memoization, but result of such code is "1" for All grids.
def memoize(f): #memoization
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
#memoize
def search(node):
global route
if node[0] >= k and node[1] >= k:
route += 1
return route
else:
if node[0] < k + 1 and node[1] < k + 1:
search((node[0] + 1, node[1]))
search((node[0], node[1] + 1))
return route
k = 2 #grid size
route = 0
print(search((0, 0)))
If commenting out code to disable memoize func:
##memoize
all works, but to slow for big grids. What am i doing wrong? Help to debbug. Thx a lot!
Update1:
Thank for your help, I've found answer too:
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
#memoize
def search(node):
n = 0
if node[0] == k and node[1] == k:
return 1
if node[0] < k+1 and node[1] < k+1:
n += search((node[0] + 1, node[1]))
n += search((node[0], node[1] + 1))
return n
k = 20
print(search((0, 0)))
Problem was not in memoize func as i thought before. Problem was in 'search' function. Whithout globals it wroiking right i wished. Thx for comments, they was really usefull.
Your memoization function is fine, at least for this problem. For the more general case, I'd use this:
def memoize(f):
f.cache = {} # - one cache for each function
def _f(*args, **kwargs): # - works with arbitrary arguments
if args not in f.cache: # as long as those are hashable
f.cache[args] = f(*args, **kwargs)
return f.cache[args]
return _f
The actual problem -- as pointed out by Kevin in the comments -- is that memoization only works if the function does not work via side effects. While your function does return the result, you do not use this in the recursive calculation, but just rely on incrementing the global counter variable. When you get an earlier result via memoization, that counter is not increased any further, and you do not use the returned value, either.
Change your function to sum up the results of the recursive calls, then it will work.
You can also simplify your code somewhat. Particularly, the if check before the recursive call is not necessary, since you check for >= k anyway, but then you should check whether the x component or the y component is >= k, not both; once either has hit k, there's just one more route to the goal. Also, you could try to count down to 0 instead of up to k so the code does not need k anymore.
#memoize
def search(node):
x, y = node
if x <= 0 or y <= 0:
return 1
return search((x - 1, y)) + search((x, y - 1))
print(search((20, 20)))
Try this code. It works fast even with grids over 1000x1000! Not nessesarily square.
But I didn't know about memoization yet...
import time
def e15():
x=int(input("Enter X of grid: "))
y=int(input("Enter Y of grid: "))
start = time.time()
lst=list(range(1,x+2))
while lst[1]!=y+1:
i=0
for n in lst[1:]:
i+=1
lst[i]=n+lst[i-1]
print(f"There are {lst[-1]} routes in {x}x{y} grid!")
end = time.time() - start
print("Runtime =", end)
e15()
This problem can be solved in O(1) time by using the code below:
from math import factorial as f
n, m = map(int, input("Enter dimensions (separate by space)?").split())
print ("Routes through a", n, "x", m, "grid", f(n+m) // f(n) // f(m))
Here's a link for a proof of the equation:
Project Euler Problem 15 Solution
This is not homework.
I saw this article praising Linq library and how great it is for doing combinatorics stuff, and I thought to myself: Python can do it in a more readable fashion.
After half hour of dabbing with Python I failed. Please finish where I left off. Also, do it in the most Pythonic and efficient way possible please.
from itertools import permutations
from operator import mul
from functools import reduce
glob_lst = []
def divisible(n): return (sum(j*10^i for i,j in enumerate(reversed(glob_lst))) % n == 0)
oneToNine = list(range(1, 10))
twoToNine = oneToNine[1:]
for perm in permutations(oneToNine, 9):
for n in twoToNine:
glob_lst = perm[1:n]
#print(glob_lst)
if not divisible(n):
continue
else:
# Is invoked if the loop succeeds
# So, we found the number
print(perm)
Thanks!
Here's a short solution, using itertools.permutations:
from itertools import permutations
def is_solution(seq):
return all(int(seq[:i]) % i == 0 for i in range(2, 9))
for p in permutations('123456789'):
seq = ''.join(p)
if is_solution(seq):
print(seq)
I've deliberately omitted the divisibility checks by 1 and by 9, since they'll always be satisfied.
Here's my solution. I like all things bottom-up ;-). On my machine it runs about 580 times faster (3.1 msecs vs. 1.8 secs) than Marks:
def generate(digits, remaining=set('123456789').difference):
return (n + m
for n in generate(digits - 1)
for m in remaining(n)
if int(n + m) % digits == 0) if digits > 0 else ['']
for each in generate(9):
print(int(each))
EDIT: Also, this works, and twice as fast (1.6 msecs):
from functools import reduce
def generate():
def digits(x):
while x:
x, y = divmod(x, 10)
yield y
remaining = set(range(1, 10)).difference
def gen(numbers, decimal_place):
for n in numbers:
for m in remaining(digits(n)):
number = 10 * n + m
if number % decimal_place == 0:
yield number
return reduce(gen, range(2, 10), remaining())
for each in generate():
print(int(each))
Here's my solution (not as elegant as Mark's, but it still works):
from itertools import permutations
for perm in permutations('123456789'):
isgood = 1
for i in xrange(9):
if(int(''.join(perm[:9-i])) % (9-i)):
isgood = 0
break
if isgood:
print ''.join(perm)
this is my solution, it is very similar to Marks, but it runs about twice as fast
from itertools import permutations
def is_solution(seq):
if seq[-1]=='9':
for i in range(8,1,-1):
n = -(9-i)
if eval(seq[:n]+'%'+str(i))==0:
continue
else:return False
return True
else:return False
for p in permutations('123456789'):
seq = ''.join(p)
if is_solution(seq):
print(seq)