[英]Python Efficiency / Optimization Project Euler #5 example
我为欧拉5号项目写了这个解决方案。
import time
start_time = time.time()
def ProjectEulerFive (m = 20):
a = m
start = 2
while (m % start) == 0:
start += 1
b = start
while b < m:
if ( a % b) != 0:
a += m
b = start
continue
else:
b += 1
return a
import sys
if (len(sys.argv)) > 2:
print "error: this function takes a max of 1 argument"
elif (len(sys.argv)) == 2:
print ProjectEulerFive(int(sys.argv[1]))
else:
print ProjectEulerFive();
print "took " + str(time.time() - start_time ) + " seconds"
我的系统大约需要8.5秒。
然后,我决定与其他人的解决方案进行比较。 我在Python中找到了这个Euler 5项目-如何优化解决方案? 。
我没有想到独特的素因数分解。
但是无论如何,在那里发布了一个据称经过优化的基于非素数分解的解决方案:
import time
start_time = time.time()
check_list = [11, 13, 14, 16, 17, 18, 19, 20]
def find_solution(step):
for num in xrange(step, 999999999, step):
if all(num % n == 0 for n in check_list):
return num
return None
if __name__ == '__main__':
solution = find_solution(20)
if solution is None:
print "No answer found"
else:
print "found an answer:", solution
print "took " + str(time.time() - start_time ) + " seconds"
我的系统大约需要37秒
即使我不必要地检查3、4、5、6、7、8、9、10和12的除数,我的代码也快了大约4倍。
我是python的新手,无法查看效率低下的原因。
编辑:
我做了另一个测试。
import time
start_time = time.time()
def ProjectEulerFive (m = 20):
ls = [11, 13, 14, 15, 16, 17, 18, 19]
a = m
i = 0
while i < len(ls):
if ( a % ls[i]) != 0:
a += m
i = 0
continue
else:
i += 1
return a
print ProjectEulerFive();
print "took " + str(time.time() - start_time ) + " seconds"
我的系统需要6秒钟,但这是:
import time
start_time = time.time()
def ProjectEulerFive (m = 20):
a = m
start = 11
b = start
while b < m:
if ( a % b) != 0:
a += m
b = start
continue
else:
b += 1
return a
print ProjectEulerFive()
print "took " + str(time.time() - start_time ) + " seconds"
大约需要3.7秒
我看到,尽管已经发布了一种更快的解决方案,但实际上没有人回答这个问题。 实际上,这是一个相当难回答的问题! 基本的解释是函数调用相对昂贵。 为了使这个结论具有说服力,我必须相当深入地研究Python内部。 做好准备!
首先,我将使用find_solution
从“优化”解决方案中分解ProjectEulerFive
(您的第三个版本)和dis.dis
。 这里有很多东西,但是需要快速扫描才能确认您的代码根本不调用任何函数 :
>>> dis.dis(ProjectEulerFive)
2 0 LOAD_FAST 0 (m)
3 STORE_FAST 1 (a)
3 6 LOAD_CONST 1 (11)
9 STORE_FAST 2 (start)
4 12 LOAD_FAST 2 (start)
15 STORE_FAST 3 (b)
5 18 SETUP_LOOP 64 (to 85)
>> 21 LOAD_FAST 3 (b)
24 LOAD_FAST 0 (m)
27 COMPARE_OP 0 (<)
30 POP_JUMP_IF_FALSE 84
6 33 LOAD_FAST 1 (a)
36 LOAD_FAST 3 (b)
39 BINARY_MODULO
40 LOAD_CONST 2 (0)
43 COMPARE_OP 3 (!=)
46 POP_JUMP_IF_FALSE 71
7 49 LOAD_FAST 1 (a)
52 LOAD_FAST 0 (m)
55 INPLACE_ADD
56 STORE_FAST 1 (a)
8 59 LOAD_FAST 2 (start)
62 STORE_FAST 3 (b)
9 65 JUMP_ABSOLUTE 21
68 JUMP_ABSOLUTE 21
11 >> 71 LOAD_FAST 3 (b)
74 LOAD_CONST 3 (1)
77 INPLACE_ADD
78 STORE_FAST 3 (b)
81 JUMP_ABSOLUTE 21
>> 84 POP_BLOCK
12 >> 85 LOAD_FAST 1 (a)
88 RETURN_VALUE
现在让我们看一下find_solution
:
>>> dis.dis(find_solution)
2 0 SETUP_LOOP 58 (to 61)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_FAST 0 (step)
9 LOAD_CONST 1 (999999999)
12 LOAD_FAST 0 (step)
15 CALL_FUNCTION 3
18 GET_ITER
>> 19 FOR_ITER 38 (to 60)
22 STORE_DEREF 0 (num)
3 25 LOAD_GLOBAL 1 (all)
28 LOAD_CLOSURE 0 (num)
31 BUILD_TUPLE 1
34 LOAD_CONST 2 (<code object <genexpr> at
0x10027eeb0, file "<stdin>",
line 3>)
37 MAKE_CLOSURE 0
40 LOAD_GLOBAL 2 (check_list)
43 GET_ITER
44 CALL_FUNCTION 1
47 CALL_FUNCTION 1
50 POP_JUMP_IF_FALSE 19
4 53 LOAD_DEREF 0 (num)
56 RETURN_VALUE
57 JUMP_ABSOLUTE 19
>> 60 POP_BLOCK
5 >> 61 LOAD_CONST 0 (None)
64 RETURN_VALUE
立刻清楚的是(a)此代码的复杂性要低得多,但是(b)它也调用了三个不同的函数。 第一个只是对xrange
的单个调用,而其他两个调用则出现在最外层的for循环内。 第一个电话是all
的电话; 我怀疑,第二个是生成器表达式的next
方法被调用。 但是功能到底是什么并不重要。 重要的是它们在循环内被调用。
现在,您可能会想“有什么大不了的?” 这里。 这只是一个函数调用; 在这里或那里几纳秒-是吗? 但实际上,这些纳秒加起来。 由于最外面的循环总共进行232792560 / 20 == 11639628
循环,因此任何开销都将乘以一千一百万以上 。 在ipython
中使用%timeit
命令进行快速计时, ipython
表明,在我的机器上,一个函数调用(全部本身)花费约120纳秒:
>>> def no_call():
... pass
...
>>> def calling():
... no_call()
...
>>> %timeit no_call()
10000000 loops, best of 3: 107 ns per loop
>>> %timeit calling()
1000000 loops, best of 3: 235 ns per loop
因此,对于出现在while循环内的每个函数调用,这将花费120 nanoseconds * 11000000 = 1.32 seconds
。 如果我没错,第二个函数调用是对生成器表达式的next
方法的调用,那么该函数被调用的次数甚至更多,一次通过Genex进行的每次迭代-平均每个循环3-4次。
现在来检验这个假设。 如果函数调用是问题所在,那么消除函数调用就是解决方案。 让我们来看看...
def find_solution(step):
for num in xrange(step, 999999999, step):
for n in check_list:
if num % n != 0:
break
else:
return num
return None
这是find_solution
的一个版本,该版本几乎与原始版本使用for/else
语法完全for/else
。 唯一的函数调用是外部调用xrange
,这不会引起任何问题。 现在,当我为原始版本计时时,花了11秒:
found an answer: 232792560
took 11.2349967957 seconds
让我们看看这个新的,改进的版本管理什么:
found an answer: 232792560
took 2.12648200989 seconds
这比我的计算机上最快版本的ProjectEulerFive
的性能快了很多:
232792560
took 2.40848493576 seconds
一切都重新变得有意义。
这应该不需要任何时间:
def gcd(a, b):
if (b == 0): return a
else: return gcd(b, a%b)
def lcm(a, b):
return abs(a*b) / gcd(a, b)
def euler5(n):
if (n == 1): return 1
else: return lcm(n, euler5(n-1))
print euler5(20)
不是您问题的答案(因此是社区Wiki),但是以下是计时功能的有用修饰符:
from functools import wraps
import time
def print_time(f):
@wraps(f)
def wrapper(*args, **kwargs):
t0 = time.time()
result = f(*args, **kwargs)
print "{0} took {1}s".format(f.__name__, time.time() - t0)
return result
return wrapper
用法如下:
@print_time
def foo(x, y):
time.sleep(1)
return x + y
在实践中:
>>> foo(1, 2)
foo took 1.0s
3
您可以使用主要因素解决此问题。 在0.0004s中求解n = 20,在0.0011中求解n = 50。
from math import sqrt
import time
num = int(input("Number: "))
start_time = time.clock()
def is_prime(n):
if(n == 2 or n == 3):
return True
elif(n < 2 or n % 2 == 0):
return False
for i in range(3, int(sqrt(n))+1, 2):
if(n % i == 0):
return False
return True
def decompose(n):
if(n == 1 or n == 0):
return [n]
l = []
residue = n
while(residue != 1):
for i in range(1, residue+1):
if(residue%i==0 and is_prime(i)):
l.append(i)
residue //= i
break
return l
l = []
for i in range(1, num):
d = decompose(i)
for n in d:
if(l.count(n) < d.count(n)):
l += [n]*(d.count(n)-l.count(n))
result = 1
for i in l:
result*=i
print(result)
print("time: ", time.clock()-start_time)
这是我的实现
我理解原因,但希望能启发您对其背后的数学知识有所了解
如果我写下不大于最高可除数的所有素数,则替换因子子集,其中包括小于我极限的素数除数。
from functools import reduce
def divisible_by_all_up_to(limit):
def is_prime(num):
if num == 2 or num == 3:
return True
if num % 2 == 0 or num < 2:
return False
for i in range(3, num):
if num % i == 0:
return False
return True
primes = [i for i in range(limit) if is_prime(i) == True]
mult = []
for index, value in enumerate(primes):
while value * value < limit:
value = value * value
mult += [value]
return mult
ans = divisible_by_all_up_to(20)
resp = reduce(lambda x, y: x*y, ans)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.